diff options
93 files changed, 23157 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9e6b84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,83 @@ +debian/*.log +*.o +*.lo +*.a +.deps +Makefile +Makefile.in +bscconfig.h +bscconfig.h.in +openbsc.pc +src/osmo-nitb/osmo-nitb +src/osmo-bsc_mgcp/osmo-bsc_mgcp +src/osmo-bsc/osmo-bsc +src/utils/meas_vis +src/utils/meas_json +src/utils/osmo-meas-pcap2db +src/utils/osmo-meas-udp2db +src/utils/smpp_mirror +*.*~ +*.sw? +.libs +*.pyc +*.gcda +*.gcno + +#configure +aclocal.m4 +autom4te.cache/ +config.log +config.status +config.guess +config.sub +configure +compile +depcomp +install-sh +missing +stamp-h1 +libtool +ltmain.sh + +# git-version-gen magic +.tarball-version +.version + + +# apps and app data +hlr.sqlite3 +src/utils/bs11_config +src/ipaccess/ipaccess-config +src/ipaccess/abisip-find +src/ipaccess/ipaccess-firmware +src/ipaccess/ipaccess-proxy +src/utils/isdnsync +src/nat/bsc_nat +src/gprs/osmo-sgsn +src/gprs/osmo-gbproxy +src/gprs/osmo-gtphub +src/osmo-bsc_nat/osmo-bsc_nat +src/libcommon/gsup_test_client +src/osmo-msc/osmo-msc + +#tests +tests/testsuite.dir +tests/*/*_test +# ignore compiled binaries like msc_vlr_test_foo; do not ignore +# msc_vlr_test_foo.{c,ok,err}, but do still ignore the corresponding .o object +# files: +tests/msc_vlr/msc_vlr_test_* +!tests/msc_vlr/msc_vlr_test_*.* +tests/msc_vlr/msc_vlr_test_*.o + + +tests/atconfig +tests/atlocal +tests/package.m4 +tests/testsuite +tests/testsuite.log + +gsn_restart +src/openbsc.cfg* +writtenconfig/ +gtphub_restart_count diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..1f71dcf --- /dev/null +++ b/.gitreview @@ -0,0 +1,3 @@ +[gerrit] +host=gerrit.osmocom.org +project=osmo-mgw diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..cda4057 --- /dev/null +++ b/.mailmap @@ -0,0 +1,12 @@ +Harald Welte <laforge@gnumonks.org> +Harald Welte <laforge@gnumonks.org> <laflocal@hanuman.gnumonks.org> +Harald Welte <laforge@gnumonks.org> <laflocal@goeller.de.gnumonks.org> +Holger Hans Peter Freyther <holger@moiji-mobile.com> <zecke@selfish.org> +Holger Hans Peter Freyther <holger@moiji-mobile.com> <ich@tamarin.(none)> +Holger Hans Peter Freyther <holgre@moiji-mobile.com> <holger@freyther.de> +Andreas Eversberg <jolly@eversberg.eu> +Andreas Eversberg <jolly@eversberg.eu> <Andreas.Eversberg@versatel.de> +Andreas Eversberg <jolly@eversberg.eu> <root@nuedel.(none)> +Pablo Neira Ayuso <pablo@soleta.eu> <pablo@gnumonks.org> +Max Suraev <msuraev@sysmocom.de> +Tom Tsou <tom.tsou@ettus.com> <tom@tsou.cc> @@ -0,0 +1,9 @@ +Harald Welte <laforge@gnumonks.org> +Holger Freyther <zecke@selfish.org> +Jan Luebbe <jluebbe@debian.org> +Stefan Schmidt <stefan@datenfreihafen.org> +Daniel Willmann <daniel@totalueberwachung.de> +Andreas Eversberg <Andreas.Eversberg@versatel.de> +Sylvain Munaut <246tnt@gmail.com> +Jacob Erlbeck <jerlbeck@sysmocom.de> +Neels Hofmeyr <nhofmeyr@sysmocom.de> @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..7b9ef95 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,34 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 + +## FIXME: automake >= 1.13 or autoconf >= 2.70 provide better suited AC_CONFIG_MACRO_DIRS for configure.ac +## remove line below when OE toolchain is updated to version which include those +ACLOCAL_AMFLAGS = -I m4 +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +SUBDIRS = \ + doc \ + include \ + src \ + tests \ + contrib \ + $(NULL) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = \ + libosmo-legacy-mgcp.pc \ + libosmo-mgcp-client.pc \ + libosmo-mgcp.pc \ + $(NULL) + +BUILT_SOURCES = $(top_srcdir)/.version +EXTRA_DIST = git-version-gen osmoappdesc.py .version + +@RELMAKE@ + +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version @@ -0,0 +1,28 @@ +About OsmoMGW +============= + +OsmoMGW originated from the OpenBSC project, which started as a minimalistic +all-in-one implementation of the GSM Network. In 2017, OpenBSC had reached +maturity and diversity (including M3UA SIGTRAN and 3G support in the form of +IuCS and IuPS interfaces) that naturally lead to a separation of the all-in-one +approach to fully independent separate programs as in typical GSM networks. + +OsmoMGW was one of the parts split off from the old openbsc.git. It originated +as a solution to merely navigate RTP streams through a NAT, but has since +matured to a Media Gateway implementation that is capable of streaming RTP for +2G (AoIP) and 3G (IuCS) GSM networks as well as (still not implemented at time +of writing) transcoding between TRAU, various RTP payloads and IuUP. + +The OsmoMGW program exposes an MGCP interface towards clients like OsmoMSC and +OsmoBSC, and receives and sends RTP streams as configured via MGCP. + +The libosmo-mgcp-client library exposes utilities used by e.g. OsmoMSC (found +in osmo-msc.git) to instruct OsmoMGW via its MGCP service. + +The libosmo-mgcp library exposes MGCP server utilities used by e.g. OsmoBSC-NAT +(found in osmo-bsc.git) to navigate RTP streams through a NAT. +(At time of writing, this is still called libosmo-legacy-mgcp.) + +Find OsmoMGW issue tracker and wiki online at +https://osmocom.org/projects/osmo-mgw +https://osmocom.org/projects/osmo-mgw/wiki diff --git a/README.vty-tests b/README.vty-tests new file mode 100644 index 0000000..ba1b87c --- /dev/null +++ b/README.vty-tests @@ -0,0 +1,11 @@ +To run the configuration parsing and output (VTY) test suite, first install + + git://git.osmocom.org/python/osmo-python-tests + +and pass the following configure options here: + + ./configure --enable-vty-tests --enable-external-tests + +The VTY tests are then included in the standard check target: + + make check diff --git a/TODO-RELEASE b/TODO-RELEASE new file mode 100644 index 0000000..d198b97 --- /dev/null +++ b/TODO-RELEASE @@ -0,0 +1,26 @@ +# When cleaning up this file upon a release: +# +# - Note that the release version number is entirely unrelated to the API +# versions. A release version 5.2.3 may happily have an API version of 42:7:5. +# +# - Bump API version in src/lib*/Makefile.am files according to chapter +# "Library interface versions" of the libtool documentation. +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +# +# - Iff the 'current' API version has changed, rename debian/lib*.install +# +# API version bumping for the impatient: +# LIBVERSION=c:r:a (current:revision_of_current:backwards_compat_age) +# 5:2:4 means that +# - this implements version 5 of the API; +# - this is the 2nd (compatible) revision of API version 5; +# - this is backwards compatible to all APIs since 4 versions ago, +# i.e. callers that need API versions from 1 to 5 can use this. +# +# Bumping API versions recipe: +# If the library source code has changed at all since the last update, r++; +# If any interfaces have been added, removed, or changed since the last update, c++, r=0; +# If any interfaces have been added since the last public release, a++; +# If any interfaces have been removed or changed since the last public release, a=0. +# +#library what description / commit summary line diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..dea41b1 --- /dev/null +++ b/configure.ac @@ -0,0 +1,142 @@ +dnl Process this file with autoconf to produce a configure script +AC_INIT([osmo-mgw], + m4_esyscmd([./git-version-gen .tarball-version]), + [openbsc@lists.osmocom.org]) + +dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ +AC_CONFIG_AUX_DIR([.]) + +AM_INIT_AUTOMAKE([dist-bzip2]) +AC_CONFIG_TESTDIR(tests) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl include release helper +RELMAKE='-include osmo-release.mk' +AC_SUBST([RELMAKE]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +LT_INIT + +dnl check for pkg-config (explained in detail in libosmocore/configure.ac) +AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no) +if test "x$PKG_CONFIG_INSTALLED" = "xno"; then + AC_MSG_WARN([You need to install pkg-config]) +fi +PKG_PROG_PKG_CONFIG([0.20]) + +dnl check for AX_CHECK_COMPILE_FLAG +m4_ifdef([AX_CHECK_COMPILE_FLAG], [], [ + AC_MSG_ERROR([Please install autoconf-archive; re-run 'autoreconf -fi' for it to take effect.]) + ]) + +dnl checks for libraries +AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""]) +AC_SUBST(LIBRARY_DL) + + +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.10.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.10.0) +PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.1.0) + +# Enable/disable transcoding within osmo-bsc_mgcp? +AC_ARG_ENABLE([mgcp-transcoding], [AS_HELP_STRING([--enable-mgcp-transcoding], [Build the MGCP gateway with internal transcoding enabled.])], + [osmo_ac_mgcp_transcoding="$enableval"],[osmo_ac_mgcp_transcoding="no"]) +AC_ARG_WITH([g729], [AS_HELP_STRING([--with-g729], [Enable G.729 encoding/decoding.])], [osmo_ac_with_g729="$withval"],[osmo_ac_with_g729="no"]) + +if test "$osmo_ac_mgcp_transcoding" = "yes" ; then + AC_SEARCH_LIBS([gsm_create], [gsm], [LIBRARY_GSM="$LIBS";LIBS=""], [AC_MSG_ERROR([--enable-mgcp-transcoding: cannot find usable libgsm])]) + AC_SUBST(LIBRARY_GSM) + if test "$osmo_ac_with_g729" = "yes" ; then + PKG_CHECK_MODULES(LIBBCG729, libbcg729 >= 0.1, [AC_DEFINE([HAVE_BCG729], [1], [Use bgc729 decoder/encoder])]) + fi + AC_DEFINE(BUILD_MGCP_TRANSCODING, 1, [Define if we want to build the MGCP gateway with transcoding support]) +fi +AM_CONDITIONAL(BUILD_MGCP_TRANSCODING, test "x$osmo_ac_mgcp_transcoding" = "xyes") +AC_SUBST(osmo_ac_mgcp_transcoding) + +dnl Checks for typedefs, structures and compiler characteristics + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +AX_CHECK_COMPILE_FLAG([-Werror=implicit], [CFLAGS="$CFLAGS -Werror=implicit"]) +AX_CHECK_COMPILE_FLAG([-Werror=maybe-uninitialized], [CFLAGS="$CFLAGS -Werror=maybe-uninitialized"]) +AX_CHECK_COMPILE_FLAG([-Werror=memset-transposed-args], [CFLAGS="$CFLAGS -Werror=memset-transposed-args"]) +AX_CHECK_COMPILE_FLAG([-Werror=null-dereference], [CFLAGS="$CFLAGS -Werror=null-dereference"]) +AX_CHECK_COMPILE_FLAG([-Werror=sizeof-array-argument], [CFLAGS="$CFLAGS -Werror=sizeof-array-argument"]) +AX_CHECK_COMPILE_FLAG([-Werror=sizeof-pointer-memaccess], [CFLAGS="$CFLAGS -Werror=sizeof-pointer-memaccess"]) + +# Coverage build taken from WebKit's configure.in +AC_MSG_CHECKING([whether to enable code coverage support]) +AC_ARG_ENABLE(coverage, + AC_HELP_STRING([--enable-coverage], + [enable code coverage support [default=no]]), + [],[enable_coverage="no"]) +AC_MSG_RESULT([$enable_coverage]) +if test "$enable_coverage" = "yes"; then + COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" + COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" + AC_SUBST([COVERAGE_CFLAGS]) + AC_SUBST([COVERAGE_LDFLAGS]) +fi + +AC_ARG_ENABLE([vty_tests], + AC_HELP_STRING([--enable-vty-tests], + [Include the VTY/CTRL tests in make check (deprecated) + [default=no]]), + [enable_ext_tests="$enableval"],[enable_ext_tests="no"]) +AC_ARG_ENABLE([external_tests], + AC_HELP_STRING([--enable-external-tests], + [Include the VTY/CTRL tests in make check [default=no]]), + [enable_ext_tests="$enableval"],[enable_ext_tests="no"]) +if test "x$enable_ext_tests" = "xyes" ; then + AM_PATH_PYTHON + AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes) + if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then + AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.]) + fi +fi +AC_MSG_CHECKING([whether to enable VTY/CTRL tests]) +AC_MSG_RESULT([$enable_ext_tests]) +AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes") + +dnl Generate the output +AM_CONFIG_HEADER(bscconfig.h) + +AC_OUTPUT( + libosmo-legacy-mgcp.pc + libosmo-mgcp-client.pc + libosmo-mgcp.pc + include/Makefile + include/osmocom/Makefile + include/osmocom/legacy_mgcp/Makefile + include/osmocom/mgcp_client/Makefile + include/osmocom/mgcp/Makefile + src/Makefile + src/libosmo-legacy-mgcp/Makefile + src/libosmo-mgcp-client/Makefile + src/libosmo-mgcp/Makefile + src/osmo-bsc_mgcp/Makefile + src/osmo-mgw/Makefile + tests/Makefile + tests/atlocal + tests/legacy_mgcp/Makefile + tests/mgcp_client/Makefile + tests/mgcp/Makefile + doc/Makefile + doc/examples/Makefile + contrib/Makefile + Makefile) diff --git a/contrib/Makefile.am b/contrib/Makefile.am new file mode 100644 index 0000000..db6d0f5 --- /dev/null +++ b/contrib/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST = ipa.py diff --git a/contrib/ipa.py b/contrib/ipa.py new file mode 100755 index 0000000..71cbf45 --- /dev/null +++ b/contrib/ipa.py @@ -0,0 +1,278 @@ +#!/usr/bin/python3 +# -*- mode: python-mode; py-indent-tabs-mode: nil -*- +""" +/* + * Copyright (C) 2016 sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +""" + +import struct, random, sys + +class IPA(object): + """ + Stateless IPA protocol multiplexer: add/remove/parse (extended) header + """ + version = "0.0.5" + TCP_PORT_OML = 3002 + TCP_PORT_RSL = 3003 + # OpenBSC extensions: OSMO, MGCP_OLD + PROTO = dict(RSL=0x00, CCM=0xFE, SCCP=0xFD, OML=0xFF, OSMO=0xEE, MGCP_OLD=0xFC) + # ...OML Router Control, GSUP GPRS extension, Osmocom Authn Protocol + EXT = dict(CTRL=0, MGCP=1, LAC=2, SMSC=3, ORC=4, GSUP=5, OAP=6) + # OpenBSC extension: SCCP_OLD + MSGT = dict(PING=0x00, PONG=0x01, ID_GET=0x04, ID_RESP=0x05, ID_ACK=0x06, SCCP_OLD=0xFF) + _IDTAG = dict(SERNR=0, UNITNAME=1, LOCATION=2, TYPE=3, EQUIPVERS=4, SWVERSION=5, IPADDR=6, MACADDR=7, UNIT=8) + CTRL_GET = 'GET' + CTRL_SET = 'SET' + CTRL_REP = 'REPLY' + CTRL_ERR = 'ERR' + CTRL_TRAP = 'TRAP' + + def _l(self, d, p): + """ + Reverse dictionary lookup: return key for a given value + """ + if p is None: + return 'UNKNOWN' + return list(d.keys())[list(d.values()).index(p)] + + def _tag(self, t, v): + """ + Create TAG as TLV data + """ + return struct.pack(">HB", len(v) + 1, t) + v + + def proto(self, p): + """ + Lookup protocol name + """ + return self._l(self.PROTO, p) + + def ext(self, p): + """ + Lookup protocol extension name + """ + return self._l(self.EXT, p) + + def msgt(self, p): + """ + Lookup message type name + """ + return self._l(self.MSGT, p) + + def idtag(self, p): + """ + Lookup ID tag name + """ + return self._l(self._IDTAG, p) + + def ext_name(self, proto, exten): + """ + Return proper extension byte name depending on the protocol used + """ + if self.PROTO['CCM'] == proto: + return self.msgt(exten) + if self.PROTO['OSMO'] == proto: + return self.ext(exten) + return None + + def add_header(self, data, proto, ext=None): + """ + Add IPA header (with extension if necessary), data must be represented as bytes + """ + if ext is None: + return struct.pack(">HB", len(data) + 1, proto) + data + return struct.pack(">HBB", len(data) + 1, proto, ext) + data + + def del_header(self, data): + """ + Strip IPA protocol header correctly removing extension if present + Returns data length, IPA protocol, extension (or None if not defined for a give protocol) and the data without header + """ + if not len(data): + return None, None, None, None + (dlen, proto) = struct.unpack('>HB', data[:3]) + if self.PROTO['OSMO'] == proto or self.PROTO['CCM'] == proto: # there's extension which we have to unpack + return struct.unpack('>HBB', data[:4]) + (data[4:], ) # length, protocol, extension, data + return dlen, proto, None, data[3:] # length, protocol, _, data + + def split_combined(self, data): + """ + Split the data which contains multiple concatenated IPA messages into tuple (first, rest) where rest contains remaining messages, first is the single IPA message + """ + (length, _, _, _) = self.del_header(data) + return data[:(length + 3)], data[(length + 3):] + + def tag_serial(self, data): + """ + Make TAG for serial number + """ + return self._tag(self._IDTAG['SERNR'], data) + + def tag_name(self, data): + """ + Make TAG for unit name + """ + return self._tag(self._IDTAG['UNITNAME'], data) + + def tag_loc(self, data): + """ + Make TAG for location + """ + return self._tag(self._IDTAG['LOCATION'], data) + + def tag_type(self, data): + """ + Make TAG for unit type + """ + return self._tag(self._IDTAG['TYPE'], data) + + def tag_equip(self, data): + """ + Make TAG for equipment version + """ + return self._tag(self._IDTAG['EQUIPVERS'], data) + + def tag_sw(self, data): + """ + Make TAG for software version + """ + return self._tag(self._IDTAG['SWVERSION'], data) + + def tag_ip(self, data): + """ + Make TAG for IP address + """ + return self._tag(self._IDTAG['IPADDR'], data) + + def tag_mac(self, data): + """ + Make TAG for MAC address + """ + return self._tag(self._IDTAG['MACADDR'], data) + + def tag_unit(self, data): + """ + Make TAG for unit ID + """ + return self._tag(self._IDTAG['UNIT'], data) + + def identity(self, unit=b'', mac=b'', location=b'', utype=b'', equip=b'', sw=b'', name=b'', serial=b''): + """ + Make IPA IDENTITY tag list, by default returns empty concatenated bytes of tag list + """ + return self.tag_unit(unit) + self.tag_mac(mac) + self.tag_loc(location) + self.tag_type(utype) + self.tag_equip(equip) + self.tag_sw(sw) + self.tag_name(name) + self.tag_serial(serial) + + def ping(self): + """ + Make PING message + """ + return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PING']) + + def pong(self): + """ + Make PONG message + """ + return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PONG']) + + def id_ack(self): + """ + Make ID_ACK CCM message + """ + return self.add_header(b'', self.PROTO['CCM'], self.MSGT['ID_ACK']) + + def id_get(self): + """ + Make ID_GET CCM message + """ + return self.add_header(self.identity(), self.PROTO['CCM'], self.MSGT['ID_GET']) + + def id_resp(self, data): + """ + Make ID_RESP CCM message + """ + return self.add_header(data, self.PROTO['CCM'], self.MSGT['ID_RESP']) + +class Ctrl(IPA): + """ + Osmocom CTRL protocol implemented on top of IPA multiplexer + """ + def __init__(self): + random.seed() + + def add_header(self, data): + """ + Add CTRL header + """ + return super(Ctrl, self).add_header(data.encode('utf-8'), IPA.PROTO['OSMO'], IPA.EXT['CTRL']) + + def rem_header(self, data): + """ + Remove CTRL header, check for appropriate protocol and extension + """ + (_, proto, ext, d) = super(Ctrl, self).del_header(data) + if self.PROTO['OSMO'] != proto or self.EXT['CTRL'] != ext: + return None + return d + + def parse(self, data, op=None): + """ + Parse Ctrl string returning (var, value) pair + var could be None in case of ERROR message + value could be None in case of GET message + """ + (s, i, v) = data.split(' ', 2) + if s == self.CTRL_ERR: + return None, v + if s == self.CTRL_GET: + return v, None + (s, i, var, val) = data.split(' ', 3) + if s == self.CTRL_TRAP and i != '0': + return None, '%s with non-zero id %s' % (s, i) + if op is not None and i != op: + if s == self.CTRL_GET + '_' + self.CTRL_REP or s == self.CTRL_SET + '_' + self.CTRL_REP: + return None, '%s with unexpected id %s' % (s, i) + return var, val + + def trap(self, var, val): + """ + Make TRAP message with given (vak, val) pair + """ + return self.add_header("%s 0 %s %s" % (self.CTRL_TRAP, var, val)) + + def cmd(self, var, val=None): + """ + Make SET/GET command message: returns (r, m) tuple where r is random operation id and m is assembled message + """ + r = random.randint(1, sys.maxsize) + if val is not None: + return r, self.add_header("%s %s %s %s" % (self.CTRL_SET, r, var, val)) + return r, self.add_header("%s %s %s" % (self.CTRL_GET, r, var)) + + def verify(self, reply, r, var, val=None): + """ + Verify reply to SET/GET command: returns (b, v) tuple where v is True/False verification result and v is the variable value + """ + (k, v) = self.parse(reply) + if k != var or (val is not None and v != val): + return False, v + return True, v + +if __name__ == '__main__': + print("IPA multiplexer v%s loaded." % IPA.version) diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh new file mode 100755 index 0000000..44152dc --- /dev/null +++ b/contrib/jenkins.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# jenkins build helper script for openbsc. This is how we build on jenkins.osmocom.org + +if ! [ -x "$(command -v osmo-build-dep.sh)" ]; then + echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !" + exit 2 +fi + + +set -ex + +base="$PWD" +deps="$base/deps" +inst="$deps/install" +export deps inst + +osmo-clean-workspace.sh + +mkdir "$deps" || true +osmo-build-dep.sh libosmocore "" ac_cv_path_DOXYGEN=false + +verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]") + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis +osmo-build-dep.sh libosmo-netif + +set +x +echo +echo +echo +echo " =============================== osmo-mgw ===============================" +echo +set -x + +cd "$base" +autoreconf --install --force +./configure $MGCP --enable-vty-tests --enable-external-tests +$MAKE $PARALLEL_MAKE +LD_LIBRARY_PATH="$inst/lib" $MAKE check \ + || cat-testlogs.sh +LD_LIBRARY_PATH="$inst/lib" \ + DISTCHECK_CONFIGURE_FLAGS="$MGCP --enable-vty-tests --enable-external-tests" \ + $MAKE distcheck \ + || cat-testlogs.sh + +osmo-clean-workspace.sh diff --git a/contrib/mgcp_server.py b/contrib/mgcp_server.py new file mode 100755 index 0000000..05c489d --- /dev/null +++ b/contrib/mgcp_server.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# Simple server for mgcp... send audit, receive response.. + +import socket, time + +MGCP_GATEWAY_PORT = 2427 +MGCP_CALLAGENT_PORT = 2727 + +rsip_resp = """200 321321332\r\n""" +audit_packet = """AUEP %d 13@mgw MGCP 1.0\r\n""" +crcx_packet = """CRCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nL: p:20, a:GSM-EFR, nt:IN\r\nM: recvonly\r\n""" +dlcx_packet = """DLCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nI: %d\r\n""" +mdcx_packet = """MDCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nI: %d\r\nL: p:20, a:GSM-EFR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 258696477 0 IN IP4 172.16.1.107\r\ns=-\r\nc=IN IP4 172.16.1.107\r\nt=0 0\r\nm=audio 6666 RTP/AVP 127\r\na=rtpmap:127 GSM-EFR/8000/1\r\na=ptime:20\r\na=recvonly\r\nm=image 4402 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n""" + +def hexdump(src, length=8): + """Recipe is from http://code.activestate.com/recipes/142812/""" + result = [] + digits = 4 if isinstance(src, unicode) else 2 + for i in xrange(0, len(src), length): + s = src[i:i+length] + hexa = b' '.join(["%0*X" % (digits, ord(x)) for x in s]) + text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s]) + result.append( b"%04X %-*s %s" % (i, length*(digits + 1), hexa, text) ) + return b'\n'.join(result) + +server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +server_socket.bind(("127.0.0.1", MGCP_CALLAGENT_PORT)) +server_socket.setblocking(1) + +last_ci = 1 +def send_and_receive(packet): + global last_ci + server_socket.sendto(packet, ("127.0.0.1", MGCP_GATEWAY_PORT)) + try: + data, addr = server_socket.recvfrom(4096) + + # attempt to store the CI of the response + list = data.split("\n") + for item in list: + if item.startswith("I: "): + last_ci = int(item[3:]) + + print hexdump(data), addr + except socket.error, e: + print e + pass + +def generate_tid(): + import random + return random.randint(0, 65123) + + + +while True: + send_and_receive(audit_packet % generate_tid()) + send_and_receive(crcx_packet % generate_tid() ) + send_and_receive(mdcx_packet % (generate_tid(), last_ci)) + send_and_receive(dlcx_packet % (generate_tid(), last_ci)) + + time.sleep(3) diff --git a/contrib/systemd/osmo-bsc-mgcp.service b/contrib/systemd/osmo-bsc-mgcp.service new file mode 100644 index 0000000..c040e60 --- /dev/null +++ b/contrib/systemd/osmo-bsc-mgcp.service @@ -0,0 +1,11 @@ +[Unit] +Description=OpenBSC MGCP + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-bsc_mgcp -s -c /etc/osmocom/osmo-bsc-mgcp.cfg +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..5a23107 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + examples \ + $(NULL) diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am new file mode 100644 index 0000000..530c3fa --- /dev/null +++ b/doc/examples/Makefile.am @@ -0,0 +1,22 @@ +CFG_FILES = find $(srcdir) -name '*.cfg*' | sed -e 's,^$(srcdir),,' + +dist-hook: + for f in $$($(CFG_FILES)); do \ + j="$(distdir)/$$f" && \ + mkdir -p "$$(dirname $$j)" && \ + $(INSTALL_DATA) $(srcdir)/$$f $$j; \ + done + +install-data-hook: + for f in $$($(CFG_FILES)); do \ + j="$(DESTDIR)$(docdir)/examples/$$f" && \ + mkdir -p "$$(dirname $$j)" && \ + $(INSTALL_DATA) $(srcdir)/$$f $$j; \ + done + +uninstall-hook: + @$(PRE_UNINSTALL) + for f in $$($(CFG_FILES)); do \ + j="$(DESTDIR)$(docdir)/examples/$$f" && \ + $(RM) $$j; \ + done diff --git a/doc/examples/osmo-bsc_mgcp/mgcp.cfg b/doc/examples/osmo-bsc_mgcp/mgcp.cfg new file mode 100644 index 0000000..442162f --- /dev/null +++ b/doc/examples/osmo-bsc_mgcp/mgcp.cfg @@ -0,0 +1,14 @@ +! +! MGCP configuration example +! +mgcp + !local ip 10.23.24.2 + !bts ip 10.24.24.1 + !bind ip 10.23.24.1 + bind port 2427 + rtp base 4000 + rtp force-ptime 20 + sdp audio payload number 98 + sdp audio payload name AMR/8000 + number endpoints 31 + no rtcp-omit diff --git a/doc/examples/osmo-mgw/osmo-mgw.cfg b/doc/examples/osmo-mgw/osmo-mgw.cfg new file mode 100644 index 0000000..d8ea3a5 --- /dev/null +++ b/doc/examples/osmo-mgw/osmo-mgw.cfg @@ -0,0 +1,13 @@ +! +! MGCP configuration example +! +mgcp + !local ip 10.23.24.2 + !bts ip 10.24.24.1 + !bind ip 10.23.24.1 + bind port 2427 + rtp force-ptime 20 + sdp audio payload number 98 + sdp audio payload name AMR/8000 + number endpoints 31 + no rtcp-omit diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..8e59c5a --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif test -d ./.git \ + && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..b52e5ea --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,15 @@ +SUBDIRS = \ + osmocom \ + $(NULL) + +nobase_include_HEADERS = \ + osmocom/legacy_mgcp/mgcp.h \ + osmocom/legacy_mgcp/mgcp_internal.h \ + osmocom/legacy_mgcp/osmux.h \ + osmocom/mgcp_client/mgcp_client.h \ + osmocom/mgcp_client/mgcp_common.h \ + osmocom/mgcp/mgcp.h \ + osmocom/mgcp/mgcp_common.h \ + osmocom/mgcp/mgcp_internal.h \ + osmocom/mgcp/osmux.h \ + $(NULL) diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am new file mode 100644 index 0000000..c752c40 --- /dev/null +++ b/include/osmocom/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = \ + legacy_mgcp \ + mgcp_client \ + mgcp \ + $(NULL) diff --git a/include/osmocom/legacy_mgcp/Makefile.am b/include/osmocom/legacy_mgcp/Makefile.am new file mode 100644 index 0000000..4a9550c --- /dev/null +++ b/include/osmocom/legacy_mgcp/Makefile.am @@ -0,0 +1,4 @@ +noinst_HEADERS = \ + mgcp_transcode.h \ + vty.h \ + $(NULL) diff --git a/include/osmocom/legacy_mgcp/mgcp.h b/include/osmocom/legacy_mgcp/mgcp.h new file mode 100644 index 0000000..147a0d5 --- /dev/null +++ b/include/osmocom/legacy_mgcp/mgcp.h @@ -0,0 +1,292 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef OPENBSC_MGCP_H +#define OPENBSC_MGCP_H + +#include <osmocom/core/msgb.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/logging.h> + +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#define RTP_PORT_DEFAULT 4000 +#define RTP_PORT_NET_DEFAULT 16000 + +/** + * Calculate the RTP audio port for the given multiplex + * and the direction. This allows a semi static endpoint + * to port calculation removing the need for the BSC + * and the MediaGateway to communicate. + * + * Port usage explained: + * base + (multiplex * 2) + 0 == local port to wait for network packets + * base + (multiplex * 2) + 1 == local port for rtcp + * + * The above port will receive packets from the BTS that need + * to be patched and forwarded to the network. + * The above port will receive packets from the network that + * need to be patched and forwarded to the BTS. + * + * We assume to have a static BTS IP address so we can differentiate + * network and BTS. + * + */ +static inline int rtp_calculate_port(int multiplex, int base) +{ + return base + (multiplex * 2); +} + + +/* + * Handling of MGCP Endpoints and the MGCP Config + */ +struct mgcp_endpoint; +struct mgcp_config; +struct mgcp_trunk_config; +struct mgcp_rtp_end; + +#define MGCP_ENDP_CRCX 1 +#define MGCP_ENDP_DLCX 2 +#define MGCP_ENDP_MDCX 3 + +/* + * what to do with the msg? + * - continue as usual? + * - reject and send a failure code? + * - defer? do not send anything + */ +#define MGCP_POLICY_CONT 4 +#define MGCP_POLICY_REJECT 5 +#define MGCP_POLICY_DEFER 6 + +typedef int (*mgcp_realloc)(struct mgcp_trunk_config *cfg, int endpoint); +typedef int (*mgcp_change)(struct mgcp_trunk_config *cfg, int endpoint, int state); +typedef int (*mgcp_policy)(struct mgcp_trunk_config *cfg, int endpoint, int state, const char *transactio_id); +typedef int (*mgcp_reset)(struct mgcp_trunk_config *cfg); +typedef int (*mgcp_rqnt)(struct mgcp_endpoint *endp, char tone); + +/** + * Return: + * < 0 in case no audio was processed + * >= 0 in case audio was processed. The remaining payload + * length will be returned. + */ +typedef int (*mgcp_processing)(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size); +typedef int (*mgcp_processing_setup)(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end); + +typedef void (*mgcp_get_format)(struct mgcp_endpoint *endp, + int *payload_type, + const char**subtype_name, + const char**fmtp_extra); + +#define PORT_ALLOC_STATIC 0 +#define PORT_ALLOC_DYNAMIC 1 + +/** + * This holds information on how to allocate ports + */ +struct mgcp_port_range { + int mode; + + /* addr or NULL to fall-back to default */ + char *bind_addr; + + /* pre-allocated from a base? */ + int base_port; + + /* dynamically allocated */ + int range_start; + int range_end; + int last_port; +}; + +#define MGCP_KEEPALIVE_ONCE (-1) + +struct mgcp_trunk_config { + struct llist_head entry; + + struct mgcp_config *cfg; + + int trunk_nr; + int trunk_type; + + char *audio_fmtp_extra; + char *audio_name; + int audio_payload; + int audio_send_ptime; + int audio_send_name; + int audio_loop; + + int no_audio_transcoding; + + int omit_rtcp; + int keepalive_interval; + + /* RTP patching */ + int force_constant_ssrc; /* 0: don't, 1: once */ + int force_aligned_timing; + + /* spec handling */ + int force_realloc; + + /* timer */ + struct osmo_timer_list keepalive_timer; + + unsigned int number_endpoints; + struct mgcp_endpoint *endpoints; +}; + +enum mgcp_role { + MGCP_BSC = 0, + MGCP_BSC_NAT, +}; + +enum mgcp_connection_mode { + MGCP_CONN_NONE = 0, + MGCP_CONN_RECV_ONLY = 1, + MGCP_CONN_SEND_ONLY = 2, + MGCP_CONN_RECV_SEND = MGCP_CONN_RECV_ONLY | MGCP_CONN_SEND_ONLY, + MGCP_CONN_LOOPBACK = 4 | MGCP_CONN_RECV_SEND, +}; + +struct mgcp_config { + int source_port; + char *local_ip; + char *source_addr; + char *bts_ip; + char *call_agent_addr; + + struct in_addr bts_in; + + /* transcoder handling */ + char *transcoder_ip; + struct in_addr transcoder_in; + int transcoder_remote_base; + + /* RTP processing */ + mgcp_processing rtp_processing_cb; + mgcp_processing_setup setup_rtp_processing_cb; + + mgcp_get_format get_net_downlink_format_cb; + + struct osmo_wqueue gw_fd; + + struct mgcp_port_range bts_ports; + struct mgcp_port_range net_ports; + struct mgcp_port_range transcoder_ports; + int endp_dscp; + + int bts_force_ptime; + + mgcp_change change_cb; + mgcp_policy policy_cb; + mgcp_reset reset_cb; + mgcp_realloc realloc_cb; + mgcp_rqnt rqnt_cb; + void *data; + + uint32_t last_call_id; + + /* trunk handling */ + struct mgcp_trunk_config trunk; + struct llist_head trunks; + + /* only used for start with a static configuration */ + int last_net_port; + int last_bts_port; + + enum mgcp_role role; + + /* osmux translator: 0 means disabled, 1 means enabled */ + int osmux; + /* addr to bind the server to */ + char *osmux_addr; + /* The BSC-NAT may ask for enabling osmux on demand. This tells us if + * the osmux socket is already initialized. + */ + int osmux_init; + /* osmux batch factor: from 1 to 4 maximum */ + int osmux_batch; + /* osmux batch size (in bytes) */ + int osmux_batch_size; + /* osmux port */ + uint16_t osmux_port; + /* Pad circuit with dummy messages until we see the first voice + * message. + */ + uint16_t osmux_dummy; +}; + +/* config management */ +struct mgcp_config *mgcp_config_alloc(void); +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg, + enum mgcp_role role); +int mgcp_vty_init(void); +int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg); +void mgcp_release_endp(struct mgcp_endpoint *endp); +void mgcp_initialize_endp(struct mgcp_endpoint *endp); +int mgcp_reset_transcoder(struct mgcp_config *cfg); +void mgcp_format_stats(struct mgcp_endpoint *endp, char *stats, size_t size); +int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, uint32_t *pr, uint32_t *_or, int *loss, uint32_t *jitter); + +void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval); + +/* + * format helper functions + */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg); + +/* adc helper */ +static inline int mgcp_timeslot_to_endpoint(int multiplex, int timeslot) +{ + if (timeslot == 0) { + LOGP(DLMGCP, LOGL_ERROR, "Timeslot should not be 0\n"); + timeslot = 255; + } + + return timeslot + (32 * multiplex); +} + +static inline void mgcp_endpoint_to_timeslot(int endpoint, int *multiplex, int *timeslot) +{ + *multiplex = endpoint / 32; + *timeslot = endpoint % 32; +} + +int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint); +int mgcp_send_reset_all(struct mgcp_config *cfg); + + +int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port); +int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, struct sockaddr_in *addr, char *buf, int rc); +int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len); + +#endif diff --git a/include/osmocom/legacy_mgcp/mgcp_internal.h b/include/osmocom/legacy_mgcp/mgcp_internal.h new file mode 100644 index 0000000..dcc75f1 --- /dev/null +++ b/include/osmocom/legacy_mgcp/mgcp_internal.h @@ -0,0 +1,337 @@ +/* MGCP Private Data */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <string.h> + +#include <osmocom/core/select.h> + +#define CI_UNUSED 0 + +enum mgcp_trunk_type { + MGCP_TRUNK_VIRTUAL, + MGCP_TRUNK_E1, +}; + +struct mgcp_rtp_stream_state { + uint32_t ssrc; + uint16_t last_seq; + uint32_t last_timestamp; + uint32_t err_ts_counter; + int32_t last_tsdelta; + uint32_t last_arrival_time; +}; + +struct mgcp_rtp_state { + int initialized; + int patch_ssrc; + + uint32_t orig_ssrc; + + int seq_offset; + + int32_t timestamp_offset; + uint32_t packet_duration; + + struct mgcp_rtp_stream_state in_stream; + struct mgcp_rtp_stream_state out_stream; + + /* jitter and packet loss calculation */ + int stats_initialized; + uint16_t stats_base_seq; + uint16_t stats_max_seq; + uint32_t stats_ssrc; + uint32_t stats_jitter; + int32_t stats_transit; + int stats_cycles; + bool patched_first_rtp_payload; /* FIXME: drop this, see OS#2459 */ +}; + +struct mgcp_rtp_codec { + uint32_t rate; + int channels; + uint32_t frame_duration_num; + uint32_t frame_duration_den; + + int payload_type; + char *audio_name; + char *subtype_name; +}; + +struct mgcp_rtp_end { + /* statistics */ + unsigned int packets; + unsigned int octets; + unsigned int dropped_packets; + struct in_addr addr; + + /* in network byte order */ + int rtp_port, rtcp_port; + + /* audio codec information */ + struct mgcp_rtp_codec codec; + struct mgcp_rtp_codec alt_codec; /* TODO/XXX: make it generic */ + + /* per endpoint data */ + int frames_per_packet; + uint32_t packet_duration_ms; + char *fmtp_extra; + int output_enabled; + int force_output_ptime; + + /* RTP patching */ + int force_constant_ssrc; /* -1: always, 0: don't, 1: once */ + int force_aligned_timing; + void *rtp_process_data; + + /* + * Each end has a socket... + */ + struct osmo_fd rtp; + struct osmo_fd rtcp; + + int local_port; + int local_alloc; +}; + +enum { + MGCP_TAP_BTS_IN, + MGCP_TAP_BTS_OUT, + MGCP_TAP_NET_IN, + MGCP_TAP_NET_OUT, + + /* last element */ + MGCP_TAP_COUNT +}; + +struct mgcp_rtp_tap { + int enabled; + struct sockaddr_in forward; +}; + +struct mgcp_lco { + char *string; + char *codec; + int pkt_period_min; /* time in ms */ + int pkt_period_max; /* time in ms */ +}; + +enum mgcp_type { + MGCP_RTP_DEFAULT = 0, + MGCP_RTP_TRANSCODED, + MGCP_OSMUX_BSC, + MGCP_OSMUX_BSC_NAT, +}; + +#include <osmocom/legacy_mgcp/osmux.h> + +struct mgcp_endpoint { + int allocated; + uint32_t ci; + char *callid; + struct mgcp_lco local_options; + int conn_mode; + int orig_mode; + + /* backpointer */ + struct mgcp_config *cfg; + struct mgcp_trunk_config *tcfg; + + /* port status for bts/net */ + struct mgcp_rtp_end bts_end; + struct mgcp_rtp_end net_end; + + /* + * For transcoding we will send from the local_port + * of trans_bts and it will arrive at trans_net from + * where we will forward it to the network. + */ + struct mgcp_rtp_end trans_bts; + struct mgcp_rtp_end trans_net; + enum mgcp_type type; + + /* sequence bits */ + struct mgcp_rtp_state net_state; + struct mgcp_rtp_state bts_state; + + /* fields for re-transmission */ + char *last_trans; + char *last_response; + + /* tap for the endpoint */ + struct mgcp_rtp_tap taps[MGCP_TAP_COUNT]; + + struct { + /* Osmux state: disabled, activating, active */ + enum osmux_state state; + /* Allocated Osmux circuit ID for this endpoint */ + int allocated_cid; + /* Used Osmux circuit ID for this endpoint */ + uint8_t cid; + /* handle to batch messages */ + struct osmux_in_handle *in; + /* handle to unbatch messages */ + struct osmux_out_handle out; + /* statistics */ + struct { + uint32_t chunks; + uint32_t octets; + } stats; + } osmux; +}; + +#define for_each_line(line, save) \ + for (line = strline_r(NULL, &save); line;\ + line = strline_r(NULL, &save)) + +static inline char *strline_r(char *str, char **saveptr) +{ + char *result; + + if (str) + *saveptr = str; + + result = *saveptr; + + if (*saveptr != NULL) { + *saveptr = strpbrk(*saveptr, "\r\n"); + + if (*saveptr != NULL) { + char *eos = *saveptr; + + if ((*saveptr)[0] == '\r' && (*saveptr)[1] == '\n') + (*saveptr)++; + (*saveptr)++; + if ((*saveptr)[0] == '\0') + *saveptr = NULL; + + *eos = '\0'; + } + } + + return result; +} + + + +#define ENDPOINT_NUMBER(endp) abs((int)(endp - endp->tcfg->endpoints)) + +/** + * Internal structure while parsing a request + */ +struct mgcp_parse_data { + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + char *trans; + char *save; + int found; +}; + +int mgcp_send_dummy(struct mgcp_endpoint *endp); +int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port); +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port); +int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *enp, int rtp_port); +int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *enp, int rtp_port); +int mgcp_free_rtp_port(struct mgcp_rtp_end *end); + +/* For transcoding we need to manage an in and an output that are connected */ +static inline int endp_back_channel(int endpoint) +{ + return endpoint + 60; +} + +struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index); +struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index); + +void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change, + struct mgcp_rtp_end *rtp); +uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *rtp); + +void mgcp_state_calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *, + uint32_t *expected, int *loss); +uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *); + +/* payload processing default functions */ +int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size); + +int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end); + +void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, + int *payload_type, + const char**subtype_name, + const char**fmtp_extra); + +/* internal RTP Annex A counting */ +void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + const uint16_t seq, const int32_t transit, + const uint32_t ssrc); + +int mgcp_set_ip_tos(int fd, int tos); + +enum { + MGCP_DEST_NET = 0, + MGCP_DEST_BTS, +}; + + +#define MGCP_DUMMY_LOAD 0x23 + + +/** + * SDP related information + */ +/* Assume audio frame length of 20ms */ +#define DEFAULT_RTP_AUDIO_FRAME_DUR_NUM 20 +#define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000 +#define DEFAULT_RTP_AUDIO_PACKET_DURATION_MS 20 +#define DEFAULT_RTP_AUDIO_DEFAULT_RATE 8000 +#define DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS 1 + +#define PTYPE_UNDEFINED (-1) +int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p); +int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, + int payload_type, const char *audio_name); + + +/** + * Internal network related + */ +static inline const char *mgcp_net_src_addr(struct mgcp_endpoint *endp) +{ + if (endp->cfg->net_ports.bind_addr) + return endp->cfg->net_ports.bind_addr; + return endp->cfg->source_addr; +} + +static inline const char *mgcp_bts_src_addr(struct mgcp_endpoint *endp) +{ + if (endp->cfg->bts_ports.bind_addr) + return endp->cfg->bts_ports.bind_addr; + return endp->cfg->source_addr; +} + +int mgcp_msg_terminate_nul(struct msgb *msg); diff --git a/include/osmocom/legacy_mgcp/mgcp_transcode.h b/include/osmocom/legacy_mgcp/mgcp_transcode.h new file mode 100644 index 0000000..147e48b --- /dev/null +++ b/include/osmocom/legacy_mgcp/mgcp_transcode.h @@ -0,0 +1,90 @@ +/* + * (C) 2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#ifndef OPENBSC_MGCP_TRANSCODE_H +#define OPENBSC_MGCP_TRANSCODE_H + +#include "bscconfig.h" + +#include <gsm.h> +#ifdef HAVE_BCG729 +#include <bcg729/decoder.h> +#include <bcg729/encoder.h> +#endif + +enum audio_format { + AF_INVALID, + AF_S16, + AF_L16, + AF_GSM, + AF_G729, + AF_PCMA, + AF_PCMU +}; + + +struct mgcp_process_rtp_state { + /* decoding */ + enum audio_format src_fmt; + union { + gsm gsm_handle; +#ifdef HAVE_BCG729 + bcg729DecoderChannelContextStruct *g729_dec; +#endif + } src; + size_t src_frame_size; + size_t src_samples_per_frame; + + /* processing */ + + /* encoding */ + enum audio_format dst_fmt; + union { + gsm gsm_handle; +#ifdef HAVE_BCG729 + bcg729EncoderChannelContextStruct *g729_enc; +#endif + } dst; + size_t dst_frame_size; + size_t dst_samples_per_frame; + int dst_packet_duration; + + int is_running; + uint16_t next_seq; + uint32_t next_time; + int16_t samples[10*160]; + size_t sample_cnt; + size_t sample_offs; +}; + + +int mgcp_transcoding_setup(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end); + +void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp, + int *payload_type, + const char**audio_name, + const char**fmtp_extra); + +int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size); + +int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst); +#endif /* OPENBSC_MGCP_TRANSCODE_H */ diff --git a/include/osmocom/legacy_mgcp/osmux.h b/include/osmocom/legacy_mgcp/osmux.h new file mode 100644 index 0000000..f3ea72a --- /dev/null +++ b/include/osmocom/legacy_mgcp/osmux.h @@ -0,0 +1,41 @@ +#ifndef _OPENBSC_OSMUX_H_ +#define _OPENBSC_OSMUX_H_ + +#include <osmocom/netif/osmux.h> + +#define OSMUX_PORT 1984 + +enum { + OSMUX_ROLE_BSC = 0, + OSMUX_ROLE_BSC_NAT, +}; + +int osmux_init(int role, struct mgcp_config *cfg); +int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint16_t port); +void osmux_disable_endpoint(struct mgcp_endpoint *endp); +void osmux_allocate_cid(struct mgcp_endpoint *endp); +void osmux_release_cid(struct mgcp_endpoint *endp); + +int osmux_xfrm_to_rtp(struct mgcp_endpoint *endp, int type, char *buf, int rc); +int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp); + +int osmux_send_dummy(struct mgcp_endpoint *endp); + +int osmux_get_cid(void); +void osmux_put_cid(uint8_t osmux_cid); +int osmux_used_cid(void); + +enum osmux_state { + OSMUX_STATE_DISABLED = 0, + OSMUX_STATE_NEGOTIATING, + OSMUX_STATE_ACTIVATING, + OSMUX_STATE_ENABLED, +}; + +enum osmux_usage { + OSMUX_USAGE_OFF = 0, + OSMUX_USAGE_ON = 1, + OSMUX_USAGE_ONLY = 2, +}; + +#endif diff --git a/include/osmocom/legacy_mgcp/vty.h b/include/osmocom/legacy_mgcp/vty.h new file mode 100644 index 0000000..c277c0a --- /dev/null +++ b/include/osmocom/legacy_mgcp/vty.h @@ -0,0 +1,31 @@ +#ifndef OPENBSC_VTY_H +#define OPENBSC_VTY_H + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/command.h> + +struct gsm_network; +struct vty; + +void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *); + +struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base); + +extern struct cmd_element cfg_description_cmd; +extern struct cmd_element cfg_no_description_cmd; + +enum mgcp_vty_node { + MGCP_NODE = _LAST_OSMOVTY_NODE + 1, + TRUNK_NODE, +}; + +struct log_info; +int bsc_vty_init(struct gsm_network *network); +int bsc_vty_init_extra(void); + +void msc_vty_init(struct gsm_network *msc_network); + +struct gsm_network *gsmnet_from_vty(struct vty *vty); + +#endif diff --git a/include/osmocom/mgcp/Makefile.am b/include/osmocom/mgcp/Makefile.am new file mode 100644 index 0000000..d706807 --- /dev/null +++ b/include/osmocom/mgcp/Makefile.am @@ -0,0 +1,9 @@ +noinst_HEADERS = \ + vty.h \ + mgcp_msg.h \ + mgcp_conn.h \ + mgcp_stat.h \ + mgcp_ep.h \ + mgcp_sdp.h \ + debug.h \ + $(NULL) diff --git a/include/osmocom/mgcp/debug.h b/include/osmocom/mgcp/debug.h new file mode 100644 index 0000000..ddeb0dc --- /dev/null +++ b/include/osmocom/mgcp/debug.h @@ -0,0 +1,35 @@ +/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <stdio.h> +#include <osmocom/core/linuxlist.h> + +#define DEBUG +#include <osmocom/core/logging.h> + +/* Debug Areas of the code */ +enum { + DRTP, + Debug_LastEntry, +}; + +extern const struct log_info log_info; diff --git a/include/osmocom/mgcp/mgcp.h b/include/osmocom/mgcp/mgcp.h new file mode 100644 index 0000000..42a91d8 --- /dev/null +++ b/include/osmocom/mgcp/mgcp.h @@ -0,0 +1,236 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <osmocom/core/msgb.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/logging.h> + +#include <osmocom/mgcp/mgcp_common.h> + +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#define RTP_PORT_DEFAULT_RANGE_START 16002 +#define RTP_PORT_DEFAULT_RANGE_END RTP_PORT_DEFAULT_RANGE_START + 64 + +/* + * Handling of MGCP Endpoints and the MGCP Config + */ +struct mgcp_endpoint; +struct mgcp_config; +struct mgcp_trunk_config; +struct mgcp_rtp_end; + +#define MGCP_ENDP_CRCX 1 +#define MGCP_ENDP_DLCX 2 +#define MGCP_ENDP_MDCX 3 + +/* + * what to do with the msg? + * - continue as usual? + * - reject and send a failure code? + * - defer? do not send anything + */ +#define MGCP_POLICY_CONT 4 +#define MGCP_POLICY_REJECT 5 +#define MGCP_POLICY_DEFER 6 + +typedef int (*mgcp_realloc)(struct mgcp_trunk_config *cfg, int endpoint); +typedef int (*mgcp_change)(struct mgcp_trunk_config *cfg, int endpoint, int state); +typedef int (*mgcp_policy)(struct mgcp_trunk_config *cfg, int endpoint, int state, const char *transactio_id); +typedef int (*mgcp_reset)(struct mgcp_trunk_config *cfg); +typedef int (*mgcp_rqnt)(struct mgcp_endpoint *endp, char tone); + +/** + * Return: + * < 0 in case no audio was processed + * >= 0 in case audio was processed. The remaining payload + * length will be returned. + */ +typedef int (*mgcp_processing)(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size); +typedef int (*mgcp_processing_setup)(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end); + +struct mgcp_conn_rtp; +typedef void (*mgcp_get_format)(struct mgcp_endpoint *endp, + int *payload_type, + const char**subtype_name, + const char**fmtp_extra, + struct mgcp_conn_rtp *conn); + +/** + * This holds information on how to allocate ports + */ +struct mgcp_port_range { + /* addr or NULL to fall-back to default */ + char *bind_addr; + + /* dynamically allocated */ + int range_start; + int range_end; + int last_port; + + /* set to true to enable automatic probing + * of the local bind IP-Address, bind_addr + * (or its fall back) is used when automatic + * probing fails */ + bool bind_addr_probe; +}; + +/* There are up to three modes in which the keep-alive dummy packet can be + * sent. The beviour is controlled viw the keepalive_interval member of the + * trunk config. If that member is set to 0 (MGCP_KEEPALIVE_NEVER) no dummy- + * packet is sent at all and the timer that sends regular dummy packets + * is no longer scheduled. If the keepalive_interval is set to -1, only + * one dummy packet is sent when an CRCX or an MDCX is performed. No timer + * is scheduled. For all vales greater 0, the a timer is scheduled and the + * value is used as interval. See also mgcp_keepalive_timer_cb(), + * handle_modify_con(), and handle_create_con() */ +#define MGCP_KEEPALIVE_ONCE (-1) +#define MGCP_KEEPALIVE_NEVER 0 + +struct mgcp_trunk_config { + struct llist_head entry; + + struct mgcp_config *cfg; + + int trunk_nr; + int trunk_type; + + char *audio_fmtp_extra; + char *audio_name; + int audio_payload; + int audio_send_ptime; + int audio_send_name; + int audio_loop; + + int no_audio_transcoding; + + int omit_rtcp; + int keepalive_interval; + + /* RTP patching */ + int force_constant_ssrc; /* 0: don't, 1: once */ + int force_aligned_timing; + + /* spec handling */ + int force_realloc; + + /* timer */ + struct osmo_timer_list keepalive_timer; + + /* When set, incoming RTP packets are not filtered + * when ports and ip-address do not match (debug) */ + int rtp_accept_all; + + unsigned int number_endpoints; + struct mgcp_endpoint *endpoints; +}; + +enum mgcp_role { + MGCP_BSC = 0, + MGCP_BSC_NAT, +}; + +struct mgcp_config { + int source_port; + char *local_ip; + char *source_addr; + char *call_agent_addr; + + /* RTP processing */ + mgcp_processing rtp_processing_cb; + mgcp_processing_setup setup_rtp_processing_cb; + + mgcp_get_format get_net_downlink_format_cb; + + struct osmo_wqueue gw_fd; + + struct mgcp_port_range net_ports; + int endp_dscp; + + int force_ptime; + + mgcp_change change_cb; + mgcp_policy policy_cb; + mgcp_reset reset_cb; + mgcp_realloc realloc_cb; + mgcp_rqnt rqnt_cb; + void *data; + + uint32_t last_call_id; + + /* trunk handling */ + struct mgcp_trunk_config trunk; + struct llist_head trunks; + + enum mgcp_role role; + + /* osmux translator: 0 means disabled, 1 means enabled */ + int osmux; + /* addr to bind the server to */ + char *osmux_addr; + /* The BSC-NAT may ask for enabling osmux on demand. This tells us if + * the osmux socket is already initialized. + */ + int osmux_init; + /* osmux batch factor: from 1 to 4 maximum */ + int osmux_batch; + /* osmux batch size (in bytes) */ + int osmux_batch_size; + /* osmux port */ + uint16_t osmux_port; + /* Pad circuit with dummy messages until we see the first voice + * message. + */ + uint16_t osmux_dummy; +}; + +/* config management */ +struct mgcp_config *mgcp_config_alloc(void); +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg, + enum mgcp_role role); +int mgcp_vty_init(void); +int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg); +void mgcp_release_endp(struct mgcp_endpoint *endp); +void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval); + +/* + * format helper functions + */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg); + + +int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint); +int mgcp_send_reset_all(struct mgcp_config *cfg); + + +int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port); +int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len); diff --git a/include/osmocom/mgcp/mgcp_common.h b/include/osmocom/mgcp/mgcp_common.h new file mode 100644 index 0000000..0eb1388 --- /dev/null +++ b/include/osmocom/mgcp/mgcp_common.h @@ -0,0 +1,71 @@ +/* MGCP common implementations. + * These are used in libosmo-mgcp as well as libosmo-mgcp-client. + * To avoid interdependency, these are implemented in .h file only. */ + +/* + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* Two copies of this file are kept in osmocom/mgcp/ and osmocom/mgcp_client/. + * Since both are by definition identical, use the old header exclusion ifdefs + * instead of '#pragma once' to avoid including both of these files. + * Though at the time of writing there are no such users, this allows including + * both libosmo-mgcp and libosmo-mgcp-client headers in the same file. */ +#ifndef OSMO_MGCP_COMMON_H +#define OSMO_MGCP_COMMON_H + +#include <string.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> + +#define for_each_non_empty_line(line, save) \ + for (line = strtok_r(NULL, "\r\n", &save); line; \ + line = strtok_r(NULL, "\r\n", &save)) + +enum mgcp_connection_mode { + MGCP_CONN_NONE = 0, + MGCP_CONN_RECV_ONLY = 1, + MGCP_CONN_SEND_ONLY = 2, + MGCP_CONN_RECV_SEND = MGCP_CONN_RECV_ONLY | MGCP_CONN_SEND_ONLY, + MGCP_CONN_LOOPBACK = 4 | MGCP_CONN_RECV_SEND, +}; + +/* Ensure that the msg->l2h is NUL terminated. */ +static inline int mgcp_msg_terminate_nul(struct msgb *msg) +{ + unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */ + if (tail[-1] == '\0') + /* nothing to do */; + else if (msgb_tailroom(msg) > 0) + tail[0] = '\0'; + else if (tail[-1] == '\r' || tail[-1] == '\n') + tail[-1] = '\0'; + else { + LOGP(DLMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: " + "Length: %d, Buffer size: %d\n", + msgb_l2len(msg), msg->data_len); + return -ENOTSUP; + } + return 0; +} + +#endif diff --git a/include/osmocom/mgcp/mgcp_conn.h b/include/osmocom/mgcp/mgcp_conn.h new file mode 100644 index 0000000..e0ae021 --- /dev/null +++ b/include/osmocom/mgcp/mgcp_conn.h @@ -0,0 +1,40 @@ +/* Message connection list handling */ + +/* + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/core/linuxlist.h> +#include <inttypes.h> + +struct mgcp_conn *mgcp_conn_alloc(void *ctx, struct mgcp_endpoint *endp, + uint32_t id, enum mgcp_conn_type type, + char *name); +struct mgcp_conn *mgcp_conn_get(struct mgcp_endpoint *endp, uint32_t id); +struct mgcp_conn_rtp *mgcp_conn_get_rtp(struct mgcp_endpoint *endp, + uint32_t id); +void mgcp_conn_free(struct mgcp_endpoint *endp, uint32_t id); +void mgcp_conn_free_oldest(struct mgcp_endpoint *endp); +void mgcp_conn_free_all(struct mgcp_endpoint *endp); +char *mgcp_conn_dump(struct mgcp_conn *conn); +struct mgcp_conn *mgcp_find_dst_conn(struct mgcp_conn *conn); diff --git a/include/osmocom/mgcp/mgcp_ep.h b/include/osmocom/mgcp/mgcp_ep.h new file mode 100644 index 0000000..e477a47 --- /dev/null +++ b/include/osmocom/mgcp/mgcp_ep.h @@ -0,0 +1,50 @@ +/* Endpoint types */ + +/* + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +struct sockaddr_in; +struct mgcp_conn; + +/* Callback type for RTP dispatcher functions + (e.g mgcp_dispatch_rtp_bridge_cb, see below) */ +typedef int (*mgcp_dispatch_rtp_cb) (int proto, struct sockaddr_in * addr, + char *buf, unsigned int buf_size, + struct mgcp_conn * conn); + +/*! MGCP endpoint properties */ +struct mgcp_endpoint_type { + /*!< maximum number of connections */ + int max_conns; + + /*!< callback that defines how to dispatch incoming RTP data */ + mgcp_dispatch_rtp_cb dispatch_rtp_cb; +}; + +/*! MGCP endpoint typeset */ +struct mgcp_endpoint_typeset { + struct mgcp_endpoint_type rtp; +}; + +/*! static MGCP endpoint typeset (pre-initalized, read-only) */ +extern const struct mgcp_endpoint_typeset ep_typeset; diff --git a/include/osmocom/mgcp/mgcp_internal.h b/include/osmocom/mgcp/mgcp_internal.h new file mode 100644 index 0000000..b9c1731 --- /dev/null +++ b/include/osmocom/mgcp/mgcp_internal.h @@ -0,0 +1,321 @@ +/* MGCP Private Data */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <string.h> +#include <inttypes.h> +#include <osmocom/core/select.h> +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/core/linuxlist.h> + +#define CI_UNUSED 0 + +#define CONN_ID_BTS 0 +#define CONN_ID_NET 1 + +enum mgcp_trunk_type { + MGCP_TRUNK_VIRTUAL, + MGCP_TRUNK_E1, +}; + +struct mgcp_rtp_stream_state { + uint32_t ssrc; + uint16_t last_seq; + uint32_t last_timestamp; + uint32_t err_ts_counter; + int32_t last_tsdelta; + uint32_t last_arrival_time; +}; + +struct mgcp_rtp_state { + int initialized; + int patch_ssrc; + + uint32_t orig_ssrc; + + int seq_offset; + + int32_t timestamp_offset; + uint32_t packet_duration; + + struct mgcp_rtp_stream_state in_stream; + struct mgcp_rtp_stream_state out_stream; + + /* jitter and packet loss calculation */ + int stats_initialized; + uint16_t stats_base_seq; + uint16_t stats_max_seq; + uint32_t stats_ssrc; + uint32_t stats_jitter; + int32_t stats_transit; + int stats_cycles; + bool patched_first_rtp_payload; /* FIXME: drop this, see OS#2459 */ +}; + +struct mgcp_rtp_codec { + uint32_t rate; + int channels; + uint32_t frame_duration_num; + uint32_t frame_duration_den; + + int payload_type; + char *audio_name; + char *subtype_name; +}; + +struct mgcp_rtp_end { + /* statistics */ + unsigned int packets_rx; + unsigned int octets_rx; + unsigned int packets_tx; + unsigned int octets_tx; + unsigned int dropped_packets; + struct in_addr addr; + + /* in network byte order */ + int rtp_port, rtcp_port; + + /* audio codec information */ + struct mgcp_rtp_codec codec; + struct mgcp_rtp_codec alt_codec; /* TODO/XXX: make it generic */ + + /* per endpoint data */ + int frames_per_packet; + uint32_t packet_duration_ms; + char *fmtp_extra; + int output_enabled; + int force_output_ptime; + + /* RTP patching */ + int force_constant_ssrc; /* -1: always, 0: don't, 1: once */ + int force_aligned_timing; + void *rtp_process_data; + + /* Each end has a separate socket for RTP and RTCP */ + struct osmo_fd rtp; + struct osmo_fd rtcp; + + int local_port; +}; + +struct mgcp_rtp_tap { + int enabled; + struct sockaddr_in forward; +}; + +struct mgcp_lco { + char *string; + char *codec; + int pkt_period_min; /* time in ms */ + int pkt_period_max; /* time in ms */ +}; + +/* Specific rtp connection type (see struct mgcp_conn_rtp) */ +enum mgcp_conn_rtp_type { + MGCP_RTP_DEFAULT = 0, + MGCP_OSMUX_BSC, + MGCP_OSMUX_BSC_NAT, +}; + +#include <osmocom/mgcp/osmux.h> +struct mgcp_conn; + +/* MGCP connection (RTP) */ +struct mgcp_conn_rtp { + + /* Backpointer to conn struct */ + struct mgcp_conn *conn; + + /* Specific connection type */ + enum mgcp_conn_rtp_type type; + + /* Port status */ + struct mgcp_rtp_end end; + + /* Sequence bits */ + struct mgcp_rtp_state state; + + /* taps for the rtp connection */ + struct mgcp_rtp_tap tap_in; + struct mgcp_rtp_tap tap_out; + + /* Osmux states (optional) */ + struct { + /* Osmux state: disabled, activating, active */ + enum osmux_state state; + /* Allocated Osmux circuit ID for this endpoint */ + int allocated_cid; + /* Used Osmux circuit ID for this endpoint */ + uint8_t cid; + /* handle to batch messages */ + struct osmux_in_handle *in; + /* handle to unbatch messages */ + struct osmux_out_handle out; + /* statistics */ + struct { + uint32_t chunks; + uint32_t octets; + } stats; + } osmux; +}; + +/*! Connection type, specifies which member of the union "u" in mgcp_conn + * contains a useful connection description (currently only RTP) */ +enum mgcp_conn_type { + MGCP_CONN_TYPE_RTP, +}; + +/*! MGCP connection (untyped) */ +struct mgcp_conn { + /*!< list head */ + struct llist_head entry; + + /*!< Backpointer to the endpoint where the conn belongs to */ + struct mgcp_endpoint *endp; + + /*!< type of the connection (union) */ + enum mgcp_conn_type type; + + /*!< mode of the connection */ + enum mgcp_connection_mode mode; + + /*!< copy of the mode to restore the original setting (VTY) */ + enum mgcp_connection_mode mode_orig; + + /*!< connection id to identify the conntion */ + uint32_t id; + + /*!< human readable name (vty, logging) */ + char name[256]; + + /*!< union with connection description */ + union { + struct mgcp_conn_rtp rtp; + } u; + + /*!< pointer to optional private data */ + void *priv; +}; + +#include <osmocom/mgcp/mgcp_conn.h> + +struct mgcp_endpoint_type; + +struct mgcp_endpoint { + char *callid; + struct mgcp_lco local_options; + + struct llist_head conns; + + /* backpointer */ + struct mgcp_config *cfg; + struct mgcp_trunk_config *tcfg; + + const struct mgcp_endpoint_type *type; + + /* fields for re-transmission */ + char *last_trans; + char *last_response; +}; + + +#define ENDPOINT_NUMBER(endp) abs((int)(endp - endp->tcfg->endpoints)) + +/** + * Internal structure while parsing a request + */ +struct mgcp_parse_data { + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + char *trans; + char *save; + int found; +}; + +int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr, + char *buf, int rc, struct mgcp_conn_rtp *conn_src, + struct mgcp_conn_rtp *conn_dst); +int mgcp_send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn); +int mgcp_dispatch_rtp_bridge_cb(int proto, struct sockaddr_in *addr, char *buf, + unsigned int buf_size, struct mgcp_conn *conn); +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port, + struct mgcp_conn_rtp *conn); +void mgcp_free_rtp_port(struct mgcp_rtp_end *end); + +/* For transcoding we need to manage an in and an output that are connected */ +static inline int endp_back_channel(int endpoint) +{ + return endpoint + 60; +} + +struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index); +struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index); + +void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change, + struct mgcp_rtp_end *rtp); +uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *rtp); + +/* payload processing default functions */ +int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size); + +int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end); + +void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, + int *payload_type, + const char**audio_name, + const char**fmtp_extra, + struct mgcp_conn_rtp *conn); + +/* internal RTP Annex A counting */ +void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + const uint16_t seq, const int32_t transit, + const uint32_t ssrc); + +int mgcp_set_ip_tos(int fd, int tos); + +enum { + MGCP_DEST_NET = 0, + MGCP_DEST_BTS, +}; + + +#define MGCP_DUMMY_LOAD 0x23 + + +/** + * SDP related information + */ +/* Assume audio frame length of 20ms */ +#define DEFAULT_RTP_AUDIO_FRAME_DUR_NUM 20 +#define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000 +#define DEFAULT_RTP_AUDIO_PACKET_DURATION_MS 20 +#define DEFAULT_RTP_AUDIO_DEFAULT_RATE 8000 +#define DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS 1 + +#define PTYPE_UNDEFINED (-1) + +void mgcp_get_local_addr(char *addr, struct mgcp_conn_rtp *conn); diff --git a/include/osmocom/mgcp/mgcp_msg.h b/include/osmocom/mgcp/mgcp_msg.h new file mode 100644 index 0000000..b7d52bb --- /dev/null +++ b/include/osmocom/mgcp/mgcp_msg.h @@ -0,0 +1,58 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* Message parser/generator utilities */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <stdint.h> + +struct mgcp_conn; +struct mgcp_parse_data; +struct mgcp_endpoint; + +void mgcp_disp_msg(unsigned char *message, unsigned int len, char *preamble); + +int mgcp_parse_conn_mode(const char *msg, struct mgcp_endpoint *endp, + struct mgcp_conn *conn); + +int mgcp_parse_header(struct mgcp_parse_data *pdata, char *data); + +int mgcp_parse_osmux_cid(const char *line); + +int mgcp_check_param(const struct mgcp_endpoint *endp, const char *line); + +int mgcp_verify_call_id(struct mgcp_endpoint *endp, const char *callid); + +int mgcp_verify_ci(struct mgcp_endpoint *endp, const char *ci); + +char *mgcp_strline(char *str, char **saveptr); + +#define for_each_line(line, save)\ + for (line = mgcp_strline(NULL, &save); line;\ + line = mgcp_strline(NULL, &save)) + +#define for_each_non_empty_line(line, save)\ + for (line = strtok_r(NULL, "\r\n", &save); line;\ + line = strtok_r(NULL, "\r\n", &save)) + +int mgcp_parse_ci(uint32_t *conn_id, const char *ci); diff --git a/include/osmocom/mgcp/mgcp_sdp.h b/include/osmocom/mgcp/mgcp_sdp.h new file mode 100644 index 0000000..da23cba --- /dev/null +++ b/include/osmocom/mgcp/mgcp_sdp.h @@ -0,0 +1,35 @@ +/* + * SDP generation and parsing + * + * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once +#include <osmocom/mgcp/mgcp_sdp.h> + +int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, + struct mgcp_conn_rtp *conn, + struct mgcp_parse_data *p); + +int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, + int payload_type, const char *audio_name); + +int mgcp_write_response_sdp(const struct mgcp_endpoint *endp, + const struct mgcp_conn_rtp *conn, struct msgb *sdp, + const char *addr); diff --git a/include/osmocom/mgcp/mgcp_stat.h b/include/osmocom/mgcp/mgcp_stat.h new file mode 100644 index 0000000..b6c73fa --- /dev/null +++ b/include/osmocom/mgcp/mgcp_stat.h @@ -0,0 +1,37 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The statistics generator */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <osmocom/mgcp/mgcp_internal.h> +#include <inttypes.h> + +void mgcp_format_stats(char *str, size_t str_len, struct mgcp_conn *conn); + +/* Exposed for test purposes only, do not use actively */ +void calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *, + uint32_t *expected, int *loss); + +/* Exposed for test purposes only, do not use actively */ +uint32_t calc_jitter(struct mgcp_rtp_state *); diff --git a/include/osmocom/mgcp/osmux.h b/include/osmocom/mgcp/osmux.h new file mode 100644 index 0000000..ea849c8 --- /dev/null +++ b/include/osmocom/mgcp/osmux.h @@ -0,0 +1,38 @@ +#pragma once + + +#include <osmocom/netif/osmux.h> +struct mgcp_conn_rtp; + +#define OSMUX_PORT 1984 + +enum { + OSMUX_ROLE_BSC = 0, + OSMUX_ROLE_BSC_NAT, +}; + +int osmux_init(int role, struct mgcp_config *cfg); +int osmux_enable_conn(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn, + struct in_addr *addr, uint16_t port); +void osmux_disable_conn(struct mgcp_conn_rtp *conn); +void osmux_allocate_cid(struct mgcp_conn_rtp *conn); +void osmux_release_cid(struct mgcp_conn_rtp *conn); +int osmux_xfrm_to_osmux(char *buf, int buf_len, struct mgcp_conn_rtp *conn); +int osmux_send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn); +int osmux_get_cid(void); +void osmux_put_cid(uint8_t osmux_cid); +int osmux_used_cid(void); + +enum osmux_state { + OSMUX_STATE_DISABLED = 0, + OSMUX_STATE_NEGOTIATING, + OSMUX_STATE_ACTIVATING, + OSMUX_STATE_ENABLED, +}; + +enum osmux_usage { + OSMUX_USAGE_OFF = 0, + OSMUX_USAGE_ON = 1, + OSMUX_USAGE_ONLY = 2, +}; + diff --git a/include/osmocom/mgcp/vty.h b/include/osmocom/mgcp/vty.h new file mode 100644 index 0000000..c277c0a --- /dev/null +++ b/include/osmocom/mgcp/vty.h @@ -0,0 +1,31 @@ +#ifndef OPENBSC_VTY_H +#define OPENBSC_VTY_H + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/command.h> + +struct gsm_network; +struct vty; + +void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *); + +struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base); + +extern struct cmd_element cfg_description_cmd; +extern struct cmd_element cfg_no_description_cmd; + +enum mgcp_vty_node { + MGCP_NODE = _LAST_OSMOVTY_NODE + 1, + TRUNK_NODE, +}; + +struct log_info; +int bsc_vty_init(struct gsm_network *network); +int bsc_vty_init_extra(void); + +void msc_vty_init(struct gsm_network *msc_network); + +struct gsm_network *gsmnet_from_vty(struct vty *vty); + +#endif diff --git a/include/osmocom/mgcp_client/Makefile.am b/include/osmocom/mgcp_client/Makefile.am new file mode 100644 index 0000000..d303ddf --- /dev/null +++ b/include/osmocom/mgcp_client/Makefile.am @@ -0,0 +1,13 @@ +BUILT_SOURCES = \ + mgcp_common.h \ + $(NULL) + +noinst_HEADERS = \ + mgcp_client_internal.h \ + $(NULL) + +mgcp_common.h: $(top_srcdir)/include/osmocom/mgcp/mgcp_common.h + echo -e "/*\n\n DO NOT EDIT THIS FILE!\n THIS IS OVERWRITTEN DURING BUILD\n This is an automatic copy of <osmocom/mgcp/mgcp_common.h>\n\n */" > mgcp_common.h + cat $(top_srcdir)/include/osmocom/mgcp/mgcp_common.h >> mgcp_common.h + +CLEANFILES = mgcp_common.h diff --git a/include/osmocom/mgcp_client/mgcp_client.h b/include/osmocom/mgcp_client/mgcp_client.h new file mode 100644 index 0000000..e91b190 --- /dev/null +++ b/include/osmocom/mgcp_client/mgcp_client.h @@ -0,0 +1,118 @@ +#pragma once + +#include <stdint.h> +#include <arpa/inet.h> + +#include <osmocom/mgcp_client/mgcp_common.h> + +#define MGCP_CLIENT_LOCAL_ADDR_DEFAULT "0.0.0.0" +#define MGCP_CLIENT_LOCAL_PORT_DEFAULT 0 +#define MGCP_CLIENT_REMOTE_ADDR_DEFAULT "127.0.0.1" +#define MGCP_CLIENT_REMOTE_PORT_DEFAULT 2427 + +struct msgb; +struct vty; +struct mgcp_client; + +struct mgcp_client_conf { + const char *local_addr; + int local_port; + const char *remote_addr; + int remote_port; + uint16_t first_endpoint; + uint16_t last_endpoint; + uint16_t bts_base; +}; + +typedef unsigned int mgcp_trans_id_t; + +struct mgcp_response_head { + int response_code; + mgcp_trans_id_t trans_id; + const char *comment; +}; + +struct mgcp_response { + char *body; + struct mgcp_response_head head; + uint16_t audio_port; + char audio_ip[INET_ADDRSTRLEN]; +}; + +enum mgcp_verb { + MGCP_VERB_CRCX, + MGCP_VERB_MDCX, + MGCP_VERB_DLCX, + MGCP_VERB_AUEP, + MGCP_VERB_RSIP, +}; + +#define MGCP_MSG_PRESENCE_ENDPOINT 0x0001 +#define MGCP_MSG_PRESENCE_CALL_ID 0x0002 +#define MGCP_MSG_PRESENCE_CONN_ID 0x0004 +#define MGCP_MSG_PRESENCE_AUDIO_IP 0x0008 +#define MGCP_MSG_PRESENCE_AUDIO_PORT 0x0010 +#define MGCP_MSG_PRESENCE_CONN_MODE 0x0020 + +/* See also RFC3435 section 3.2.1.3 */ +#define MGCP_ENDPOINT_MAXLEN (255*2+1+1) + +struct mgcp_msg { + enum mgcp_verb verb; + /* See MGCP_MSG_PRESENCE_* constants */ + uint32_t presence; + char endpoint[MGCP_ENDPOINT_MAXLEN]; + unsigned int call_id; + uint32_t conn_id; + uint16_t audio_port; + char *audio_ip; + enum mgcp_connection_mode conn_mode; +}; + +void mgcp_client_conf_init(struct mgcp_client_conf *conf); +void mgcp_client_vty_init(void *talloc_ctx, int node, struct mgcp_client_conf *conf); +int mgcp_client_config_write(struct vty *vty, const char *indent); +struct mgcp_client_conf *mgcp_client_conf_actual(struct mgcp_client *mgcp); + +struct mgcp_client *mgcp_client_init(void *ctx, + struct mgcp_client_conf *conf); +int mgcp_client_connect(struct mgcp_client *mgcp); + +const char *mgcp_client_remote_addr_str(struct mgcp_client *mgcp); +uint16_t mgcp_client_remote_port(struct mgcp_client *mgcp); +uint32_t mgcp_client_remote_addr_n(struct mgcp_client *mgcp); + +int mgcp_client_next_endpoint(struct mgcp_client *client); +void mgcp_client_release_endpoint(uint16_t id, struct mgcp_client *client); + +/* Invoked when an MGCP response is received or sending failed. When the + * response is passed as NULL, this indicates failure during transmission. */ +typedef void (* mgcp_response_cb_t )(struct mgcp_response *response, void *priv); +int mgcp_response_parse_params(struct mgcp_response *r); + +int mgcp_client_tx(struct mgcp_client *mgcp, struct msgb *msg, + mgcp_response_cb_t response_cb, void *priv); + +enum mgcp_connection_mode; + +struct msgb *mgcp_msg_crcx(struct mgcp_client *mgcp, + uint16_t rtp_endpoint, unsigned int call_id, + enum mgcp_connection_mode mode) +OSMO_DEPRECATED("Use mgcp_msg_gen() instead"); + +struct msgb *mgcp_msg_mdcx(struct mgcp_client *mgcp, + uint16_t rtp_endpoint, const char *rtp_conn_addr, + uint16_t rtp_port, enum mgcp_connection_mode mode) +OSMO_DEPRECATED("Use mgcp_msg_gen() instead"); + +struct msgb *mgcp_msg_dlcx(struct mgcp_client *mgcp, uint16_t rtp_endpoint, + unsigned int call_id) +OSMO_DEPRECATED("Use mgcp_msg_gen() instead"); + +struct msgb *mgcp_msg_gen(struct mgcp_client *mgcp, struct mgcp_msg *mgcp_msg); + +extern const struct value_string mgcp_client_connection_mode_strs[]; +static inline const char *mgcp_client_cmode_name(enum mgcp_connection_mode mode) +{ + return get_value_string(mgcp_client_connection_mode_strs, mode); +} diff --git a/include/osmocom/mgcp_client/mgcp_client_internal.h b/include/osmocom/mgcp_client/mgcp_client_internal.h new file mode 100644 index 0000000..690a4af --- /dev/null +++ b/include/osmocom/mgcp_client/mgcp_client_internal.h @@ -0,0 +1,35 @@ +#pragma once + +#include <osmocom/core/write_queue.h> + +#define MSGB_CB_MGCP_TRANS_ID 0 + +struct mgcp_client { + struct mgcp_client_conf actual; + uint32_t remote_addr; + struct osmo_wqueue wq; + mgcp_trans_id_t next_trans_id; + struct llist_head responses_pending; + struct llist_head inuse_endpoints; +}; + +struct mgcp_inuse_endpoint { + struct llist_head entry; + uint16_t id; +}; + +struct mgcp_response_pending { + struct llist_head entry; + + mgcp_trans_id_t trans_id; + mgcp_response_cb_t response_cb; + void *priv; +}; + +int mgcp_client_rx(struct mgcp_client *mgcp, struct msgb *msg); + +struct mgcp_response_pending * mgcp_client_pending_add( + struct mgcp_client *mgcp, + mgcp_trans_id_t trans_id, + mgcp_response_cb_t response_cb, + void *priv); diff --git a/libosmo-legacy-mgcp.pc.in b/libosmo-legacy-mgcp.pc.in new file mode 100644 index 0000000..62d9edc --- /dev/null +++ b/libosmo-legacy-mgcp.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom legacy Media Gateway Control Protocol library +Description: C Utility Library +Version: @VERSION@ +Libs: -L${libdir} -losmo-legacy-mgcp +Cflags: -I${includedir}/ diff --git a/libosmo-mgcp-client.pc.in b/libosmo-mgcp-client.pc.in new file mode 100644 index 0000000..aee86dc --- /dev/null +++ b/libosmo-mgcp-client.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom Media Gateway Control Protocol Client library +Description: C Utility Library +Version: @VERSION@ +Libs: -L${libdir} -losmo-mgcp-client +Cflags: -I${includedir}/ diff --git a/libosmo-mgcp.pc.in b/libosmo-mgcp.pc.in new file mode 100644 index 0000000..63b794a --- /dev/null +++ b/libosmo-mgcp.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom Media Gateway Control Protocol library +Description: C Utility Library +Version: @VERSION@ +Libs: -L${libdir} -losmo-mgcp +Cflags: -I${includedir}/ diff --git a/m4/README b/m4/README new file mode 100644 index 0000000..92eb30b --- /dev/null +++ b/m4/README @@ -0,0 +1,3 @@ +We want to avoid creating too many external build-time dependencies +like this one to autoconf-archive. This directory provides a local +copy of required m4 rules. diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 0000000..ca36397 --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,74 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 4 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/osmoappdesc.py b/osmoappdesc.py new file mode 100644 index 0000000..c9ebf03 --- /dev/null +++ b/osmoappdesc.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com> +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> + + +app_configs = { + "osmo-mgw": ["doc/examples/osmo-mgw/osmo-mgw.cfg"], + "osmo-bsc_mgcp": ["doc/examples/osmo-bsc_mgcp/mgcp.cfg"], +} + +apps = [(4243, "src/osmo-mgw/osmo-mgw", "OsmoMGW", "osmo-mgw"), + (4243, "src/osmo-bsc_mgcp/osmo-bsc_mgcp", "OpenBSC MGCP", "osmo-bsc_mgcp"), + ] + +vty_command = ["./src/osmo-mgw/osmo-mgw", "-c", + "doc/examples/osmo-mgw/osmo-mgw.cfg"] + +vty_app = apps[0] diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..fab1bf0 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,33 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +# Libraries +SUBDIRS = \ + libosmo-legacy-mgcp \ + libosmo-mgcp-client \ + libosmo-mgcp \ + $(NULL) + +# Programs +SUBDIRS += \ + osmo-bsc_mgcp \ + osmo-mgw \ + $(NULL) diff --git a/src/libosmo-legacy-mgcp/Makefile.am b/src/libosmo-legacy-mgcp/Makefile.am new file mode 100644 index 0000000..9160586 --- /dev/null +++ b/src/libosmo-legacy-mgcp/Makefile.am @@ -0,0 +1,51 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(LIBBCG729_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(LIBBCG729_LIBS) \ + $(LIBRARY_GSM) \ + $(NULL) + +# This is not at all related to the release version, but a range of supported +# API versions. Read TODO_RELEASE in the source tree's root! +LEGACY_MGCP_LIBVERSION=0:0:0 + +lib_LTLIBRARIES = \ + libosmo-legacy-mgcp.la \ + $(NULL) + +noinst_HEADERS = \ + g711common.h \ + $(NULL) + +libosmo_legacy_mgcp_la_SOURCES = \ + mgcp_common.c \ + mgcp_protocol.c \ + mgcp_network.c \ + mgcp_vty.c \ + mgcp_osmux.c \ + mgcp_sdp.c \ + $(NULL) +if BUILD_MGCP_TRANSCODING +libosmo_legacy_mgcp_la_SOURCES += \ + mgcp_transcode.c \ + $(NULL) +endif + +libosmo_legacy_mgcp_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LEGACY_MGCP_LIBVERSION) diff --git a/src/libosmo-legacy-mgcp/g711common.h b/src/libosmo-legacy-mgcp/g711common.h new file mode 100644 index 0000000..cb35fc6 --- /dev/null +++ b/src/libosmo-legacy-mgcp/g711common.h @@ -0,0 +1,187 @@ +/* + * PCM - A-Law conversion + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * Wrapper for linphone Codec class by Simon Morlat <simon.morlat@linphone.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; /*7 = 4 + 3*/ + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +/* + * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + * G711 is designed for 13 bits input signal, this function add extra shifting to take this into account. + */ + +static inline unsigned char s16_to_alaw(int pcm_val) +{ + int mask; + int seg; + unsigned char aval; + + if (pcm_val >= 0) { + mask = 0xD5; + } else { + mask = 0x55; + pcm_val = -pcm_val; + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + } + + if (pcm_val < 256) /*256 = 32 << 3*/ + aval = pcm_val >> 4; /*4 = 1 + 3*/ + else { + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + } + return aval ^ mask; +} + +/* + * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM + * + */ +static inline int alaw_to_s16(unsigned char a_val) +{ + int t; + int seg; + + a_val ^= 0x55; + t = a_val & 0x7f; + if (t < 16) + t = (t << 4) + 8; + else { + seg = (t >> 4) & 0x07; + t = ((t & 0x0f) << 4) + 0x108; + t <<= seg -1; + } + return ((a_val & 0x80) ? t : -t); +} +/* + * s16_to_ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + +static inline unsigned char s16_to_ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + if (pcm_val < 0) { + pcm_val = 0x84 - pcm_val; + mask = 0x7f; + } else { + pcm_val += 0x84; + mask = 0xff; + } + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + return uval ^ mask; +} + +/* + * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +static inline int ulaw_to_s16(unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & 0x0f) << 3) + 0x84; + t <<= (u_val & 0x70) >> 4; + + return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84)); +} diff --git a/src/libosmo-legacy-mgcp/mgcp_common.c b/src/libosmo-legacy-mgcp/mgcp_common.c new file mode 100644 index 0000000..444a060 --- /dev/null +++ b/src/libosmo-legacy-mgcp/mgcp_common.c @@ -0,0 +1,54 @@ +/* Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* Implementations useful both for the MGCP GW as well as MGCP GW clients */ + +/* + * (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/legacy_mgcp/mgcp.h> + +const struct value_string mgcp_connection_mode_strs[] = { + { MGCP_CONN_NONE, "none" }, + { MGCP_CONN_RECV_SEND, "sendrecv" }, + { MGCP_CONN_SEND_ONLY, "sendonly" }, + { MGCP_CONN_RECV_ONLY, "recvonly" }, + { MGCP_CONN_LOOPBACK, "loopback" }, + { 0, NULL } +}; + +/* Ensure that the msg->l2h is NUL terminated. */ +int mgcp_msg_terminate_nul(struct msgb *msg) +{ + unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */ + if (tail[-1] == '\0') + /* nothing to do */; + else if (msgb_tailroom(msg) > 0) + tail[0] = '\0'; + else if (tail[-1] == '\r' || tail[-1] == '\n') + tail[-1] = '\0'; + else { + LOGP(DLMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: " + "Length: %d, Buffer size: %d\n", + msgb_l2len(msg), msg->data_len); + return -ENOTSUP; + } + return 0; +} diff --git a/src/libosmo-legacy-mgcp/mgcp_network.c b/src/libosmo-legacy-mgcp/mgcp_network.c new file mode 100644 index 0000000..8ccfb42 --- /dev/null +++ b/src/libosmo-legacy-mgcp/mgcp_network.c @@ -0,0 +1,1166 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <limits.h> + +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> + +#include <osmocom/netif/rtp.h> + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> + +#include <osmocom/legacy_mgcp/osmux.h> + +#warning "Make use of the rtp proxy code" + + +#define RTP_SEQ_MOD (1 << 16) +#define RTP_MAX_DROPOUT 3000 +#define RTP_MAX_MISORDER 100 +#define RTP_BUF_SIZE 4096 + +enum { + MGCP_PROTO_RTP, + MGCP_PROTO_RTCP, +}; + +/** + * This does not need to be a precision timestamp and + * is allowed to wrap quite fast. The returned value is + * 1/unit seconds. + */ +static uint32_t get_current_ts(unsigned unit) +{ + struct timespec tp; + uint64_t ret; + + if (!unit) + return 0; + + memset(&tp, 0, sizeof(tp)); + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) + LOGP(DLMGCP, LOGL_NOTICE, + "Getting the clock failed.\n"); + + /* convert it to 1/unit seconds */ + ret = tp.tv_sec; + ret *= unit; + ret += (int64_t)tp.tv_nsec * unit / 1000 / 1000 / 1000; + + return ret; +} + +int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len) +{ + struct sockaddr_in out; + out.sin_family = AF_INET; + out.sin_port = port; + memcpy(&out.sin_addr, addr, sizeof(*addr)); + + return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out)); +} + +int mgcp_send_dummy(struct mgcp_endpoint *endp) +{ + static char buf[] = { MGCP_DUMMY_LOAD }; + int rc; + int was_rtcp = 0; + + rc = mgcp_udp_send(endp->net_end.rtp.fd, &endp->net_end.addr, + endp->net_end.rtp_port, buf, 1); + + if (rc == -1) + goto failed; + + if (endp->tcfg->omit_rtcp) + return rc; + + was_rtcp = 1; + rc = mgcp_udp_send(endp->net_end.rtcp.fd, &endp->net_end.addr, + endp->net_end.rtcp_port, buf, 1); + + if (rc >= 0) + return rc; + +failed: + LOGP(DLMGCP, LOGL_ERROR, + "Failed to send dummy %s packet: %s on: 0x%x to %s:%d\n", + was_rtcp ? "RTCP" : "RTP", + strerror(errno), ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), + was_rtcp ? endp->net_end.rtcp_port : endp->net_end.rtp_port); + + return -1; +} + +static int32_t compute_timestamp_aligment_error(struct mgcp_rtp_stream_state *sstate, + int ptime, uint32_t timestamp) +{ + int32_t timestamp_delta; + + if (ptime == 0) + return 0; + + /* Align according to: T - Tlast = k * Tptime */ + timestamp_delta = timestamp - sstate->last_timestamp; + + return timestamp_delta % ptime; +} + +static int check_rtp_timestamp(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_stream_state *sstate, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + uint16_t seq, uint32_t timestamp, + const char *text, int32_t *tsdelta_out) +{ + int32_t tsdelta; + int32_t timestamp_error; + + /* Not fully intialized, skip */ + if (sstate->last_tsdelta == 0 && timestamp == sstate->last_timestamp) + return 0; + + if (seq == sstate->last_seq) { + if (timestamp != sstate->last_timestamp) { + sstate->err_ts_counter += 1; + LOGP(DLMGCP, LOGL_ERROR, + "The %s timestamp delta is != 0 but the sequence " + "number %d is the same, " + "TS offset: %d, SeqNo offset: %d " + "on 0x%x SSRC: %u timestamp: %u " + "from %s:%d in %d\n", + text, seq, + state->timestamp_offset, state->seq_offset, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + return 0; + } + + tsdelta = + (int32_t)(timestamp - sstate->last_timestamp) / + (int16_t)(seq - sstate->last_seq); + + if (tsdelta == 0) { + /* Don't update *tsdelta_out */ + LOGP(DLMGCP, LOGL_NOTICE, + "The %s timestamp delta is %d " + "on 0x%x SSRC: %u timestamp: %u " + "from %s:%d in %d\n", + text, tsdelta, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + + return 0; + } + + if (sstate->last_tsdelta != tsdelta) { + if (sstate->last_tsdelta) { + LOGP(DLMGCP, LOGL_INFO, + "The %s timestamp delta changes from %d to %d " + "on 0x%x SSRC: %u timestamp: %u from %s:%d in %d\n", + text, sstate->last_tsdelta, tsdelta, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + } + + if (tsdelta_out) + *tsdelta_out = tsdelta; + + timestamp_error = + compute_timestamp_aligment_error(sstate, state->packet_duration, + timestamp); + + if (timestamp_error) { + sstate->err_ts_counter += 1; + LOGP(DLMGCP, LOGL_NOTICE, + "The %s timestamp has an alignment error of %d " + "on 0x%x SSRC: %u " + "SeqNo delta: %d, TS delta: %d, dTS/dSeq: %d " + "from %s:%d in mode %d. ptime: %d\n", + text, timestamp_error, + ENDPOINT_NUMBER(endp), sstate->ssrc, + (int16_t)(seq - sstate->last_seq), + (int32_t)(timestamp - sstate->last_timestamp), + tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode, state->packet_duration); + } + return 1; +} + +/* Set the timestamp offset according to the packet duration. */ +static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + int16_t delta_seq, uint32_t in_timestamp) +{ + int32_t tsdelta = state->packet_duration; + int timestamp_offset; + uint32_t out_timestamp; + + if (tsdelta == 0) { + tsdelta = state->out_stream.last_tsdelta; + if (tsdelta != 0) { + LOGP(DLMGCP, LOGL_NOTICE, + "A fixed packet duration is not available on 0x%x, " + "using last output timestamp delta instead: %d " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } else { + tsdelta = rtp_end->codec.rate * 20 / 1000; + LOGP(DLMGCP, LOGL_NOTICE, + "Fixed packet duration and last timestamp delta " + "are not available on 0x%x, " + "using fixed 20ms instead: %d " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + } + + out_timestamp = state->out_stream.last_timestamp + delta_seq * tsdelta; + timestamp_offset = out_timestamp - in_timestamp; + + if (state->timestamp_offset != timestamp_offset) { + state->timestamp_offset = timestamp_offset; + + LOGP(DLMGCP, LOGL_NOTICE, + "Timestamp offset change on 0x%x SSRC: %u " + "SeqNo delta: %d, TS offset: %d, " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + delta_seq, state->timestamp_offset, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + + return timestamp_offset; +} + +/* Set the timestamp offset according to the packet duration. */ +static int align_rtp_timestamp_offset(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + uint32_t timestamp) +{ + int timestamp_error = 0; + int ptime = state->packet_duration; + + /* Align according to: T + Toffs - Tlast = k * Tptime */ + + timestamp_error = compute_timestamp_aligment_error( + &state->out_stream, ptime, + timestamp + state->timestamp_offset); + + if (timestamp_error) { + state->timestamp_offset += ptime - timestamp_error; + + LOGP(DLMGCP, LOGL_NOTICE, + "Corrected timestamp alignment error of %d on 0x%x SSRC: %u " + "new TS offset: %d, " + "from %s:%d in %d\n", + timestamp_error, + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->timestamp_offset, inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port), endp->conn_mode); + } + + OSMO_ASSERT(compute_timestamp_aligment_error(&state->out_stream, ptime, + timestamp + state->timestamp_offset) == 0); + + return timestamp_error; +} + +int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size) +{ + return 0; +} + +int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end) +{ + return 0; +} + +void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, + int *payload_type, + const char**audio_name, + const char**fmtp_extra) +{ + /* Use the BTS side parameters when passing the SDP data (for + * downlink) to the net peer. + */ + *payload_type = endp->bts_end.codec.payload_type; + *audio_name = endp->bts_end.codec.audio_name; + *fmtp_extra = endp->bts_end.fmtp_extra; +} + + +void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + const uint16_t seq, const int32_t transit, + const uint32_t ssrc) +{ + int32_t d; + + /* initialize or re-initialize */ + if (!state->stats_initialized || state->stats_ssrc != ssrc) { + state->stats_initialized = 1; + state->stats_base_seq = seq; + state->stats_max_seq = seq - 1; + state->stats_ssrc = ssrc; + state->stats_jitter = 0; + state->stats_transit = transit; + state->stats_cycles = 0; + } else { + uint16_t udelta; + + /* + * The below takes the shape of the validation of + * Appendix A. Check if there is something weird with + * the sequence number, otherwise check for a wrap + * around in the sequence number. + * It can't wrap during the initialization so let's + * skip it here. The Appendix A probably doesn't have + * this issue because of the probation. + */ + udelta = seq - state->stats_max_seq; + if (udelta < RTP_MAX_DROPOUT) { + if (seq < state->stats_max_seq) + state->stats_cycles += RTP_SEQ_MOD; + } else if (udelta <= RTP_SEQ_MOD - RTP_MAX_MISORDER) { + LOGP(DLMGCP, LOGL_NOTICE, + "RTP seqno made a very large jump on 0x%x delta: %u\n", + ENDPOINT_NUMBER(endp), udelta); + } + } + + /* + * Calculate the jitter between the two packages. The TS should be + * taken closer to the read function. This was taken from the + * Appendix A of RFC 3550. Timestamp and arrival_time have a 1/rate + * resolution. + */ + d = transit - state->stats_transit; + state->stats_transit = transit; + if (d < 0) + d = -d; + state->stats_jitter += d - ((state->stats_jitter + 8) >> 4); + state->stats_max_seq = seq; +} + + + +/** + * The RFC 3550 Appendix A assumes there are multiple sources but + * some of the supported endpoints (e.g. the nanoBTS) can only handle + * one source and this code will patch RTP header to appear as if there + * is only one source. + * There is also no probation period for new sources. Every RTP header + * we receive will be seen as a switch in streams. + */ +void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, + char *data, int len) +{ + uint32_t arrival_time; + int32_t transit; + uint16_t seq; + uint32_t timestamp, ssrc; + struct rtp_hdr *rtp_hdr; + int payload = rtp_end->codec.payload_type; + + if (len < sizeof(*rtp_hdr)) + return; + + rtp_hdr = (struct rtp_hdr *) data; + seq = ntohs(rtp_hdr->sequence); + timestamp = ntohl(rtp_hdr->timestamp); + arrival_time = get_current_ts(rtp_end->codec.rate); + ssrc = ntohl(rtp_hdr->ssrc); + transit = arrival_time - timestamp; + + mgcp_rtp_annex_count(endp, state, seq, transit, ssrc); + + if (!state->initialized) { + state->initialized = 1; + state->in_stream.last_seq = seq - 1; + state->in_stream.ssrc = state->orig_ssrc = ssrc; + state->in_stream.last_tsdelta = 0; + state->packet_duration = mgcp_rtp_packet_duration(endp, rtp_end); + state->out_stream = state->in_stream; + state->out_stream.last_timestamp = timestamp; + state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */ + LOGP(DLMGCP, LOGL_INFO, + "Initializing stream on 0x%x SSRC: %u timestamp: %u " + "pkt-duration: %d, from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->seq_offset, state->packet_duration, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + if (state->packet_duration == 0) { + state->packet_duration = rtp_end->codec.rate * 20 / 1000; + LOGP(DLMGCP, LOGL_NOTICE, + "Fixed packet duration is not available on 0x%x, " + "using fixed 20ms instead: %d from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->packet_duration, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + } else if (state->in_stream.ssrc != ssrc) { + LOGP(DLMGCP, LOGL_NOTICE, + "The SSRC changed on 0x%x: %u -> %u " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), + state->in_stream.ssrc, rtp_hdr->ssrc, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + + state->in_stream.ssrc = ssrc; + if (rtp_end->force_constant_ssrc) { + int16_t delta_seq; + + /* Always increment seqno by 1 */ + state->seq_offset = + (state->out_stream.last_seq + 1) - seq; + + /* Estimate number of packets that would have been sent */ + delta_seq = + (arrival_time - state->in_stream.last_arrival_time + + state->packet_duration/2) / + state->packet_duration; + + adjust_rtp_timestamp_offset(endp, state, rtp_end, addr, + delta_seq, timestamp); + + state->patch_ssrc = 1; + ssrc = state->orig_ssrc; + if (rtp_end->force_constant_ssrc != -1) + rtp_end->force_constant_ssrc -= 1; + + LOGP(DLMGCP, LOGL_NOTICE, + "SSRC patching enabled on 0x%x SSRC: %u " + "SeqNo offset: %d, TS offset: %d " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->seq_offset, state->timestamp_offset, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + + state->in_stream.last_tsdelta = 0; + } else { + /* Compute current per-packet timestamp delta */ + check_rtp_timestamp(endp, state, &state->in_stream, rtp_end, addr, + seq, timestamp, "input", + &state->in_stream.last_tsdelta); + + if (state->patch_ssrc) + ssrc = state->orig_ssrc; + } + + /* Save before patching */ + state->in_stream.last_timestamp = timestamp; + state->in_stream.last_seq = seq; + state->in_stream.last_arrival_time = arrival_time; + + if (rtp_end->force_aligned_timing && + state->out_stream.ssrc == ssrc && state->packet_duration) + /* Align the timestamp offset */ + align_rtp_timestamp_offset(endp, state, rtp_end, addr, timestamp); + + /* Store the updated SSRC back to the packet */ + if (state->patch_ssrc) + rtp_hdr->ssrc = htonl(ssrc); + + /* Apply the offset and store it back to the packet. + * This won't change anything if the offset is 0, so the conditional is + * omitted. */ + seq += state->seq_offset; + rtp_hdr->sequence = htons(seq); + timestamp += state->timestamp_offset; + rtp_hdr->timestamp = htonl(timestamp); + + /* Check again, whether the timestamps are still valid */ + if (state->out_stream.ssrc == ssrc) + check_rtp_timestamp(endp, state, &state->out_stream, rtp_end, + addr, seq, timestamp, "output", + &state->out_stream.last_tsdelta); + + /* Save output values */ + state->out_stream.last_seq = seq; + state->out_stream.last_timestamp = timestamp; + state->out_stream.ssrc = ssrc; + + if (payload < 0) + return; + +#if 0 + DEBUGP(DLMGCP, "Payload hdr payload %u -> endp payload %u\n", + rtp_hdr->payload_type, payload); + rtp_hdr->payload_type = payload; +#endif +} + +/* + * The below code is for dispatching. We have a dedicated port for + * the data coming from the net and one to discover the BTS. + */ +static int forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf, int len) +{ + if (!tap->enabled) + return 0; + + return sendto(fd, buf, len, 0, + (struct sockaddr *)&tap->forward, sizeof(tap->forward)); +} + +static int mgcp_send_transcoder(struct mgcp_rtp_end *end, + struct mgcp_config *cfg, int is_rtp, + const char *buf, int len) +{ + int rc; + int port; + struct sockaddr_in addr; + + port = is_rtp ? end->rtp_port : end->rtcp_port; + + addr.sin_family = AF_INET; + addr.sin_addr = cfg->transcoder_in; + addr.sin_port = port; + + rc = sendto(is_rtp ? + end->rtp.fd : + end->rtcp.fd, buf, len, 0, + (struct sockaddr *) &addr, sizeof(addr)); + + if (rc != len) + LOGP(DLMGCP, LOGL_ERROR, + "Failed to send data to the transcoder: %s\n", + strerror(errno)); + + return rc; +} + +int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, + struct sockaddr_in *addr, char *buf, int rc) +{ + struct mgcp_trunk_config *tcfg = endp->tcfg; + struct mgcp_rtp_end *rtp_end; + struct mgcp_rtp_state *rtp_state; + int tap_idx; + + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x dest %s tcfg->audio_loop %d endp->conn_mode %d (== loopback: %d)\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + tcfg->audio_loop, + endp->conn_mode, + endp->conn_mode == MGCP_CONN_LOOPBACK); + + /* For loop toggle the destination and then dispatch. */ + if (tcfg->audio_loop) + dest = !dest; + + /* Loop based on the conn_mode, maybe undoing the above */ + if (endp->conn_mode == MGCP_CONN_LOOPBACK) + dest = !dest; + + if (dest == MGCP_DEST_NET) { + rtp_end = &endp->net_end; + rtp_state = &endp->bts_state; + tap_idx = MGCP_TAP_NET_OUT; + } else { + rtp_end = &endp->bts_end; + rtp_state = &endp->net_state; + tap_idx = MGCP_TAP_BTS_OUT; + } + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x dest %s net_end %s %d %d bts_end %s %d %d rtp_end %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + + inet_ntoa(endp->net_end.addr), + ntohs(endp->net_end.rtp_port), + ntohs(endp->net_end.rtcp_port), + + inet_ntoa(endp->bts_end.addr), + ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port), + + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + + if (!rtp_end->output_enabled) { + rtp_end->dropped_packets += 1; + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x output disabled, drop to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + } else if (is_rtp) { + int cont; + int nbytes = 0; + int len = rc; + do { + cont = endp->cfg->rtp_processing_cb(endp, rtp_end, + buf, &len, RTP_BUF_SIZE); + if (cont < 0) + break; + + mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, len); + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x process/send to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + (dest == MGCP_DEST_NET)? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx], + buf, len); + + /* FIXME: HACK HACK HACK. See OS#2459. + * The ip.access nano3G needs the first RTP payload's first two bytes to read hex + * 'e400', or it will reject the RAB assignment. It seems to not harm other femto + * cells (as long as we patch only the first RTP payload in each stream). + */ + if (tap_idx == MGCP_TAP_BTS_OUT + && !rtp_state->patched_first_rtp_payload) { + uint8_t *data = (uint8_t*)&buf[12]; + data[0] = 0xe4; + data[1] = 0x00; + rtp_state->patched_first_rtp_payload = true; + } + + rc = mgcp_udp_send(rtp_end->rtp.fd, + &rtp_end->addr, + rtp_end->rtp_port, buf, len); + + if (rc <= 0) + return rc; + nbytes += rc; + len = cont; + } while (len > 0); + return nbytes; + } else if (!tcfg->omit_rtcp) { + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x send to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + + return mgcp_udp_send(rtp_end->rtcp.fd, + &rtp_end->addr, + rtp_end->rtcp_port, buf, rc); + } + + return 0; +} + +static int receive_from(struct mgcp_endpoint *endp, int fd, struct sockaddr_in *addr, + char *buf, int bufsize) +{ + int rc; + socklen_t slen = sizeof(*addr); + + rc = recvfrom(fd, buf, bufsize, 0, + (struct sockaddr *) addr, &slen); + if (rc < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n", + ENDPOINT_NUMBER(endp), errno, strerror(errno)); + return -1; + } + + /* do not forward aynthing... maybe there is a packet from the bts */ + if (!endp->allocated) + return -1; + + #warning "Slight spec violation. With connection mode recvonly we should attempt to forward." + + return rc; +} + +static int rtp_data_net(struct osmo_fd *fd, unsigned int what) +{ + char buf[RTP_BUF_SIZE]; + struct sockaddr_in addr; + struct mgcp_endpoint *endp; + int rc, proto; + + endp = (struct mgcp_endpoint *) fd->data; + + rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x", + ENDPOINT_NUMBER(endp)); + LOGPC(DLMGCP, LOGL_DEBUG, + " from net %s %d", + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + LOGPC(DLMGCP, LOGL_DEBUG, + " net_end %s %d %d", + inet_ntoa(endp->net_end.addr), + ntohs(endp->net_end.rtp_port), + ntohs(endp->net_end.rtcp_port)); + LOGPC(DLMGCP, LOGL_DEBUG, + " bts_end %s %d %d\n", + inet_ntoa(endp->bts_end.addr), + ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port) + ); + + if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_net: Endpoint 0x%x data from wrong address %s vs. ", + ENDPOINT_NUMBER(endp), inet_ntoa(addr.sin_addr)); + LOGPC(DLMGCP, LOGL_ERROR, + "%s\n", inet_ntoa(endp->net_end.addr)); + return -1; + } + + switch(endp->type) { + case MGCP_RTP_DEFAULT: + case MGCP_RTP_TRANSCODED: + if (endp->net_end.rtp_port != addr.sin_port && + endp->net_end.rtcp_port != addr.sin_port) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_net: Data from wrong source port %d on 0x%x\n", + ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); + return -1; + } + break; + case MGCP_OSMUX_BSC: + case MGCP_OSMUX_BSC_NAT: + break; + } + + LOGP(DLMGCP, LOGL_DEBUG, + "rtp_data_net: Endpoint %x data from %s %d\n", + ENDPOINT_NUMBER(endp), + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { + LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + proto = fd == &endp->net_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; + endp->net_end.packets += 1; + endp->net_end.octets += rc; + + forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc); + + switch (endp->type) { + case MGCP_RTP_DEFAULT: + return mgcp_send(endp, MGCP_DEST_BTS, proto == MGCP_PROTO_RTP, + &addr, buf, rc); + case MGCP_RTP_TRANSCODED: + return mgcp_send_transcoder(&endp->trans_net, endp->cfg, + proto == MGCP_PROTO_RTP, buf, rc); + case MGCP_OSMUX_BSC_NAT: + return osmux_xfrm_to_osmux(MGCP_DEST_BTS, buf, rc, endp); + case MGCP_OSMUX_BSC: /* Should not happen */ + break; + } + + LOGP(DLMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n", + endp->type, ENDPOINT_NUMBER(endp)); + return 0; +} + +static void discover_bts(struct mgcp_endpoint *endp, int proto, struct sockaddr_in *addr) +{ + struct mgcp_config *cfg = endp->cfg; + + if (proto == MGCP_PROTO_RTP && endp->bts_end.rtp_port == 0) { + if (!cfg->bts_ip || + memcmp(&addr->sin_addr, + &cfg->bts_in, sizeof(cfg->bts_in)) == 0 || + memcmp(&addr->sin_addr, + &endp->bts_end.addr, sizeof(endp->bts_end.addr)) == 0) { + + endp->bts_end.rtp_port = addr->sin_port; + endp->bts_end.addr = addr->sin_addr; + + LOGP(DLMGCP, LOGL_NOTICE, + "Found BTS for endpoint: 0x%x on port: %d/%d of %s\n", + ENDPOINT_NUMBER(endp), ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port), inet_ntoa(addr->sin_addr)); + } + } else if (proto == MGCP_PROTO_RTCP && endp->bts_end.rtcp_port == 0) { + if (memcmp(&endp->bts_end.addr, &addr->sin_addr, + sizeof(endp->bts_end.addr)) == 0) { + endp->bts_end.rtcp_port = addr->sin_port; + } + } +} + +static int rtp_data_bts(struct osmo_fd *fd, unsigned int what) +{ + char buf[RTP_BUF_SIZE]; + struct sockaddr_in addr; + struct mgcp_endpoint *endp; + int rc, proto; + + endp = (struct mgcp_endpoint *) fd->data; + + rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + proto = fd == &endp->bts_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; + + /* We have no idea who called us, maybe it is the BTS. */ + /* it was the BTS... */ + discover_bts(endp, proto, &addr); + + if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_bts: Data from wrong bts %s on 0x%x\n", + inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp)); + return -1; + } + + if (endp->bts_end.rtp_port != addr.sin_port && + endp->bts_end.rtcp_port != addr.sin_port) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_bts: ata from wrong bts source port %d on 0x%x\n", + ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); + return -1; + } + + LOGP(DLMGCP, LOGL_DEBUG, + "rtp_data_bts: Endpoint %x data from %s %d\n", + ENDPOINT_NUMBER(endp), + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { + LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + /* do this before the loop handling */ + endp->bts_end.packets += 1; + endp->bts_end.octets += rc; + + forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc); + + switch (endp->type) { + case MGCP_RTP_DEFAULT: + LOGP(DLMGCP, LOGL_DEBUG, + "rtp_data_bts: Endpoint %x MGCP_RTP_DEFAULT\n", + ENDPOINT_NUMBER(endp)); + return mgcp_send(endp, MGCP_DEST_NET, proto == MGCP_PROTO_RTP, + &addr, buf, rc); + case MGCP_RTP_TRANSCODED: + return mgcp_send_transcoder(&endp->trans_bts, endp->cfg, + proto == MGCP_PROTO_RTP, buf, rc); + case MGCP_OSMUX_BSC: + /* OSMUX translation: BTS -> BSC */ + return osmux_xfrm_to_osmux(MGCP_DEST_NET, buf, rc, endp); + case MGCP_OSMUX_BSC_NAT: + break; /* Should not happen */ + } + + LOGP(DLMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n", + endp->type, ENDPOINT_NUMBER(endp)); + return 0; +} + +static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp, + int dest, struct osmo_fd *fd) +{ + char buf[RTP_BUF_SIZE]; + struct sockaddr_in addr; + struct mgcp_config *cfg; + int rc, proto; + + cfg = _endp->cfg; + rc = receive_from(_endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + proto = fd == &end->rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; + + if (memcmp(&addr.sin_addr, &cfg->transcoder_in, sizeof(addr.sin_addr)) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Data not coming from transcoder dest: %d %s on 0x%x\n", + dest, inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(_endp)); + return -1; + } + + if (end->rtp_port != addr.sin_port && + end->rtcp_port != addr.sin_port) { + LOGP(DLMGCP, LOGL_ERROR, + "Data from wrong transcoder dest %d source port %d on 0x%x\n", + dest, ntohs(addr.sin_port), ENDPOINT_NUMBER(_endp)); + return -1; + } + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { + LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from transcoder dest %d on 0x%x\n", + dest, ENDPOINT_NUMBER(_endp)); + return 0; + } + + end->packets += 1; + return mgcp_send(_endp, dest, proto == MGCP_PROTO_RTP, &addr, buf, rc); +} + +static int rtp_data_trans_net(struct osmo_fd *fd, unsigned int what) +{ + struct mgcp_endpoint *endp; + endp = (struct mgcp_endpoint *) fd->data; + + return rtp_data_transcoder(&endp->trans_net, endp, MGCP_DEST_NET, fd); +} + +static int rtp_data_trans_bts(struct osmo_fd *fd, unsigned int what) +{ + struct mgcp_endpoint *endp; + endp = (struct mgcp_endpoint *) fd->data; + + return rtp_data_transcoder(&endp->trans_bts, endp, MGCP_DEST_BTS, fd); +} + +int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port) +{ + struct sockaddr_in addr; + int on = 1; + + fd->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd->fd < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to create UDP port.\n"); + return -1; + } + + setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_aton(source_addr, &addr.sin_addr); + + if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd->fd); + fd->fd = -1; + return -1; + } + + return 0; +} + +int mgcp_set_ip_tos(int fd, int tos) +{ + int ret; + ret = setsockopt(fd, IPPROTO_IP, IP_TOS, + &tos, sizeof(tos)); + return ret != 0; +} + +static int bind_rtp(struct mgcp_config *cfg, const char *source_addr, + struct mgcp_rtp_end *rtp_end, int endpno) +{ + if (mgcp_create_bind(source_addr, &rtp_end->rtp, + rtp_end->local_port) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to create RTP port: %s:%d on 0x%x\n", + source_addr, rtp_end->local_port, endpno); + goto cleanup0; + } + + if (mgcp_create_bind(source_addr, &rtp_end->rtcp, + rtp_end->local_port + 1) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to create RTCP port: %s:%d on 0x%x\n", + source_addr, rtp_end->local_port + 1, endpno); + goto cleanup1; + } + + mgcp_set_ip_tos(rtp_end->rtp.fd, cfg->endp_dscp); + mgcp_set_ip_tos(rtp_end->rtcp.fd, cfg->endp_dscp); + + rtp_end->rtp.when = BSC_FD_READ; + if (osmo_fd_register(&rtp_end->rtp) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to register RTP port %d on 0x%x\n", + rtp_end->local_port, endpno); + goto cleanup2; + } + + rtp_end->rtcp.when = BSC_FD_READ; + if (osmo_fd_register(&rtp_end->rtcp) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to register RTCP port %d on 0x%x\n", + rtp_end->local_port + 1, endpno); + goto cleanup3; + } + + return 0; + +cleanup3: + osmo_fd_unregister(&rtp_end->rtp); +cleanup2: + close(rtp_end->rtcp.fd); + rtp_end->rtcp.fd = -1; +cleanup1: + close(rtp_end->rtp.fd); + rtp_end->rtp.fd = -1; +cleanup0: + return -1; +} + +static int int_bind(const char *port, + struct mgcp_rtp_end *end, int (*cb)(struct osmo_fd *, unsigned), + struct mgcp_endpoint *_endp, + const char *source_addr, int rtp_port) +{ + if (end->rtp.fd != -1 || end->rtcp.fd != -1) { + LOGP(DLMGCP, LOGL_ERROR, "Previous %s was still bound on %d\n", + port, ENDPOINT_NUMBER(_endp)); + mgcp_free_rtp_port(end); + } + + end->local_port = rtp_port; + end->rtp.cb = cb; + end->rtp.data = _endp; + end->rtcp.data = _endp; + end->rtcp.cb = cb; + return bind_rtp(_endp->cfg, source_addr, end, ENDPOINT_NUMBER(_endp)); +} + +int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("bts-port", &endp->bts_end, + rtp_data_bts, endp, + mgcp_bts_src_addr(endp), rtp_port); +} + +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("net-port", &endp->net_end, + rtp_data_net, endp, + mgcp_net_src_addr(endp), rtp_port); +} + +int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("trans-net", &endp->trans_net, + rtp_data_trans_net, endp, + endp->cfg->source_addr, rtp_port); +} + +int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("trans-bts", &endp->trans_bts, + rtp_data_trans_bts, endp, + endp->cfg->source_addr, rtp_port); +} + +int mgcp_free_rtp_port(struct mgcp_rtp_end *end) +{ + if (end->rtp.fd != -1) { + close(end->rtp.fd); + end->rtp.fd = -1; + osmo_fd_unregister(&end->rtp); + } + + if (end->rtcp.fd != -1) { + close(end->rtcp.fd); + end->rtcp.fd = -1; + osmo_fd_unregister(&end->rtcp); + } + + return 0; +} + + +void mgcp_state_calc_loss(struct mgcp_rtp_state *state, + struct mgcp_rtp_end *end, uint32_t *expected, + int *loss) +{ + *expected = state->stats_cycles + state->stats_max_seq; + *expected = *expected - state->stats_base_seq + 1; + + if (!state->stats_initialized) { + *expected = 0; + *loss = 0; + return; + } + + /* + * Make sure the sign is correct and use the biggest + * positive/negative number that fits. + */ + *loss = *expected - end->packets; + if (*expected < end->packets) { + if (*loss > 0) + *loss = INT_MIN; + } else { + if (*loss < 0) + *loss = INT_MAX; + } +} + +uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *state) +{ + if (!state->stats_initialized) + return 0; + return state->stats_jitter >> 4; +} diff --git a/src/libosmo-legacy-mgcp/mgcp_osmux.c b/src/libosmo-legacy-mgcp/mgcp_osmux.c new file mode 100644 index 0000000..743d3f9 --- /dev/null +++ b/src/libosmo-legacy-mgcp/mgcp_osmux.c @@ -0,0 +1,583 @@ +/* + * (C) 2012-2013 by Pablo Neira Ayuso <pablo@gnumonks.org> + * (C) 2012-2013 by On Waves ehf <http://www.on-waves.com> + * All rights not specifically granted under this license are reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + */ + +#include <stdio.h> /* for printf */ +#include <string.h> /* for memcpy */ +#include <stdlib.h> /* for abs */ +#include <inttypes.h> /* for PRIu64 */ +#include <netinet/in.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/netif/osmux.h> +#include <osmocom/netif/rtp.h> + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> +#include <osmocom/legacy_mgcp/osmux.h> + +static struct osmo_fd osmux_fd; + +static LLIST_HEAD(osmux_handle_list); + +struct osmux_handle { + struct llist_head head; + struct osmux_in_handle *in; + struct in_addr rem_addr; + int rem_port; + int refcnt; +}; + +static void *osmux; + +static void osmux_deliver(struct msgb *batch_msg, void *data) +{ + struct osmux_handle *handle = data; + struct sockaddr_in out = { + .sin_family = AF_INET, + .sin_port = handle->rem_port, + }; + + memcpy(&out.sin_addr, &handle->rem_addr, sizeof(handle->rem_addr)); + sendto(osmux_fd.fd, batch_msg->data, batch_msg->len, 0, + (struct sockaddr *)&out, sizeof(out)); + msgb_free(batch_msg); +} + +static struct osmux_handle * +osmux_handle_find_get(struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + /* Lookup for existing OSMUX handle for this destination address. */ + llist_for_each_entry(h, &osmux_handle_list, head) { + if (memcmp(&h->rem_addr, addr, sizeof(struct in_addr)) == 0 && + h->rem_port == rem_port) { + LOGP(DLMGCP, LOGL_DEBUG, "using existing OSMUX handle " + "for addr=%s:%d\n", + inet_ntoa(*addr), ntohs(rem_port)); + h->refcnt++; + return h; + } + } + + return NULL; +} + +static void osmux_handle_put(struct osmux_in_handle *in) +{ + struct osmux_handle *h; + + /* Lookup for existing OSMUX handle for this destination address. */ + llist_for_each_entry(h, &osmux_handle_list, head) { + if (h->in == in) { + if (--h->refcnt == 0) { + LOGP(DLMGCP, LOGL_INFO, + "Releasing unused osmux handle for %s:%d\n", + inet_ntoa(h->rem_addr), + ntohs(h->rem_port)); + LOGP(DLMGCP, LOGL_INFO, "Stats: " + "input RTP msgs: %u bytes: %"PRIu64" " + "output osmux msgs: %u bytes: %"PRIu64"\n", + in->stats.input_rtp_msgs, + in->stats.input_rtp_bytes, + in->stats.output_osmux_msgs, + in->stats.output_osmux_bytes); + llist_del(&h->head); + osmux_xfrm_input_fini(h->in); + talloc_free(h); + } + return; + } + } + LOGP(DLMGCP, LOGL_ERROR, "cannot find Osmux input handle %p\n", in); +} + +static struct osmux_handle * +osmux_handle_alloc(struct mgcp_config *cfg, struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + h = talloc_zero(osmux, struct osmux_handle); + if (!h) + return NULL; + h->rem_addr = *addr; + h->rem_port = rem_port; + h->refcnt++; + + h->in = talloc_zero(h, struct osmux_in_handle); + if (!h->in) { + talloc_free(h); + return NULL; + } + + h->in->osmux_seq = 0; /* sequence number to start OSmux message from */ + h->in->batch_factor = cfg->osmux_batch; + /* If batch size is zero, the library defaults to 1470 bytes. */ + h->in->batch_size = cfg->osmux_batch_size; + h->in->deliver = osmux_deliver; + osmux_xfrm_input_init(h->in); + h->in->data = h; + + llist_add(&h->head, &osmux_handle_list); + + LOGP(DLMGCP, LOGL_DEBUG, "created new OSMUX handle for addr=%s:%d\n", + inet_ntoa(*addr), ntohs(rem_port)); + + return h; +} + +static struct osmux_in_handle * +osmux_handle_lookup(struct mgcp_config *cfg, struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + h = osmux_handle_find_get(addr, rem_port); + if (h != NULL) + return h->in; + + h = osmux_handle_alloc(cfg, addr, rem_port); + if (h == NULL) + return NULL; + + return h->in; +} + +int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp) +{ + int ret; + struct msgb *msg; + + msg = msgb_alloc(4096, "RTP"); + if (!msg) + return 0; + + memcpy(msg->data, buf, rc); + msgb_put(msg, rc); + + while ((ret = osmux_xfrm_input(endp->osmux.in, msg, endp->osmux.cid)) > 0) { + /* batch full, build and deliver it */ + osmux_xfrm_input_deliver(endp->osmux.in); + } + return 0; +} + +static struct mgcp_endpoint * +endpoint_lookup(struct mgcp_config *cfg, int cid, + struct in_addr *from_addr, int type) +{ + struct mgcp_endpoint *tmp = NULL; + int i; + + /* Lookup for the endpoint that corresponds to this port */ + for (i=0; i<cfg->trunk.number_endpoints; i++) { + struct in_addr *this; + + tmp = &cfg->trunk.endpoints[i]; + + if (!tmp->allocated) + continue; + + switch(type) { + case MGCP_DEST_NET: + this = &tmp->net_end.addr; + break; + case MGCP_DEST_BTS: + this = &tmp->bts_end.addr; + break; + default: + /* Should not ever happen */ + LOGP(DLMGCP, LOGL_ERROR, "Bad type %d. Fix your code.\n", type); + return NULL; + } + + if (tmp->osmux.cid == cid && this->s_addr == from_addr->s_addr) + return tmp; + } + + LOGP(DLMGCP, LOGL_ERROR, "Cannot find endpoint with cid=%d\n", cid); + + return NULL; +} + +static void scheduled_tx_net_cb(struct msgb *msg, void *data) +{ + struct mgcp_endpoint *endp = data; + struct sockaddr_in addr = { + .sin_addr = endp->net_end.addr, + .sin_port = endp->net_end.rtp_port, + }; + + endp->bts_end.octets += msg->len; + endp->bts_end.packets++; + + mgcp_send(endp, MGCP_DEST_NET, 1, &addr, (char *)msg->data, msg->len); + msgb_free(msg); +} + +static void scheduled_tx_bts_cb(struct msgb *msg, void *data) +{ + struct mgcp_endpoint *endp = data; + struct sockaddr_in addr = { + .sin_addr = endp->bts_end.addr, + .sin_port = endp->bts_end.rtp_port, + }; + + endp->net_end.octets += msg->len; + endp->net_end.packets++; + + mgcp_send(endp, MGCP_DEST_BTS, 1, &addr, (char *)msg->data, msg->len); + msgb_free(msg); +} + +static struct msgb *osmux_recv(struct osmo_fd *ofd, struct sockaddr_in *addr) +{ + struct msgb *msg; + socklen_t slen = sizeof(*addr); + int ret; + + msg = msgb_alloc(4096, "OSMUX"); + if (!msg) { + LOGP(DLMGCP, LOGL_ERROR, "cannot allocate message\n"); + return NULL; + } + ret = recvfrom(ofd->fd, msg->data, msg->data_len, 0, + (struct sockaddr *)addr, &slen); + if (ret <= 0) { + msgb_free(msg); + LOGP(DLMGCP, LOGL_ERROR, "cannot receive message\n"); + return NULL; + } + msgb_put(msg, ret); + + return msg; +} + +#define osmux_chunk_length(msg, rem) (rem - msg->len); + +int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct msgb *msg; + struct osmux_hdr *osmuxh; + struct llist_head list; + struct sockaddr_in addr; + struct mgcp_config *cfg = ofd->data; + uint32_t rem; + + msg = osmux_recv(ofd, &addr); + if (!msg) + return -1; + + /* not any further processing dummy messages */ + if (msg->data[0] == MGCP_DUMMY_LOAD) + goto out; + + rem = msg->len; + while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct mgcp_endpoint *endp; + + /* Yes, we use MGCP_DEST_NET to locate the origin */ + endp = endpoint_lookup(cfg, osmuxh->circuit_id, + &addr.sin_addr, MGCP_DEST_NET); + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find an endpoint for circuit_id=%d\n", + osmuxh->circuit_id); + goto out; + } + endp->osmux.stats.octets += osmux_chunk_length(msg, rem); + endp->osmux.stats.chunks++; + rem = msg->len; + + osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); + osmux_tx_sched(&list, scheduled_tx_bts_cb, endp); + } +out: + msgb_free(msg); + return 0; +} + +/* This is called from the bsc-nat */ +static int osmux_handle_dummy(struct mgcp_config *cfg, struct sockaddr_in *addr, + struct msgb *msg) +{ + struct mgcp_endpoint *endp; + uint8_t osmux_cid; + + if (msg->len < 1 + sizeof(osmux_cid)) { + LOGP(DLMGCP, LOGL_ERROR, + "Discarding truncated Osmux dummy load\n"); + goto out; + } + + LOGP(DLMGCP, LOGL_DEBUG, "Received Osmux dummy load from %s\n", + inet_ntoa(addr->sin_addr)); + + if (!cfg->osmux) { + LOGP(DLMGCP, LOGL_ERROR, + "bsc wants to use Osmux but bsc-nat did not request it\n"); + goto out; + } + + /* extract the osmux CID from the dummy message */ + memcpy(&osmux_cid, &msg->data[1], sizeof(osmux_cid)); + + endp = endpoint_lookup(cfg, osmux_cid, &addr->sin_addr, MGCP_DEST_BTS); + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find endpoint for Osmux CID %d\n", osmux_cid); + goto out; + } + + if (endp->osmux.state == OSMUX_STATE_ENABLED) + goto out; + + if (osmux_enable_endpoint(endp, &addr->sin_addr, addr->sin_port) < 0 ) { + LOGP(DLMGCP, LOGL_ERROR, + "Could not enable osmux in endpoint %d\n", + ENDPOINT_NUMBER(endp)); + goto out; + } + + LOGP(DLMGCP, LOGL_INFO, "Enabling osmux in endpoint %d for %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); +out: + msgb_free(msg); + return 0; +} + +int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct msgb *msg; + struct osmux_hdr *osmuxh; + struct llist_head list; + struct sockaddr_in addr; + struct mgcp_config *cfg = ofd->data; + uint32_t rem; + + msg = osmux_recv(ofd, &addr); + if (!msg) + return -1; + + /* not any further processing dummy messages */ + if (msg->data[0] == MGCP_DUMMY_LOAD) + return osmux_handle_dummy(cfg, &addr, msg); + + rem = msg->len; + while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct mgcp_endpoint *endp; + + /* Yes, we use MGCP_DEST_BTS to locate the origin */ + endp = endpoint_lookup(cfg, osmuxh->circuit_id, + &addr.sin_addr, MGCP_DEST_BTS); + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find an endpoint for circuit_id=%d\n", + osmuxh->circuit_id); + goto out; + } + endp->osmux.stats.octets += osmux_chunk_length(msg, rem); + endp->osmux.stats.chunks++; + rem = msg->len; + + osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); + osmux_tx_sched(&list, scheduled_tx_net_cb, endp); + } +out: + msgb_free(msg); + return 0; +} + +int osmux_init(int role, struct mgcp_config *cfg) +{ + int ret; + + switch(role) { + case OSMUX_ROLE_BSC: + osmux_fd.cb = osmux_read_from_bsc_nat_cb; + break; + case OSMUX_ROLE_BSC_NAT: + osmux_fd.cb = osmux_read_from_bsc_cb; + break; + default: + LOGP(DLMGCP, LOGL_ERROR, "wrong role for OSMUX\n"); + return -1; + } + osmux_fd.data = cfg; + + ret = mgcp_create_bind(cfg->osmux_addr, &osmux_fd, cfg->osmux_port); + if (ret < 0) { + LOGP(DLMGCP, LOGL_ERROR, "cannot bind OSMUX socket\n"); + return ret; + } + mgcp_set_ip_tos(osmux_fd.fd, cfg->endp_dscp); + osmux_fd.when |= BSC_FD_READ; + + ret = osmo_fd_register(&osmux_fd); + if (ret < 0) { + LOGP(DLMGCP, LOGL_ERROR, "cannot register OSMUX socket\n"); + return ret; + } + cfg->osmux_init = 1; + + return 0; +} + +int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint16_t port) +{ + /* If osmux is enabled, initialize the output handler. This handler is + * used to reconstruct the RTP flow from osmux. The RTP SSRC is + * allocated based on the circuit ID (endp->osmux.cid), which is unique + * in the local scope to the BSC/BSC-NAT. We use it to divide the RTP + * SSRC space (2^32) by the 256 possible circuit IDs, then randomly + * select one value from that window. Thus, we have no chance to have + * overlapping RTP SSRC traveling to the BTSes behind the BSC, + * similarly, for flows traveling to the MSC. + */ + static const uint32_t rtp_ssrc_winlen = UINT32_MAX / 256; + + if (endp->osmux.state == OSMUX_STATE_DISABLED) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint %u didn't request Osmux\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + osmux_xfrm_output_init(&endp->osmux.out, + (endp->osmux.cid * rtp_ssrc_winlen) + + (random() % rtp_ssrc_winlen)); + + endp->osmux.in = osmux_handle_lookup(endp->cfg, addr, port); + if (!endp->osmux.in) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot allocate input osmux handle\n"); + return -1; + } + if (!osmux_xfrm_input_open_circuit(endp->osmux.in, endp->osmux.cid, + endp->cfg->osmux_dummy)) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot open osmux circuit %u\n", + endp->osmux.cid); + return -1; + } + + switch (endp->cfg->role) { + case MGCP_BSC_NAT: + endp->type = MGCP_OSMUX_BSC_NAT; + break; + case MGCP_BSC: + endp->type = MGCP_OSMUX_BSC; + break; + } + endp->osmux.state = OSMUX_STATE_ENABLED; + + return 0; +} + +void osmux_disable_endpoint(struct mgcp_endpoint *endp) +{ + LOGP(DLMGCP, LOGL_INFO, "Releasing endpoint %u using Osmux CID %u\n", + ENDPOINT_NUMBER(endp), endp->osmux.cid); + osmux_xfrm_input_close_circuit(endp->osmux.in, endp->osmux.cid); + endp->osmux.state = OSMUX_STATE_DISABLED; + endp->osmux.cid = -1; + osmux_handle_put(endp->osmux.in); +} + +void osmux_release_cid(struct mgcp_endpoint *endp) +{ + if (endp->osmux.allocated_cid >= 0) + osmux_put_cid(endp->osmux.allocated_cid); + endp->osmux.allocated_cid = -1; +} + +void osmux_allocate_cid(struct mgcp_endpoint *endp) +{ + osmux_release_cid(endp); + endp->osmux.allocated_cid = osmux_get_cid(); +} + +/* We don't need to send the dummy load for osmux so often as another endpoint + * may have already punched the hole in the firewall. This approach is simple + * though. + */ +int osmux_send_dummy(struct mgcp_endpoint *endp) +{ + char buf[1 + sizeof(uint8_t)]; + struct in_addr addr_unset = {}; + + buf[0] = MGCP_DUMMY_LOAD; + memcpy(&buf[1], &endp->osmux.cid, sizeof(endp->osmux.cid)); + + /* Wait until we have the connection information from MDCX */ + if (memcmp(&endp->net_end.addr, &addr_unset, sizeof(addr_unset)) == 0) + return 0; + + if (endp->osmux.state == OSMUX_STATE_ACTIVATING) { + if (osmux_enable_endpoint(endp, &endp->net_end.addr, + htons(endp->cfg->osmux_port)) < 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Could not activate osmux in endpoint %d\n", + ENDPOINT_NUMBER(endp)); + } + LOGP(DLMGCP, LOGL_ERROR, + "Osmux CID %u for %s:%u is now enabled\n", + endp->osmux.cid, inet_ntoa(endp->net_end.addr), + endp->cfg->osmux_port); + } + LOGP(DLMGCP, LOGL_DEBUG, + "sending OSMUX dummy load to %s CID %u\n", + inet_ntoa(endp->net_end.addr), endp->osmux.cid); + + return mgcp_udp_send(osmux_fd.fd, &endp->net_end.addr, + htons(endp->cfg->osmux_port), buf, sizeof(buf)); +} + +/* bsc-nat allocates/releases the Osmux circuit ID */ +static uint8_t osmux_cid_bitmap[(OSMUX_CID_MAX + 1) / 8]; + +int osmux_used_cid(void) +{ + int i, j, used = 0; + + for (i = 0; i < sizeof(osmux_cid_bitmap); i++) { + for (j = 0; j < 8; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + used += 1; + } + } + + return used; +} + +int osmux_get_cid(void) +{ + int i, j; + + for (i = 0; i < sizeof(osmux_cid_bitmap); i++) { + for (j = 0; j < 8; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + continue; + + osmux_cid_bitmap[i] |= (1 << j); + LOGP(DLMGCP, LOGL_DEBUG, + "Allocating Osmux CID %u from pool\n", (i * 8) + j); + return (i * 8) + j; + } + } + + LOGP(DLMGCP, LOGL_ERROR, "All Osmux circuits are in use!\n"); + return -1; +} + +void osmux_put_cid(uint8_t osmux_cid) +{ + LOGP(DLMGCP, LOGL_DEBUG, "Osmux CID %u is back to the pool\n", osmux_cid); + osmux_cid_bitmap[osmux_cid / 8] &= ~(1 << (osmux_cid % 8)); +} diff --git a/src/libosmo-legacy-mgcp/mgcp_protocol.c b/src/libosmo-legacy-mgcp/mgcp_protocol.c new file mode 100644 index 0000000..bc1ec0d --- /dev/null +++ b/src/libosmo-legacy-mgcp/mgcp_protocol.c @@ -0,0 +1,1640 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> + +#define for_each_non_empty_line(line, save) \ + for (line = strtok_r(NULL, "\r\n", &save); line;\ + line = strtok_r(NULL, "\r\n", &save)) + + +static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end); + +struct mgcp_request { + char *name; + struct msgb *(*handle_request) (struct mgcp_parse_data *data); + char *debug_name; +}; + +#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \ + { .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME }, + +static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *data); +static struct msgb *handle_create_con(struct mgcp_parse_data *data); +static struct msgb *handle_delete_con(struct mgcp_parse_data *data); +static struct msgb *handle_modify_con(struct mgcp_parse_data *data); +static struct msgb *handle_rsip(struct mgcp_parse_data *data); +static struct msgb *handle_noti_req(struct mgcp_parse_data *data); + +static void create_transcoder(struct mgcp_endpoint *endp); +static void delete_transcoder(struct mgcp_endpoint *endp); + +static int setup_rtp_processing(struct mgcp_endpoint *endp); + +static int mgcp_analyze_header(struct mgcp_parse_data *parse, char *data); + +/* Display an mgcp message on the log output */ +void display_mgcp_message(unsigned char *message, unsigned int len, + char *preamble) +{ + unsigned char line[80]; + unsigned char *ptr; + unsigned int consumed = 0; + unsigned int consumed_line = 0; + unsigned int line_count = 0; + + if (!log_check_level(DLMGCP, LOGL_DEBUG)) + return; + + while (1) { + memset(line, 0, sizeof(line)); + ptr = line; + consumed_line = 0; + do { + if (*message != '\n' && *message != '\r') { + *ptr = *message; + ptr++; + } + message++; + consumed++; + consumed_line++; + } while (*message != '\n' && consumed < len + && consumed_line < sizeof(line)); + + if (strlen((const char *)line)) { + LOGP(DLMGCP, LOGL_DEBUG, "%s: line #%02u: %s\n", + preamble, line_count, line); + line_count++; + } + + if (consumed >= len) + return; + } +} + +static int mgcp_check_param(const struct mgcp_endpoint *endp, const char *line) +{ + const size_t line_len = strlen(line); + if (line[0] != '\0' && line_len < 2) { + LOGP(DLMGCP, LOGL_ERROR, + "Wrong MGCP option format: '%s' on 0x%x\n", + line, ENDPOINT_NUMBER(endp)); + return 0; + } + + return 1; +} + +static uint32_t generate_call_id(struct mgcp_config *cfg) +{ + int i; + + /* use the call id */ + ++cfg->last_call_id; + + /* handle wrap around */ + if (cfg->last_call_id == CI_UNUSED) + ++cfg->last_call_id; + + /* callstack can only be of size number_of_endpoints */ + /* verify that the call id is free, e.g. in case of overrun */ + for (i = 1; i < cfg->trunk.number_endpoints; ++i) + if (cfg->trunk.endpoints[i].ci == cfg->last_call_id) + return generate_call_id(cfg); + + return cfg->last_call_id; +} + +/* + * array of function pointers for handling various + * messages. In the future this might be binary sorted + * for performance reasons. + */ +static const struct mgcp_request mgcp_requests [] = { + MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint") + MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection") + MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection") + MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection") + MGCP_REQUEST("RQNT", handle_noti_req, "NotificationRequest") + + /* SPEC extension */ + MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress") +}; + +static struct msgb *mgcp_msgb_alloc(void) +{ + struct msgb *msg; + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + if (!msg) + LOGP(DLMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n"); + + return msg; +} + +static struct msgb *do_retransmission(const struct mgcp_endpoint *endp) +{ + struct msgb *msg = mgcp_msgb_alloc(); + if (!msg) + return NULL; + + msg->l2h = msgb_put(msg, strlen(endp->last_response)); + memcpy(msg->l2h, endp->last_response, msgb_l2len(msg)); + display_mgcp_message(msg->l2h, msgb_l2len(msg), "Retransmitted response"); + return msg; +} + +static struct msgb *create_resp(struct mgcp_endpoint *endp, int code, + const char *txt, const char *msg, + const char *trans, const char *param, + const char *sdp) +{ + int len; + struct msgb *res; + + res = mgcp_msgb_alloc(); + if (!res) + return NULL; + + len = snprintf((char *) res->data, 2048, "%d %s%s%s\r\n%s", + code, trans, txt, param ? param : "", sdp ? sdp : ""); + if (len < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to sprintf MGCP response.\n"); + msgb_free(res); + return NULL; + } + + res->l2h = msgb_put(res, len); + LOGP(DLMGCP, LOGL_DEBUG, "Generated response: code=%d\n", code); + display_mgcp_message(res->l2h, msgb_l2len(res), "Generated response"); + + /* + * Remember the last transmission per endpoint. + */ + if (endp) { + struct mgcp_trunk_config *tcfg = endp->tcfg; + talloc_free(endp->last_response); + talloc_free(endp->last_trans); + endp->last_trans = talloc_strdup(tcfg->endpoints, trans); + endp->last_response = talloc_strndup(tcfg->endpoints, + (const char *) res->l2h, + msgb_l2len(res)); + } + + return res; +} + +static struct msgb *create_ok_resp_with_param(struct mgcp_endpoint *endp, + int code, const char *msg, + const char *trans, const char *param) +{ + return create_resp(endp, code, " OK", msg, trans, param, NULL); +} + +static struct msgb *create_ok_response(struct mgcp_endpoint *endp, + int code, const char *msg, const char *trans) +{ + return create_ok_resp_with_param(endp, code, msg, trans, NULL); +} + +static struct msgb *create_err_response(struct mgcp_endpoint *endp, + int code, const char *msg, const char *trans) +{ + return create_resp(endp, code, " FAIL", msg, trans, NULL, NULL); +} + +static int write_response_sdp(struct mgcp_endpoint *endp, + char *sdp_record, size_t size, const char *addr) +{ + const char *fmtp_extra; + const char *audio_name; + int payload_type; + int len; + int nchars; + + endp->cfg->get_net_downlink_format_cb(endp, &payload_type, + &audio_name, &fmtp_extra); + + len = snprintf(sdp_record, size, + "v=0\r\n" + "o=- %u 23 IN IP4 %s\r\n" + "s=-\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n", + endp->ci, addr, addr); + + if (len < 0 || len >= size) + goto buffer_too_small; + + if (payload_type >= 0) { + nchars = snprintf(sdp_record + len, size - len, + "m=audio %d RTP/AVP %d\r\n", + endp->net_end.local_port, payload_type); + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + + if (audio_name && endp->tcfg->audio_send_name) { + nchars = snprintf(sdp_record + len, size - len, + "a=rtpmap:%d %s\r\n", + payload_type, audio_name); + + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + } + + if (fmtp_extra) { + nchars = snprintf(sdp_record + len, size - len, + "%s\r\n", fmtp_extra); + + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + } + } + if (endp->bts_end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) { + nchars = snprintf(sdp_record + len, size - len, + "a=ptime:%d\r\n", + endp->bts_end.packet_duration_ms); + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + } + + return len; + +buffer_too_small: + LOGP(DLMGCP, LOGL_ERROR, "SDP buffer too small: %zu (needed %d)\n", + size, len); + return -1; +} + +static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp, + const char *msg, const char *trans_id) +{ + const char *addr = endp->cfg->local_ip; + char sdp_record[4096]; + int len; + int nchars; + char osmux_extension[strlen("\nX-Osmux: 255") + 1]; + + if (!addr) + addr = mgcp_net_src_addr(endp); + + if (endp->osmux.state == OSMUX_STATE_NEGOTIATING) { + sprintf(osmux_extension, "\nX-Osmux: %u", endp->osmux.cid); + endp->osmux.state = OSMUX_STATE_ACTIVATING; + } else { + osmux_extension[0] = '\0'; + } + + len = snprintf(sdp_record, sizeof(sdp_record), + "I: %u%s\n\n", endp->ci, osmux_extension); + if (len < 0) + return NULL; + + nchars = write_response_sdp(endp, sdp_record + len, + sizeof(sdp_record) - len - 1, addr); + if (nchars < 0) + return NULL; + + len += nchars; + + sdp_record[sizeof(sdp_record) - 1] = '\0'; + + return create_resp(endp, 200, " OK", msg, trans_id, NULL, sdp_record); +} + +static void send_dummy(struct mgcp_endpoint *endp) +{ + if (endp->osmux.state != OSMUX_STATE_DISABLED) + osmux_send_dummy(endp); + else + mgcp_send_dummy(endp); +} + +/* + * handle incoming messages: + * - this can be a command (four letters, space, transaction id) + * - or a response (three numbers, space, transaction id) + */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_parse_data pdata; + int i, code, handled = 0; + struct msgb *resp = NULL; + char *data; + + if (msgb_l2len(msg) < 4) { + LOGP(DLMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len); + return NULL; + } + + if (mgcp_msg_terminate_nul(msg)) + return NULL; + + display_mgcp_message(msg->l2h, msgb_l2len(msg), "Received message"); + + /* attempt to treat it as a response */ + if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) { + LOGP(DLMGCP, LOGL_DEBUG, "Response: Code: %d\n", code); + return NULL; + } + + msg->l3h = &msg->l2h[4]; + + + /* + * Check for a duplicate message and respond. + */ + memset(&pdata, 0, sizeof(pdata)); + pdata.cfg = cfg; + data = strline_r((char *) msg->l3h, &pdata.save); + pdata.found = mgcp_analyze_header(&pdata, data); + if (pdata.endp && pdata.trans + && pdata.endp->last_trans + && strcmp(pdata.endp->last_trans, pdata.trans) == 0) { + return do_retransmission(pdata.endp); + } + + for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i) { + if (strncmp(mgcp_requests[i].name, (const char *) &msg->l2h[0], 4) == 0) { + handled = 1; + resp = mgcp_requests[i].handle_request(&pdata); + break; + } + } + + if (!handled) + LOGP(DLMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n", &msg->l2h[0]); + + return resp; +} + +/** + * We have a null terminated string with the endpoint name here. We only + * support two kinds. Simple ones as seen on the BSC level and the ones + * seen on the trunk side. + */ +static struct mgcp_endpoint *find_e1_endpoint(struct mgcp_config *cfg, + const char *mgcp) +{ + char *rest = NULL; + struct mgcp_trunk_config *tcfg; + int trunk, endp; + + trunk = strtoul(mgcp + 6, &rest, 10); + if (rest == NULL || rest[0] != '/' || trunk < 1) { + LOGP(DLMGCP, LOGL_ERROR, "Wrong trunk name '%s'\n", mgcp); + return NULL; + } + + endp = strtoul(rest + 1, &rest, 10); + if (rest == NULL || rest[0] != '@') { + LOGP(DLMGCP, LOGL_ERROR, "Wrong endpoint name '%s'\n", mgcp); + return NULL; + } + + /* signalling is on timeslot 1 */ + if (endp == 1) + return NULL; + + tcfg = mgcp_trunk_num(cfg, trunk); + if (!tcfg) { + LOGP(DLMGCP, LOGL_ERROR, "The trunk %d is not declared.\n", trunk); + return NULL; + } + + if (!tcfg->endpoints) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoints of trunk %d not allocated.\n", trunk); + return NULL; + } + + if (endp < 1 || endp >= tcfg->number_endpoints) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to find endpoint '%s'\n", mgcp); + return NULL; + } + + return &tcfg->endpoints[endp]; +} + +static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg, const char *mgcp) +{ + char *endptr = NULL; + unsigned int gw = INT_MAX; + + if (strncmp(mgcp, "ds/e1", 5) == 0) + return find_e1_endpoint(cfg, mgcp); + + gw = strtoul(mgcp, &endptr, 16); + if (gw > 0 && gw < cfg->trunk.number_endpoints && endptr[0] == '@') + return &cfg->trunk.endpoints[gw]; + + LOGP(DLMGCP, LOGL_ERROR, "Not able to find the endpoint: '%s'\n", mgcp); + return NULL; +} + +/** + * @returns 0 when the status line was complete and transaction_id and + * endp out parameters are set. + */ +static int mgcp_analyze_header(struct mgcp_parse_data *pdata, char *data) +{ + int i = 0; + char *elem, *save = NULL; + + OSMO_ASSERT(data); + pdata->trans = "000000"; + + for (elem = strtok_r(data, " ", &save); elem; + elem = strtok_r(NULL, " ", &save)) { + switch (i) { + case 0: + pdata->trans = elem; + break; + case 1: + pdata->endp = find_endpoint(pdata->cfg, elem); + if (!pdata->endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Unable to find Endpoint `%s'\n", elem); + return -1; + } + break; + case 2: + if (strcmp("MGCP", elem)) { + LOGP(DLMGCP, LOGL_ERROR, + "MGCP header parsing error\n"); + return -1; + } + break; + case 3: + if (strcmp("1.0", elem)) { + LOGP(DLMGCP, LOGL_ERROR, "MGCP version `%s' " + "not supported\n", elem); + return -1; + } + break; + } + i++; + } + + if (i != 4) { + LOGP(DLMGCP, LOGL_ERROR, "MGCP status line too short.\n"); + pdata->trans = "000000"; + pdata->endp = NULL; + return -1; + } + + return 0; +} + +static int verify_call_id(const struct mgcp_endpoint *endp, + const char *callid) +{ + if (strcmp(endp->callid, callid) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n", + ENDPOINT_NUMBER(endp), endp->callid, callid); + return -1; + } + + return 0; +} + +static int verify_ci(const struct mgcp_endpoint *endp, + const char *_ci) +{ + uint32_t ci = strtoul(_ci, NULL, 10); + + if (ci != endp->ci) { + LOGP(DLMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %u != %s\n", + ENDPOINT_NUMBER(endp), endp->ci, _ci); + return -1; + } + + return 0; +} + +static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p) +{ + if (p->found != 0) + return create_err_response(NULL, 500, "AUEP", p->trans); + else + return create_ok_response(p->endp, 200, "AUEP", p->trans); +} + +static int parse_conn_mode(const char *msg, struct mgcp_endpoint *endp) +{ + int ret = 0; + if (strcmp(msg, "recvonly") == 0) + endp->conn_mode = MGCP_CONN_RECV_ONLY; + else if (strcmp(msg, "sendrecv") == 0) + endp->conn_mode = MGCP_CONN_RECV_SEND; + else if (strcmp(msg, "sendonly") == 0) + endp->conn_mode = MGCP_CONN_SEND_ONLY; + else if (strcmp(msg, "loopback") == 0) + endp->conn_mode = MGCP_CONN_LOOPBACK; + else { + LOGP(DLMGCP, LOGL_ERROR, "Unknown connection mode: '%s'\n", msg); + ret = -1; + } + + endp->net_end.output_enabled = + endp->conn_mode & MGCP_CONN_SEND_ONLY ? 1 : 0; + endp->bts_end.output_enabled = + endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0; + + LOGP(DLMGCP, LOGL_DEBUG, "endpoint %x connection mode '%s' %d output_enabled net %d bts %d\n", + ENDPOINT_NUMBER(endp), + msg, endp->conn_mode, endp->net_end.output_enabled, + endp->bts_end.output_enabled); + + return ret; +} + +static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end, + struct mgcp_port_range *range, + int (*alloc)(struct mgcp_endpoint *endp, int port)) +{ + int i; + + if (range->mode == PORT_ALLOC_STATIC) { + end->local_alloc = PORT_ALLOC_STATIC; + return 0; + } + + /* attempt to find a port */ + for (i = 0; i < 200; ++i) { + int rc; + + if (range->last_port >= range->range_end) + range->last_port = range->range_start; + + rc = alloc(endp, range->last_port); + + range->last_port += 2; + if (rc == 0) { + end->local_alloc = PORT_ALLOC_DYNAMIC; + return 0; + } + + } + + LOGP(DLMGCP, LOGL_ERROR, "Allocating a RTP/RTCP port failed 200 times 0x%x.\n", + ENDPOINT_NUMBER(endp)); + return -1; +} + +static int allocate_ports(struct mgcp_endpoint *endp) +{ + if (allocate_port(endp, &endp->net_end, &endp->cfg->net_ports, + mgcp_bind_net_rtp_port) != 0) + return -1; + + if (allocate_port(endp, &endp->bts_end, &endp->cfg->bts_ports, + mgcp_bind_bts_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + return -1; + } + + if (endp->cfg->transcoder_ip && endp->tcfg->trunk_type == MGCP_TRUNK_VIRTUAL) { + if (allocate_port(endp, &endp->trans_net, + &endp->cfg->transcoder_ports, + mgcp_bind_trans_net_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->bts_end); + return -1; + } + + if (allocate_port(endp, &endp->trans_bts, + &endp->cfg->transcoder_ports, + mgcp_bind_trans_bts_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->bts_end); + mgcp_rtp_end_reset(&endp->trans_net); + return -1; + } + + /* remember that we have set up transcoding */ + endp->type = MGCP_RTP_TRANSCODED; + } + + return 0; +} + +/* Set the LCO from a string (see RFC 3435). + * The string is stored in the 'string' field. A NULL string is handled excatly + * like an empty string, the 'string' field is never NULL after this function + * has been called. */ +static void set_local_cx_options(void *ctx, struct mgcp_lco *lco, + const char *options) +{ + char *p_opt, *a_opt; + char codec[9]; + + talloc_free(lco->string); + talloc_free(lco->codec); + lco->codec = NULL; + lco->pkt_period_min = lco->pkt_period_max = 0; + lco->string = talloc_strdup(ctx, options ? options : ""); + + p_opt = strstr(lco->string, "p:"); + if (p_opt && sscanf(p_opt, "p:%d-%d", + &lco->pkt_period_min, &lco->pkt_period_max) == 1) + lco->pkt_period_max = lco->pkt_period_min; + + a_opt = strstr(lco->string, "a:"); + if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) + lco->codec = talloc_strdup(ctx, codec); +} + +void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change, + struct mgcp_rtp_end *rtp) +{ + struct mgcp_trunk_config *tcfg = endp->tcfg; + + int patch_ssrc = expect_ssrc_change && tcfg->force_constant_ssrc; + + rtp->force_aligned_timing = tcfg->force_aligned_timing; + rtp->force_constant_ssrc = patch_ssrc ? 1 : 0; + + LOGP(DLMGCP, LOGL_DEBUG, + "Configuring RTP endpoint: local port %d%s%s\n", + ntohs(rtp->rtp_port), + rtp->force_aligned_timing ? ", force constant timing" : "", + rtp->force_constant_ssrc ? ", force constant ssrc" : ""); +} + +uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *rtp) +{ + int f = 0; + + /* Get the number of frames per channel and packet */ + if (rtp->frames_per_packet) + f = rtp->frames_per_packet; + else if (rtp->packet_duration_ms && rtp->codec.frame_duration_num) { + int den = 1000 * rtp->codec.frame_duration_num; + f = (rtp->packet_duration_ms * rtp->codec.frame_duration_den + den/2) + / den; + } + + return rtp->codec.rate * f * rtp->codec.frame_duration_num / rtp->codec.frame_duration_den; +} + +static int mgcp_parse_osmux_cid(const char *line) +{ + int osmux_cid; + + if (sscanf(line + 2, "Osmux: %u", &osmux_cid) != 1) + return -1; + + if (osmux_cid > OSMUX_CID_MAX) { + LOGP(DLMGCP, LOGL_ERROR, "Osmux ID too large: %u > %u\n", + osmux_cid, OSMUX_CID_MAX); + return -1; + } + LOGP(DLMGCP, LOGL_DEBUG, "bsc-nat offered Osmux CID %u\n", osmux_cid); + + return osmux_cid; +} + +static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line) +{ + if (!endp->cfg->osmux_init) { + if (osmux_init(OSMUX_ROLE_BSC, endp->cfg) < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot init OSMUX\n"); + return -1; + } + LOGP(DLMGCP, LOGL_NOTICE, "OSMUX socket has been set up\n"); + } + + return mgcp_parse_osmux_cid(line); +} + +static struct msgb *handle_create_con(struct mgcp_parse_data *p) +{ + struct mgcp_trunk_config *tcfg; + struct mgcp_endpoint *endp = p->endp; + int error_code = 400; + + const char *local_options = NULL; + const char *callid = NULL; + const char *mode = NULL; + char *line; + int have_sdp = 0, osmux_cid = -1; + + if (p->found != 0) + return create_err_response(NULL, 510, "CRCX", p->trans); + + /* parse CallID C: and LocalParameters L: */ + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'L': + local_options = (const char *) line + 3; + break; + case 'C': + callid = (const char *) line + 3; + break; + case 'M': + mode = (const char *) line + 3; + break; + case 'X': + /* Osmux is not enabled in this bsc, ignore it so the + * bsc-nat knows that we don't want to use Osmux. + */ + if (!p->endp->cfg->osmux) + break; + + if (strncmp("Osmux: ", line + 2, strlen("Osmux: ")) == 0) + osmux_cid = mgcp_osmux_setup(endp, line); + break; + case '\0': + have_sdp = 1; + goto mgcp_header_done; + default: + LOGP(DLMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", + *line, *line, ENDPOINT_NUMBER(endp)); + break; + } + } + +mgcp_header_done: + tcfg = p->endp->tcfg; + + /* Check required data */ + if (!callid || !mode) { + LOGP(DLMGCP, LOGL_ERROR, "Missing callid and mode in CRCX on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + + if (endp->allocated) { + if (tcfg->force_realloc) { + LOGP(DLMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n", + ENDPOINT_NUMBER(endp)); + mgcp_release_endp(endp); + if (p->cfg->realloc_cb) + p->cfg->realloc_cb(tcfg, ENDPOINT_NUMBER(endp)); + } else { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + } + + /* copy some parameters */ + endp->callid = talloc_strdup(tcfg->endpoints, callid); + + set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, + local_options); + + if (parse_conn_mode(mode, endp) != 0) { + error_code = 517; + goto error2; + } + + /* initialize */ + endp->net_end.rtp_port = endp->net_end.rtcp_port = endp->bts_end.rtp_port = endp->bts_end.rtcp_port = 0; + mgcp_rtp_end_config(endp, 0, &endp->net_end); + mgcp_rtp_end_config(endp, 0, &endp->bts_end); + + /* set to zero until we get the info */ + memset(&endp->net_end.addr, 0, sizeof(endp->net_end.addr)); + + /* bind to the port now */ + if (allocate_ports(endp) != 0) + goto error2; + + /* assign a local call identifier or fail */ + endp->ci = generate_call_id(p->cfg); + if (endp->ci == CI_UNUSED) + goto error2; + + /* Annotate Osmux circuit ID and set it to negotiating state until this + * is fully set up from the dummy load. + */ + endp->osmux.state = OSMUX_STATE_DISABLED; + if (osmux_cid >= 0) { + endp->osmux.cid = osmux_cid; + endp->osmux.state = OSMUX_STATE_NEGOTIATING; + } else if (endp->cfg->osmux == OSMUX_USAGE_ONLY) { + LOGP(DLMGCP, LOGL_ERROR, + "Osmux only and no osmux offered on 0x%x\n", ENDPOINT_NUMBER(endp)); + goto error2; + } + + endp->allocated = 1; + + /* set up RTP media parameters */ + mgcp_set_audio_info(p->cfg, &endp->bts_end.codec, tcfg->audio_payload, tcfg->audio_name); + endp->bts_end.fmtp_extra = talloc_strdup(tcfg->endpoints, + tcfg->audio_fmtp_extra); + if (have_sdp) + mgcp_parse_sdp_data(endp, &endp->net_end, p); + else if (endp->local_options.codec) + mgcp_set_audio_info(p->cfg, &endp->net_end.codec, + PTYPE_UNDEFINED, endp->local_options.codec); + + if (p->cfg->bts_force_ptime) { + endp->bts_end.packet_duration_ms = p->cfg->bts_force_ptime; + endp->bts_end.force_output_ptime = 1; + } + + if (setup_rtp_processing(endp) != 0) + goto error2; + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_CRCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, "CRCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + mgcp_release_endp(endp); + return create_err_response(endp, 400, "CRCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + create_transcoder(endp); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + LOGP(DLMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n", + ENDPOINT_NUMBER(endp), endp->ci, + endp->net_end.local_port, endp->bts_end.local_port); + if (p->cfg->change_cb) + p->cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX); + + if (endp->conn_mode & MGCP_CONN_RECV_ONLY && tcfg->keepalive_interval != 0) { + send_dummy(endp); + } + + create_transcoder(endp); + return create_response_with_sdp(endp, "CRCX", p->trans); +error2: + mgcp_release_endp(endp); + LOGP(DLMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(endp, error_code, "CRCX", p->trans); +} + +static struct msgb *handle_modify_con(struct mgcp_parse_data *p) +{ + struct mgcp_endpoint *endp = p->endp; + int error_code = 500; + int silent = 0; + int have_sdp = 0; + char *line; + const char *local_options = NULL; + + if (p->found != 0) + return create_err_response(NULL, 510, "MDCX", p->trans); + + if (endp->ci == CI_UNUSED) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint is not " + "holding a connection. 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "MDCX", p->trans); + } + + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'C': { + if (verify_call_id(endp, line + 3) != 0) + goto error3; + break; + } + case 'I': { + if (verify_ci(endp, line + 3) != 0) + goto error3; + break; + } + case 'L': + local_options = (const char *) line + 3; + break; + case 'M': + if (parse_conn_mode(line + 3, endp) != 0) { + error_code = 517; + goto error3; + } + endp->orig_mode = endp->conn_mode; + break; + case 'Z': + silent = strcmp("noanswer", line + 3) == 0; + break; + case '\0': + /* SDP file begins */ + have_sdp = 1; + mgcp_parse_sdp_data(endp, &endp->net_end, p); + /* This will exhaust p->save, so the loop will + * terminate next time. + */ + break; + default: + LOGP(DLMGCP, LOGL_NOTICE, "Unhandled MGCP option: '%c'/%d on 0x%x\n", + line[0], line[0], ENDPOINT_NUMBER(endp)); + break; + } + } + + set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, + local_options); + + if (!have_sdp && endp->local_options.codec) + mgcp_set_audio_info(p->cfg, &endp->net_end.codec, + PTYPE_UNDEFINED, endp->local_options.codec); + + if (setup_rtp_processing(endp) != 0) + goto error3; + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_MDCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, "MDCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(endp, 400, "MDCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + LOGP(DLMGCP, LOGL_DEBUG, "endp %x MDCX defer\n", + ENDPOINT_NUMBER(endp)); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + mgcp_rtp_end_config(endp, 1, &endp->net_end); + mgcp_rtp_end_config(endp, 1, &endp->bts_end); + + /* modify */ + LOGP(DLMGCP, LOGL_DEBUG, "Modified endpoint on: 0x%x Server: %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); + if (p->cfg->change_cb) + p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX); + + if (endp->conn_mode & MGCP_CONN_RECV_ONLY && + endp->tcfg->keepalive_interval != 0) + send_dummy(endp); + + if (silent) + goto out_silent; + + return create_response_with_sdp(endp, "MDCX", p->trans); + +error3: + return create_err_response(endp, error_code, "MDCX", p->trans); + + +out_silent: + LOGP(DLMGCP, LOGL_DEBUG, "endp %x Modify endpoint: silent exit\n", + ENDPOINT_NUMBER(endp)); + return NULL; +} + +static struct msgb *handle_delete_con(struct mgcp_parse_data *p) +{ + struct mgcp_endpoint *endp = p->endp; + int error_code = 400; + int silent = 0; + char *line; + char stats[1048]; + + if (p->found != 0) + return create_err_response(NULL, error_code, "DLCX", p->trans); + + if (!p->endp->allocated) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "DLCX", p->trans); + } + + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'C': + if (verify_call_id(endp, line + 3) != 0) + goto error3; + break; + case 'I': + if (verify_ci(endp, line + 3) != 0) + goto error3; + break; + case 'Z': + silent = strcmp("noanswer", line + 3) == 0; + break; + default: + LOGP(DLMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", + line[0], line[0], ENDPOINT_NUMBER(endp)); + break; + } + } + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_DLCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, "DLCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(endp, 400, "DLCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + delete_transcoder(endp); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + /* free the connection */ + LOGP(DLMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); + + /* save the statistics of the current call */ + mgcp_format_stats(endp, stats, sizeof(stats)); + + delete_transcoder(endp); + mgcp_release_endp(endp); + if (p->cfg->change_cb) + p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX); + + if (silent) + goto out_silent; + return create_ok_resp_with_param(endp, 250, "DLCX", p->trans, stats); + +error3: + return create_err_response(endp, error_code, "DLCX", p->trans); + +out_silent: + return NULL; +} + +static struct msgb *handle_rsip(struct mgcp_parse_data *p) +{ + if (p->found != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to find the endpoint.\n"); + return NULL; + } + + if (p->cfg->reset_cb) + p->cfg->reset_cb(p->endp->tcfg); + return NULL; +} + +static char extract_tone(const char *line) +{ + const char *str = strstr(line, "D/"); + if (!str) + return CHAR_MAX; + + return str[2]; +} + +/* + * This can request like DTMF detection and forward, fax detection... it + * can also request when the notification should be send and such. We don't + * do this right now. + */ +static struct msgb *handle_noti_req(struct mgcp_parse_data *p) +{ + int res = 0; + char *line; + char tone = CHAR_MAX; + + if (p->found != 0) + return create_err_response(NULL, 400, "RQNT", p->trans); + + for_each_line(line, p->save) { + switch (line[0]) { + case 'S': + tone = extract_tone(line); + break; + } + } + + /* we didn't see a signal request with a tone */ + if (tone == CHAR_MAX) + return create_ok_response(p->endp, 200, "RQNT", p->trans); + + if (p->cfg->rqnt_cb) + res = p->cfg->rqnt_cb(p->endp, tone); + + return res == 0 ? + create_ok_response(p->endp, 200, "RQNT", p->trans) : + create_err_response(p->endp, res, "RQNT", p->trans); +} + +static void mgcp_keepalive_timer_cb(void *_tcfg) +{ + struct mgcp_trunk_config *tcfg = _tcfg; + int i; + LOGP(DLMGCP, LOGL_DEBUG, "Triggered trunk %d keepalive timer.\n", + tcfg->trunk_nr); + + if (tcfg->keepalive_interval <= 0) + return; + + for (i = 1; i < tcfg->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &tcfg->endpoints[i]; + if (endp->conn_mode == MGCP_CONN_RECV_ONLY) + send_dummy(endp); + } + + LOGP(DLMGCP, LOGL_DEBUG, "Rescheduling trunk %d keepalive timer.\n", + tcfg->trunk_nr); + osmo_timer_schedule(&tcfg->keepalive_timer, tcfg->keepalive_interval, 0); +} + +void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval) +{ + tcfg->keepalive_interval = interval; + osmo_timer_setup(&tcfg->keepalive_timer, mgcp_keepalive_timer_cb, tcfg); + + if (interval <= 0) + osmo_timer_del(&tcfg->keepalive_timer); + else + osmo_timer_schedule(&tcfg->keepalive_timer, + tcfg->keepalive_interval, 0); +} + +struct mgcp_config *mgcp_config_alloc(void) +{ + struct mgcp_config *cfg; + + cfg = talloc_zero(NULL, struct mgcp_config); + if (!cfg) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to allocate config.\n"); + return NULL; + } + + cfg->source_port = 2427; + cfg->source_addr = talloc_strdup(cfg, "0.0.0.0"); + cfg->osmux_addr = talloc_strdup(cfg, "0.0.0.0"); + + cfg->transcoder_remote_base = 4000; + + cfg->bts_ports.base_port = RTP_PORT_DEFAULT; + cfg->net_ports.base_port = RTP_PORT_NET_DEFAULT; + + cfg->rtp_processing_cb = &mgcp_rtp_processing_default; + cfg->setup_rtp_processing_cb = &mgcp_setup_rtp_processing_default; + + cfg->get_net_downlink_format_cb = &mgcp_get_net_downlink_format_default; + + /* default trunk handling */ + cfg->trunk.cfg = cfg; + cfg->trunk.trunk_nr = 0; + cfg->trunk.trunk_type = MGCP_TRUNK_VIRTUAL; + cfg->trunk.audio_name = talloc_strdup(cfg, "AMR/8000"); + cfg->trunk.audio_payload = 126; + cfg->trunk.audio_send_ptime = 1; + cfg->trunk.audio_send_name = 1; + cfg->trunk.omit_rtcp = 0; + mgcp_trunk_set_keepalive(&cfg->trunk, MGCP_KEEPALIVE_ONCE); + + INIT_LLIST_HEAD(&cfg->trunks); + + return cfg; +} + +struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + trunk = talloc_zero(cfg, struct mgcp_trunk_config); + if (!trunk) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to allocate.\n"); + return NULL; + } + + trunk->cfg = cfg; + trunk->trunk_type = MGCP_TRUNK_E1; + trunk->trunk_nr = nr; + trunk->audio_name = talloc_strdup(cfg, "AMR/8000"); + trunk->audio_payload = 126; + trunk->audio_send_ptime = 1; + trunk->audio_send_name = 1; + trunk->number_endpoints = 33; + trunk->omit_rtcp = 0; + mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE); + llist_add_tail(&trunk->entry, &cfg->trunks); + return trunk; +} + +struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &cfg->trunks, entry) + if (trunk->trunk_nr == index) + return trunk; + + return NULL; +} + +static void mgcp_rtp_codec_reset(struct mgcp_rtp_codec *codec) +{ + codec->payload_type = -1; + talloc_free(codec->subtype_name); + codec->subtype_name = NULL; + talloc_free(codec->audio_name); + codec->audio_name = NULL; + codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; + codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; + codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; + codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; +} + +static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end) +{ + if (end->local_alloc == PORT_ALLOC_DYNAMIC) { + mgcp_free_rtp_port(end); + end->local_port = 0; + } + + end->packets = 0; + end->octets = 0; + end->dropped_packets = 0; + memset(&end->addr, 0, sizeof(end->addr)); + end->rtp_port = end->rtcp_port = 0; + end->local_alloc = -1; + talloc_free(end->fmtp_extra); + end->fmtp_extra = NULL; + talloc_free(end->rtp_process_data); + end->rtp_process_data = NULL; + + /* Set default values */ + end->frames_per_packet = 0; /* unknown */ + end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS; + end->output_enabled = 0; + + mgcp_rtp_codec_reset(&end->codec); + mgcp_rtp_codec_reset(&end->alt_codec); +} + +static void mgcp_rtp_end_init(struct mgcp_rtp_end *end) +{ + mgcp_rtp_end_reset(end); + end->rtp.fd = -1; + end->rtcp.fd = -1; +} + +int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg) +{ + int i; + + /* Initialize all endpoints */ + tcfg->endpoints = _talloc_zero_array(tcfg->cfg, + sizeof(struct mgcp_endpoint), + tcfg->number_endpoints, "endpoints"); + if (!tcfg->endpoints) + return -1; + + for (i = 0; i < tcfg->number_endpoints; ++i) { + tcfg->endpoints[i].osmux.allocated_cid = -1; + tcfg->endpoints[i].ci = CI_UNUSED; + tcfg->endpoints[i].cfg = tcfg->cfg; + tcfg->endpoints[i].tcfg = tcfg; + mgcp_rtp_end_init(&tcfg->endpoints[i].net_end); + mgcp_rtp_end_init(&tcfg->endpoints[i].bts_end); + mgcp_rtp_end_init(&tcfg->endpoints[i].trans_net); + mgcp_rtp_end_init(&tcfg->endpoints[i].trans_bts); + } + + return 0; +} + +void mgcp_release_endp(struct mgcp_endpoint *endp) +{ + LOGP(DLMGCP, LOGL_DEBUG, "Releasing endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp)); + endp->ci = CI_UNUSED; + endp->allocated = 0; + + talloc_free(endp->callid); + endp->callid = NULL; + + talloc_free(endp->local_options.string); + endp->local_options.string = NULL; + talloc_free(endp->local_options.codec); + endp->local_options.codec = NULL; + + mgcp_rtp_end_reset(&endp->bts_end); + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->trans_net); + mgcp_rtp_end_reset(&endp->trans_bts); + endp->type = MGCP_RTP_DEFAULT; + + memset(&endp->net_state, 0, sizeof(endp->net_state)); + memset(&endp->bts_state, 0, sizeof(endp->bts_state)); + + endp->conn_mode = endp->orig_mode = MGCP_CONN_NONE; + + if (endp->osmux.state == OSMUX_STATE_ENABLED) + osmux_disable_endpoint(endp); + + /* release the circuit ID if it had been allocated */ + osmux_release_cid(endp); + + memset(&endp->taps, 0, sizeof(endp->taps)); +} + +void mgcp_initialize_endp(struct mgcp_endpoint *endp) +{ + return mgcp_release_endp(endp); +} + +static int send_trans(struct mgcp_config *cfg, const char *buf, int len) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr = cfg->transcoder_in; + addr.sin_port = htons(2427); + return sendto(cfg->gw_fd.bfd.fd, buf, len, 0, + (struct sockaddr *) &addr, sizeof(addr)); +} + +static void send_msg(struct mgcp_endpoint *endp, int endpoint, int port, + const char *msg, const char *mode) +{ + char buf[2096]; + int len; + int nchars; + + /* hardcoded to AMR right now, we do not know the real type at this point */ + len = snprintf(buf, sizeof(buf), + "%s 42 %x@mgw MGCP 1.0\r\n" + "C: 4256\r\n" + "M: %s\r\n" + "\r\n", + msg, endpoint, mode); + + if (len < 0) + return; + + nchars = write_response_sdp(endp, buf + len, sizeof(buf) + len - 1, NULL); + if (nchars < 0) + return; + + len += nchars; + + buf[sizeof(buf) - 1] = '\0'; + + send_trans(endp->cfg, buf, len); +} + +static void send_dlcx(struct mgcp_endpoint *endp, int endpoint) +{ + char buf[2096]; + int len; + + len = snprintf(buf, sizeof(buf), + "DLCX 43 %x@mgw MGCP 1.0\r\n" + "C: 4256\r\n" + , endpoint); + + if (len < 0) + return; + + buf[sizeof(buf) - 1] = '\0'; + + send_trans(endp->cfg, buf, len); +} + +static int send_agent(struct mgcp_config *cfg, const char *buf, int len) +{ + return write(cfg->gw_fd.bfd.fd, buf, len); +} + +int mgcp_send_reset_all(struct mgcp_config *cfg) +{ + static const char mgcp_reset[] = { + "RSIP 1 *@mgw MGCP 1.0\r\n" + }; + + return send_agent(cfg, mgcp_reset, sizeof mgcp_reset -1); +} + +int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint) +{ + char buf[128]; + int len; + + len = snprintf(buf, sizeof(buf), + "RSIP 39 %x@mgw MGCP 1.0\r\n" + , endpoint); + if (len < 0) + return len; + + buf[sizeof(buf) - 1] = '\0'; + + return send_agent(endp->cfg, buf, len); +} + +static int setup_rtp_processing(struct mgcp_endpoint *endp) +{ + int rc = 0; + struct mgcp_config *cfg = endp->cfg; + + if (endp->type != MGCP_RTP_DEFAULT) + return 0; + + if (endp->conn_mode == MGCP_CONN_LOOPBACK) + return 0; + + if (endp->conn_mode & MGCP_CONN_SEND_ONLY) + rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end); + else + rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, NULL); + + if (endp->conn_mode & MGCP_CONN_RECV_ONLY) + rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end); + else + rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, NULL); + return rc; +} + +static void create_transcoder(struct mgcp_endpoint *endp) +{ + int port; + int in_endp = ENDPOINT_NUMBER(endp); + int out_endp = endp_back_channel(in_endp); + + if (endp->type != MGCP_RTP_TRANSCODED) + return; + + send_msg(endp, in_endp, endp->trans_bts.local_port, "CRCX", "sendrecv"); + send_msg(endp, in_endp, endp->trans_bts.local_port, "MDCX", "sendrecv"); + send_msg(endp, out_endp, endp->trans_net.local_port, "CRCX", "sendrecv"); + send_msg(endp, out_endp, endp->trans_net.local_port, "MDCX", "sendrecv"); + + port = rtp_calculate_port(in_endp, endp->cfg->transcoder_remote_base); + endp->trans_bts.rtp_port = htons(port); + endp->trans_bts.rtcp_port = htons(port + 1); + + port = rtp_calculate_port(out_endp, endp->cfg->transcoder_remote_base); + endp->trans_net.rtp_port = htons(port); + endp->trans_net.rtcp_port = htons(port + 1); +} + +static void delete_transcoder(struct mgcp_endpoint *endp) +{ + int in_endp = ENDPOINT_NUMBER(endp); + int out_endp = endp_back_channel(in_endp); + + if (endp->type != MGCP_RTP_TRANSCODED) + return; + + send_dlcx(endp, in_endp); + send_dlcx(endp, out_endp); +} + +int mgcp_reset_transcoder(struct mgcp_config *cfg) +{ + if (!cfg->transcoder_ip) + return 0; + + static const char mgcp_reset[] = { + "RSIP 1 13@mgw MGCP 1.0\r\n" + }; + + return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1); +} + +void mgcp_format_stats(struct mgcp_endpoint *endp, char *msg, size_t size) +{ + uint32_t expected, jitter; + int ploss; + int nchars; + mgcp_state_calc_loss(&endp->net_state, &endp->net_end, + &expected, &ploss); + jitter = mgcp_state_calc_jitter(&endp->net_state); + + nchars = snprintf(msg, size, + "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", + endp->bts_end.packets, endp->bts_end.octets, + endp->net_end.packets, endp->net_end.octets, + ploss, jitter); + if (nchars < 0 || nchars >= size) + goto truncate; + + msg += nchars; + size -= nchars; + + /* Error Counter */ + nchars = snprintf(msg, size, + "\r\nX-Osmo-CP: EC TIS=%u, TOS=%u, TIR=%u, TOR=%u", + endp->net_state.in_stream.err_ts_counter, + endp->net_state.out_stream.err_ts_counter, + endp->bts_state.in_stream.err_ts_counter, + endp->bts_state.out_stream.err_ts_counter); + if (nchars < 0 || nchars >= size) + goto truncate; + + msg += nchars; + size -= nchars; + + if (endp->osmux.state == OSMUX_STATE_ENABLED) { + snprintf(msg, size, + "\r\nX-Osmux-ST: CR=%u, BR=%u", + endp->osmux.stats.chunks, + endp->osmux.stats.octets); + } +truncate: + msg[size - 1] = '\0'; +} + +int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, + uint32_t *pr, uint32_t *_or, int *loss, uint32_t *jitter) +{ + char *line, *save; + int rc; + + /* initialize with bad values */ + *ps = *os = *pr = *_or = *jitter = UINT_MAX; + *loss = INT_MAX; + + + line = strtok_r((char *) msg->l2h, "\r\n", &save); + if (!line) + return -1; + + /* this can only parse the message that is created above... */ + for_each_non_empty_line(line, save) { + switch (line[0]) { + case 'P': + rc = sscanf(line, "P: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", + ps, os, pr, _or, loss, jitter); + return rc == 6 ? 0 : -1; + } + } + + return -1; +} diff --git a/src/libosmo-legacy-mgcp/mgcp_sdp.c b/src/libosmo-legacy-mgcp/mgcp_sdp.c new file mode 100644 index 0000000..055e1af --- /dev/null +++ b/src/libosmo-legacy-mgcp/mgcp_sdp.c @@ -0,0 +1,305 @@ +/* + * Some SDP file parsing... + * + * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> + +#include <errno.h> + +struct sdp_rtp_map { + /* the type */ + int payload_type; + /* null, static or later dynamic codec name */ + char *codec_name; + /* A pointer to the original line for later parsing */ + char *map_line; + + int rate; + int channels; +}; + +int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, + int payload_type, const char *audio_name) +{ + int rate = codec->rate; + int channels = codec->channels; + char audio_codec[64]; + + talloc_free(codec->subtype_name); + codec->subtype_name = NULL; + talloc_free(codec->audio_name); + codec->audio_name = NULL; + + if (payload_type != PTYPE_UNDEFINED) + codec->payload_type = payload_type; + + if (!audio_name) { + switch (payload_type) { + case 0: audio_name = "PCMU/8000/1"; break; + case 3: audio_name = "GSM/8000/1"; break; + case 8: audio_name = "PCMA/8000/1"; break; + case 18: audio_name = "G729/8000/1"; break; + default: + /* Payload type is unknown, don't change rate and + * channels. */ + /* TODO: return value? */ + return 0; + } + } + + if (sscanf(audio_name, "%63[^/]/%d/%d", + audio_codec, &rate, &channels) < 1) + return -EINVAL; + + codec->rate = rate; + codec->channels = channels; + codec->subtype_name = talloc_strdup(ctx, audio_codec); + codec->audio_name = talloc_strdup(ctx, audio_name); + + if (!strcmp(audio_codec, "G729")) { + codec->frame_duration_num = 10; + codec->frame_duration_den = 1000; + } else { + codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; + codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; + } + + if (payload_type < 0) { + payload_type = 96; + if (rate == 8000 && channels == 1) { + if (!strcmp(audio_codec, "GSM")) + payload_type = 3; + else if (!strcmp(audio_codec, "PCMA")) + payload_type = 8; + else if (!strcmp(audio_codec, "PCMU")) + payload_type = 0; + else if (!strcmp(audio_codec, "G729")) + payload_type = 18; + } + + codec->payload_type = payload_type; + } + + if (channels != 1) + LOGP(DLMGCP, LOGL_NOTICE, + "Channels != 1 in SDP: '%s'\n", audio_name); + + return 0; +} + +void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) +{ + int i; + + for (i = 0; i < used; ++i) { + switch (codecs[i].payload_type) { + case 0: + codecs[i].codec_name = "PCMU"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 3: + codecs[i].codec_name = "GSM"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 8: + codecs[i].codec_name = "PCMA"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 18: + codecs[i].codec_name = "G729"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + } + } +} + +void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, int payload, char *audio_name) +{ + int i; + + for (i = 0; i < used; ++i) { + char audio_codec[64]; + int rate = -1; + int channels = -1; + if (codecs[i].payload_type != payload) + continue; + if (sscanf(audio_name, "%63[^/]/%d/%d", + audio_codec, &rate, &channels) < 1) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to parse '%s'\n", audio_name); + continue; + } + + codecs[i].map_line = talloc_strdup(ctx, audio_name); + codecs[i].codec_name = talloc_strdup(ctx, audio_codec); + codecs[i].rate = rate; + codecs[i].channels = channels; + return; + } + + LOGP(DLMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload, audio_name); +} + +int is_codec_compatible(struct mgcp_endpoint *endp, struct sdp_rtp_map *codec) +{ + char *bts_codec; + char audio_codec[64]; + + if (!codec->codec_name) + return 0; + + /* + * GSM, GSM/8000 and GSM/8000/1 should all be compatible.. let's go + * by name first. + */ + bts_codec = endp->tcfg->audio_name; + if (sscanf(bts_codec, "%63[^/]/%*d/%*d", audio_codec) < 1) + return 0; + + return strcasecmp(audio_codec, codec->codec_name) == 0; +} + +int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p) +{ + struct sdp_rtp_map codecs[10]; + int codecs_used = 0; + char *line; + int maxptime = -1; + int i; + int codecs_assigned = 0; + void *tmp_ctx = talloc_new(NULL); + + memset(&codecs, 0, sizeof(codecs)); + + for_each_line(line, p->save) { + switch (line[0]) { + case 'o': + case 's': + case 't': + case 'v': + /* skip these SDP attributes */ + break; + case 'a': { + int payload; + int ptime, ptime2 = 0; + char audio_name[64]; + + + if (sscanf(line, "a=rtpmap:%d %63s", + &payload, audio_name) == 2) { + codecs_update(tmp_ctx, codecs, codecs_used, payload, audio_name); + } else if (sscanf(line, "a=ptime:%d-%d", + &ptime, &ptime2) >= 1) { + if (ptime2 > 0 && ptime2 != ptime) + rtp->packet_duration_ms = 0; + else + rtp->packet_duration_ms = ptime; + } else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) { + maxptime = ptime2; + } + break; + } + case 'm': { + int port, rc; + + rc = sscanf(line, "m=audio %d RTP/AVP %d %d %d %d %d %d %d %d %d %d", + &port, + &codecs[0].payload_type, + &codecs[1].payload_type, + &codecs[2].payload_type, + &codecs[3].payload_type, + &codecs[4].payload_type, + &codecs[5].payload_type, + &codecs[6].payload_type, + &codecs[7].payload_type, + &codecs[8].payload_type, + &codecs[9].payload_type); + if (rc >= 2) { + rtp->rtp_port = htons(port); + rtp->rtcp_port = htons(port + 1); + codecs_used = rc - 1; + codecs_initialize(tmp_ctx, codecs, codecs_used); + } + break; + } + case 'c': { + char ipv4[16]; + + if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) { + inet_aton(ipv4, &rtp->addr); + } + break; + } + default: + if (p->endp) + LOGP(DLMGCP, LOGL_NOTICE, + "Unhandled SDP option: '%c'/%d on 0x%x\n", + line[0], line[0], ENDPOINT_NUMBER(p->endp)); + else + LOGP(DLMGCP, LOGL_NOTICE, + "Unhandled SDP option: '%c'/%d\n", + line[0], line[0]); + break; + } + } + + /* Now select the primary and alt_codec */ + for (i = 0; i < codecs_used && codecs_assigned < 2; ++i) { + struct mgcp_rtp_codec *codec = codecs_assigned == 0 ? + &rtp->codec : &rtp->alt_codec; + + if (endp->tcfg->no_audio_transcoding && + !is_codec_compatible(endp, &codecs[i])) { + LOGP(DLMGCP, LOGL_NOTICE, "Skipping codec %s\n", + codecs[i].codec_name); + continue; + } + + mgcp_set_audio_info(p->cfg, codec, + codecs[i].payload_type, + codecs[i].map_line); + codecs_assigned += 1; + } + + if (codecs_assigned > 0) { + /* TODO/XXX: Store this per codec and derive it on use */ + if (maxptime >= 0 && maxptime * rtp->codec.frame_duration_den > + rtp->codec.frame_duration_num * 1500) { + /* more than 1 frame */ + rtp->packet_duration_ms = 0; + } + + LOGP(DLMGCP, LOGL_NOTICE, + "Got media info via SDP: port %d, payload %d (%s), " + "duration %d, addr %s\n", + ntohs(rtp->rtp_port), rtp->codec.payload_type, + rtp->codec.subtype_name ? rtp->codec.subtype_name : "unknown", + rtp->packet_duration_ms, inet_ntoa(rtp->addr)); + } + + talloc_free(tmp_ctx); + return codecs_assigned > 0; +} + diff --git a/src/libosmo-legacy-mgcp/mgcp_transcode.c b/src/libosmo-legacy-mgcp/mgcp_transcode.c new file mode 100644 index 0000000..cbabdd1 --- /dev/null +++ b/src/libosmo-legacy-mgcp/mgcp_transcode.c @@ -0,0 +1,611 @@ +/* + * (C) 2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> + + +#include "g711common.h" + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> +#include <osmocom/legacy_mgcp/mgcp_transcode.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/netif/rtp.h> + +int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst) +{ + struct mgcp_process_rtp_state *state = state_; + if (dst) + return (nsamples >= 0 ? + nsamples / state->dst_samples_per_frame : + 1) * state->dst_frame_size; + else + return (nsamples >= 0 ? + nsamples / state->src_samples_per_frame : + 1) * state->src_frame_size; +} + +static enum audio_format get_audio_format(const struct mgcp_rtp_codec *codec) +{ + if (codec->subtype_name) { + if (!strcasecmp("GSM", codec->subtype_name)) + return AF_GSM; + if (!strcasecmp("PCMA", codec->subtype_name)) + return AF_PCMA; + if (!strcasecmp("PCMU", codec->subtype_name)) + return AF_PCMU; +#ifdef HAVE_BCG729 + if (!strcasecmp("G729", codec->subtype_name)) + return AF_G729; +#endif + if (!strcasecmp("L16", codec->subtype_name)) + return AF_L16; + } + + switch (codec->payload_type) { + case 0 /* PCMU */: + return AF_PCMU; + case 3 /* GSM */: + return AF_GSM; + case 8 /* PCMA */: + return AF_PCMA; +#ifdef HAVE_BCG729 + case 18 /* G.729 */: + return AF_G729; +#endif + case 11 /* L16 */: + return AF_L16; + default: + return AF_INVALID; + } +} + +static void l16_encode(short *sample, unsigned char *buf, size_t n) +{ + for (; n > 0; --n, ++sample, buf += 2) { + buf[0] = sample[0] >> 8; + buf[1] = sample[0] & 0xff; + } +} + +static void l16_decode(unsigned char *buf, short *sample, size_t n) +{ + for (; n > 0; --n, ++sample, buf += 2) + sample[0] = ((short)buf[0] << 8) | buf[1]; +} + +static void alaw_encode(short *sample, unsigned char *buf, size_t n) +{ + for (; n > 0; --n) + *(buf++) = s16_to_alaw(*(sample++)); +} + +static void alaw_decode(unsigned char *buf, short *sample, size_t n) +{ + for (; n > 0; --n) + *(sample++) = alaw_to_s16(*(buf++)); +} + +static void ulaw_encode(short *sample, unsigned char *buf, size_t n) +{ + for (; n > 0; --n) + *(buf++) = s16_to_ulaw(*(sample++)); +} + +static void ulaw_decode(unsigned char *buf, short *sample, size_t n) +{ + for (; n > 0; --n) + *(sample++) = ulaw_to_s16(*(buf++)); +} + +static int processing_state_destructor(struct mgcp_process_rtp_state *state) +{ + switch (state->src_fmt) { + case AF_GSM: + if (state->src.gsm_handle) + gsm_destroy(state->src.gsm_handle); + break; +#ifdef HAVE_BCG729 + case AF_G729: + if (state->src.g729_dec) + closeBcg729DecoderChannel(state->src.g729_dec); + break; +#endif + default: + break; + } + switch (state->dst_fmt) { + case AF_GSM: + if (state->dst.gsm_handle) + gsm_destroy(state->dst.gsm_handle); + break; +#ifdef HAVE_BCG729 + case AF_G729: + if (state->dst.g729_enc) + closeBcg729EncoderChannel(state->dst.g729_enc); + break; +#endif + default: + break; + } + return 0; +} + +int mgcp_transcoding_setup(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end) +{ + struct mgcp_process_rtp_state *state; + enum audio_format src_fmt, dst_fmt; + const struct mgcp_rtp_codec *dst_codec = &dst_end->codec; + + /* cleanup first */ + if (dst_end->rtp_process_data) { + talloc_free(dst_end->rtp_process_data); + dst_end->rtp_process_data = NULL; + } + + if (!src_end) + return 0; + + const struct mgcp_rtp_codec *src_codec = &src_end->codec; + + if (endp->tcfg->no_audio_transcoding) { + LOGP(DLMGCP, LOGL_NOTICE, + "Transcoding disabled on endpoint 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + src_fmt = get_audio_format(src_codec); + dst_fmt = get_audio_format(dst_codec); + + LOGP(DLMGCP, LOGL_ERROR, + "Checking transcoding: %s (%d) -> %s (%d)\n", + src_codec->subtype_name, src_codec->payload_type, + dst_codec->subtype_name, dst_codec->payload_type); + + if (src_fmt == AF_INVALID || dst_fmt == AF_INVALID) { + if (!src_codec->subtype_name || !dst_codec->subtype_name) + /* Not enough info, do nothing */ + return 0; + + if (strcasecmp(src_codec->subtype_name, dst_codec->subtype_name) == 0) + /* Nothing to do */ + return 0; + + LOGP(DLMGCP, LOGL_ERROR, + "Cannot transcode: %s codec not supported (%s -> %s).\n", + src_fmt != AF_INVALID ? "destination" : "source", + src_codec->audio_name, dst_codec->audio_name); + return -EINVAL; + } + + if (src_codec->rate && dst_codec->rate && src_codec->rate != dst_codec->rate) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot transcode: rate conversion (%d -> %d) not supported.\n", + src_codec->rate, dst_codec->rate); + return -EINVAL; + } + + state = talloc_zero(endp->tcfg->cfg, struct mgcp_process_rtp_state); + talloc_set_destructor(state, processing_state_destructor); + dst_end->rtp_process_data = state; + + state->src_fmt = src_fmt; + + switch (state->src_fmt) { + case AF_L16: + case AF_S16: + state->src_frame_size = 80 * sizeof(short); + state->src_samples_per_frame = 80; + break; + case AF_GSM: + state->src_frame_size = sizeof(gsm_frame); + state->src_samples_per_frame = 160; + state->src.gsm_handle = gsm_create(); + if (!state->src.gsm_handle) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize GSM decoder.\n"); + return -EINVAL; + } + break; +#ifdef HAVE_BCG729 + case AF_G729: + state->src_frame_size = 10; + state->src_samples_per_frame = 80; + state->src.g729_dec = initBcg729DecoderChannel(); + if (!state->src.g729_dec) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize G.729 decoder.\n"); + return -EINVAL; + } + break; +#endif + case AF_PCMU: + case AF_PCMA: + state->src_frame_size = 80; + state->src_samples_per_frame = 80; + break; + default: + break; + } + + state->dst_fmt = dst_fmt; + + switch (state->dst_fmt) { + case AF_L16: + case AF_S16: + state->dst_frame_size = 80*sizeof(short); + state->dst_samples_per_frame = 80; + break; + case AF_GSM: + state->dst_frame_size = sizeof(gsm_frame); + state->dst_samples_per_frame = 160; + state->dst.gsm_handle = gsm_create(); + if (!state->dst.gsm_handle) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize GSM encoder.\n"); + return -EINVAL; + } + break; +#ifdef HAVE_BCG729 + case AF_G729: + state->dst_frame_size = 10; + state->dst_samples_per_frame = 80; + state->dst.g729_enc = initBcg729EncoderChannel(); + if (!state->dst.g729_enc) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize G.729 decoder.\n"); + return -EINVAL; + } + break; +#endif + case AF_PCMU: + case AF_PCMA: + state->dst_frame_size = 80; + state->dst_samples_per_frame = 80; + break; + default: + break; + } + + if (dst_end->force_output_ptime) + state->dst_packet_duration = mgcp_rtp_packet_duration(endp, dst_end); + + LOGP(DLMGCP, LOGL_INFO, + "Initialized RTP processing on: 0x%x " + "conv: %d (%d, %d, %s) -> %d (%d, %d, %s)\n", + ENDPOINT_NUMBER(endp), + src_fmt, src_codec->payload_type, src_codec->rate, src_end->fmtp_extra, + dst_fmt, dst_codec->payload_type, dst_codec->rate, dst_end->fmtp_extra); + + return 0; +} + +void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp, + int *payload_type, + const char**audio_name, + const char**fmtp_extra) +{ + struct mgcp_process_rtp_state *state = endp->net_end.rtp_process_data; + struct mgcp_rtp_codec *net_codec = &endp->net_end.codec; + struct mgcp_rtp_codec *bts_codec = &endp->bts_end.codec; + + if (!state || net_codec->payload_type < 0) { + *payload_type = bts_codec->payload_type; + *audio_name = bts_codec->audio_name; + *fmtp_extra = endp->bts_end.fmtp_extra; + return; + } + + *payload_type = net_codec->payload_type; + *audio_name = net_codec->audio_name; + *fmtp_extra = endp->net_end.fmtp_extra; +} + +static int decode_audio(struct mgcp_process_rtp_state *state, + uint8_t **src, size_t *nbytes) +{ + while (*nbytes >= state->src_frame_size) { + if (state->sample_cnt + state->src_samples_per_frame > ARRAY_SIZE(state->samples)) { + LOGP(DLMGCP, LOGL_ERROR, + "Sample buffer too small: %zu > %zu.\n", + state->sample_cnt + state->src_samples_per_frame, + ARRAY_SIZE(state->samples)); + return -ENOSPC; + } + switch (state->src_fmt) { + case AF_GSM: + if (gsm_decode(state->src.gsm_handle, + (gsm_byte *)*src, state->samples + state->sample_cnt) < 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to decode GSM.\n"); + return -EINVAL; + } + break; +#ifdef HAVE_BCG729 + case AF_G729: + bcg729Decoder(state->src.g729_dec, *src, 0, state->samples + state->sample_cnt); + break; +#endif + case AF_PCMU: + ulaw_decode(*src, state->samples + state->sample_cnt, + state->src_samples_per_frame); + break; + case AF_PCMA: + alaw_decode(*src, state->samples + state->sample_cnt, + state->src_samples_per_frame); + break; + case AF_S16: + memmove(state->samples + state->sample_cnt, *src, + state->src_frame_size); + break; + case AF_L16: + l16_decode(*src, state->samples + state->sample_cnt, + state->src_samples_per_frame); + break; + default: + break; + } + *src += state->src_frame_size; + *nbytes -= state->src_frame_size; + state->sample_cnt += state->src_samples_per_frame; + } + return 0; +} + +static int encode_audio(struct mgcp_process_rtp_state *state, + uint8_t *dst, size_t buf_size, size_t max_samples) +{ + int nbytes = 0; + size_t nsamples = 0; + /* Encode samples into dst */ + while (nsamples + state->dst_samples_per_frame <= max_samples) { + if (nbytes + state->dst_frame_size > buf_size) { + if (nbytes > 0) + break; + + /* Not even one frame fits into the buffer */ + LOGP(DLMGCP, LOGL_INFO, + "Encoding (RTP) buffer too small: %zu > %zu.\n", + nbytes + state->dst_frame_size, buf_size); + return -ENOSPC; + } + switch (state->dst_fmt) { + case AF_GSM: + gsm_encode(state->dst.gsm_handle, + state->samples + state->sample_offs, dst); + break; +#ifdef HAVE_BCG729 + case AF_G729: + bcg729Encoder(state->dst.g729_enc, + state->samples + state->sample_offs, dst); + break; +#endif + case AF_PCMU: + ulaw_encode(state->samples + state->sample_offs, dst, + state->src_samples_per_frame); + break; + case AF_PCMA: + alaw_encode(state->samples + state->sample_offs, dst, + state->src_samples_per_frame); + break; + case AF_S16: + memmove(dst, state->samples + state->sample_offs, + state->dst_frame_size); + break; + case AF_L16: + l16_encode(state->samples + state->sample_offs, dst, + state->src_samples_per_frame); + break; + default: + break; + } + dst += state->dst_frame_size; + nbytes += state->dst_frame_size; + state->sample_offs += state->dst_samples_per_frame; + nsamples += state->dst_samples_per_frame; + } + state->sample_cnt -= nsamples; + return nbytes; +} + +static struct mgcp_rtp_end *source_for_dest(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end) +{ + if (&endp->bts_end == dst_end) + return &endp->net_end; + else if (&endp->net_end == dst_end) + return &endp->bts_end; + OSMO_ASSERT(0); +} + +/* + * With some modems we get offered multiple codecs + * and we have selected one of them. It might not + * be the right one and we need to detect this with + * the first audio packets. One difficulty is that + * we patch the rtp payload type in place, so we + * need to discuss this. + */ +struct mgcp_process_rtp_state *check_transcode_state( + struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct rtp_hdr *rtp_hdr) +{ + struct mgcp_rtp_end *src_end; + + /* Only deal with messages from net to bts */ + if (&endp->bts_end != dst_end) + goto done; + + src_end = source_for_dest(endp, dst_end); + + /* Already patched */ + if (rtp_hdr->payload_type == dst_end->codec.payload_type) + goto done; + /* The payload we expect */ + if (rtp_hdr->payload_type == src_end->codec.payload_type) + goto done; + /* The matching alternate payload type? Then switch */ + if (rtp_hdr->payload_type == src_end->alt_codec.payload_type) { + struct mgcp_config *cfg = endp->cfg; + struct mgcp_rtp_codec tmp_codec = src_end->alt_codec; + src_end->alt_codec = src_end->codec; + src_end->codec = tmp_codec; + cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end); + cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end); + } + +done: + return dst_end->rtp_process_data; +} + +int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size) +{ + struct mgcp_process_rtp_state *state; + const size_t rtp_hdr_size = sizeof(struct rtp_hdr); + struct rtp_hdr *rtp_hdr = (struct rtp_hdr *) data; + char *payload_data = (char *) &rtp_hdr->data[0]; + int payload_len = *len - rtp_hdr_size; + uint8_t *src = (uint8_t *)payload_data; + uint8_t *dst = (uint8_t *)payload_data; + size_t nbytes = payload_len; + size_t nsamples; + size_t max_samples; + uint32_t ts_no; + int rc; + + state = check_transcode_state(endp, dst_end, rtp_hdr); + if (!state) + return 0; + + if (state->src_fmt == state->dst_fmt) { + if (!state->dst_packet_duration) + return 0; + + /* TODO: repackage without transcoding */ + } + + /* If the remaining samples do not fit into a fixed ptime, + * a) discard them, if the next packet is much later + * b) add silence and * send it, if the current packet is not + * yet too late + * c) append the sample data, if the timestamp matches exactly + */ + + /* TODO: check payload type (-> G.711 comfort noise) */ + + if (payload_len > 0) { + ts_no = ntohl(rtp_hdr->timestamp); + if (!state->is_running) { + state->next_seq = ntohs(rtp_hdr->sequence); + state->next_time = ts_no; + state->is_running = 1; + } + + + if (state->sample_cnt > 0) { + int32_t delta = ts_no - state->next_time; + /* TODO: check sequence? reordering? packet loss? */ + + if (delta > state->sample_cnt) { + /* There is a time gap between the last packet + * and the current one. Just discard the + * partial data that is left in the buffer. + * TODO: This can be improved by adding silence + * instead if the delta is small enough. + */ + LOGP(DLMGCP, LOGL_NOTICE, + "0x%x dropping sample buffer due delta=%d sample_cnt=%zu\n", + ENDPOINT_NUMBER(endp), delta, state->sample_cnt); + state->sample_cnt = 0; + state->next_time = ts_no; + } else if (delta < 0) { + LOGP(DLMGCP, LOGL_NOTICE, + "RTP time jumps backwards, delta = %d, " + "discarding buffered samples\n", + delta); + state->sample_cnt = 0; + state->sample_offs = 0; + return -EAGAIN; + } + + /* Make sure the samples start without offset */ + if (state->sample_offs && state->sample_cnt) + memmove(&state->samples[0], + &state->samples[state->sample_offs], + state->sample_cnt * + sizeof(state->samples[0])); + } + + state->sample_offs = 0; + + /* Append decoded audio to samples */ + decode_audio(state, &src, &nbytes); + + if (nbytes > 0) + LOGP(DLMGCP, LOGL_NOTICE, + "Skipped audio frame in RTP packet: %zu octets\n", + nbytes); + } else + ts_no = state->next_time; + + if (state->sample_cnt < state->dst_packet_duration) + return -EAGAIN; + + max_samples = + state->dst_packet_duration ? + state->dst_packet_duration : state->sample_cnt; + + nsamples = state->sample_cnt; + + rc = encode_audio(state, dst, buf_size, max_samples); + /* + * There were no samples to encode? + * TODO: how does this work for comfort noise? + */ + if (rc == 0) + return -ENOMSG; + /* Any other error during the encoding */ + if (rc < 0) + return rc; + + nsamples -= state->sample_cnt; + + *len = rtp_hdr_size + rc; + rtp_hdr->sequence = htons(state->next_seq); + rtp_hdr->timestamp = htonl(ts_no); + + state->next_seq += 1; + state->next_time = ts_no + nsamples; + + /* + * XXX: At this point we should always have consumed + * samples. So doing OSMO_ASSERT(nsamples > 0) and returning + * rtp_hdr_size should be fine. + */ + return nsamples ? rtp_hdr_size : 0; +} diff --git a/src/libosmo-legacy-mgcp/mgcp_vty.c b/src/libosmo-legacy-mgcp/mgcp_vty.c new file mode 100644 index 0000000..7b11422 --- /dev/null +++ b/src/libosmo-legacy-mgcp/mgcp_vty.c @@ -0,0 +1,1553 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2014 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +#include <osmocom/core/talloc.h> + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> +#include <osmocom/legacy_mgcp/vty.h> + +#include <string.h> + +#define RTCP_OMIT_STR "Drop RTCP packets in both directions\n" +#define RTP_PATCH_STR "Modify RTP packet header in both directions\n" +#define RTP_KEEPALIVE_STR "Send dummy UDP packet to net RTP destination\n" + +static struct mgcp_config *g_cfg = NULL; + +static struct mgcp_trunk_config *find_trunk(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + if (nr == 0) + trunk = &cfg->trunk; + else + trunk = mgcp_trunk_num(cfg, nr); + + return trunk; +} + +/* + * vty code for mgcp below + */ +struct cmd_node mgcp_node = { + MGCP_NODE, + "%s(config-mgcp)# ", + 1, +}; + +struct cmd_node trunk_node = { + TRUNK_NODE, + "%s(config-mgcp-trunk)# ", + 1, +}; + +static int config_write_mgcp(struct vty *vty) +{ + vty_out(vty, "mgcp%s", VTY_NEWLINE); + if (g_cfg->local_ip) + vty_out(vty, " local ip %s%s", g_cfg->local_ip, VTY_NEWLINE); + if (g_cfg->bts_ip && strlen(g_cfg->bts_ip) != 0) + vty_out(vty, " bts ip %s%s", g_cfg->bts_ip, VTY_NEWLINE); + vty_out(vty, " bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE); + vty_out(vty, " bind port %u%s", g_cfg->source_port, VTY_NEWLINE); + + if (g_cfg->bts_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp bts-base %u%s", g_cfg->bts_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp bts-range %u %u%s", + g_cfg->bts_ports.range_start, g_cfg->bts_ports.range_end, VTY_NEWLINE); + if (g_cfg->bts_ports.bind_addr) + vty_out(vty, " rtp bts-bind-ip %s%s", g_cfg->bts_ports.bind_addr, VTY_NEWLINE); + + if (g_cfg->net_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp net-base %u%s", g_cfg->net_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp net-range %u %u%s", + g_cfg->net_ports.range_start, g_cfg->net_ports.range_end, VTY_NEWLINE); + if (g_cfg->net_ports.bind_addr) + vty_out(vty, " rtp net-bind-ip %s%s", g_cfg->net_ports.bind_addr, VTY_NEWLINE); + + vty_out(vty, " rtp ip-dscp %d%s", g_cfg->endp_dscp, VTY_NEWLINE); + if (g_cfg->trunk.keepalive_interval == MGCP_KEEPALIVE_ONCE) + vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE); + else if (g_cfg->trunk.keepalive_interval) + vty_out(vty, " rtp keep-alive %d%s", + g_cfg->trunk.keepalive_interval, VTY_NEWLINE); + else + vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE); + + if (g_cfg->trunk.omit_rtcp) + vty_out(vty, " rtcp-omit%s", VTY_NEWLINE); + else + vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE); + if (g_cfg->trunk.force_constant_ssrc || g_cfg->trunk.force_aligned_timing) { + vty_out(vty, " %srtp-patch ssrc%s", + g_cfg->trunk.force_constant_ssrc ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %srtp-patch timestamp%s", + g_cfg->trunk.force_aligned_timing ? "" : "no ", VTY_NEWLINE); + } else + vty_out(vty, " no rtp-patch%s", VTY_NEWLINE); + if (g_cfg->trunk.audio_payload != -1) + vty_out(vty, " sdp audio-payload number %d%s", + g_cfg->trunk.audio_payload, VTY_NEWLINE); + if (g_cfg->trunk.audio_name) + vty_out(vty, " sdp audio-payload name %s%s", + g_cfg->trunk.audio_name, VTY_NEWLINE); + if (g_cfg->trunk.audio_fmtp_extra) + vty_out(vty, " sdp audio fmtp-extra %s%s", + g_cfg->trunk.audio_fmtp_extra, VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-ptime%s", + g_cfg->trunk.audio_send_ptime ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-name%s", + g_cfg->trunk.audio_send_name ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " loop %u%s", !!g_cfg->trunk.audio_loop, VTY_NEWLINE); + vty_out(vty, " number endpoints %u%s", g_cfg->trunk.number_endpoints - 1, VTY_NEWLINE); + vty_out(vty, " %sallow-transcoding%s", + g_cfg->trunk.no_audio_transcoding ? "no " : "", VTY_NEWLINE); + if (g_cfg->call_agent_addr) + vty_out(vty, " call-agent ip %s%s", g_cfg->call_agent_addr, VTY_NEWLINE); + if (g_cfg->transcoder_ip) + vty_out(vty, " transcoder-mgw %s%s", g_cfg->transcoder_ip, VTY_NEWLINE); + + if (g_cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp transcoder-base %u%s", g_cfg->transcoder_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp transcoder-range %u %u%s", + g_cfg->transcoder_ports.range_start, g_cfg->transcoder_ports.range_end, VTY_NEWLINE); + if (g_cfg->bts_force_ptime > 0) + vty_out(vty, " rtp force-ptime %d%s", g_cfg->bts_force_ptime, VTY_NEWLINE); + vty_out(vty, " transcoder-remote-base %u%s", g_cfg->transcoder_remote_base, VTY_NEWLINE); + + switch (g_cfg->osmux) { + case OSMUX_USAGE_ON: + vty_out(vty, " osmux on%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_ONLY: + vty_out(vty, " osmux only%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_OFF: + default: + vty_out(vty, " osmux off%s", VTY_NEWLINE); + break; + } + if (g_cfg->osmux) { + vty_out(vty, " osmux bind-ip %s%s", + g_cfg->osmux_addr, VTY_NEWLINE); + vty_out(vty, " osmux batch-factor %d%s", + g_cfg->osmux_batch, VTY_NEWLINE); + vty_out(vty, " osmux batch-size %u%s", + g_cfg->osmux_batch_size, VTY_NEWLINE); + vty_out(vty, " osmux port %u%s", + g_cfg->osmux_port, VTY_NEWLINE); + vty_out(vty, " osmux dummy %s%s", + g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +static void dump_rtp_end(const char *end_name, struct vty *vty, + struct mgcp_rtp_state *state, struct mgcp_rtp_end *end) +{ + struct mgcp_rtp_codec *codec = &end->codec; + + vty_out(vty, + " %s%s" + " Timestamp Errs: %d->%d%s" + " Dropped Packets: %d%s" + " Payload Type: %d Rate: %u Channels: %d %s" + " Frame Duration: %u Frame Denominator: %u%s" + " FPP: %d Packet Duration: %u%s" + " FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s" + " Output-Enabled: %d Force-PTIME: %d%s", + end_name, VTY_NEWLINE, + state->in_stream.err_ts_counter, + state->out_stream.err_ts_counter, VTY_NEWLINE, + end->dropped_packets, VTY_NEWLINE, + codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE, + codec->frame_duration_num, codec->frame_duration_den, VTY_NEWLINE, + end->frames_per_packet, end->packet_duration_ms, VTY_NEWLINE, + end->fmtp_extra, codec->audio_name, codec->subtype_name, VTY_NEWLINE, + end->output_enabled, end->force_output_ptime, VTY_NEWLINE); +} + +static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg, int verbose) +{ + int i; + + vty_out(vty, "%s trunk nr %d with %d endpoints:%s", + cfg->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1", + cfg->trunk_nr, cfg->number_endpoints - 1, VTY_NEWLINE); + + if (!cfg->endpoints) { + vty_out(vty, "No endpoints allocated yet.%s", VTY_NEWLINE); + return; + } + + for (i = 1; i < cfg->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &cfg->endpoints[i]; + vty_out(vty, + " Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s " + "traffic received bts: %u remote: %u transcoder: %u/%u%s", + i, endp->ci, + ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port), + ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port), + inet_ntoa(endp->bts_end.addr), + endp->bts_end.packets, endp->net_end.packets, + endp->trans_net.packets, endp->trans_bts.packets, + VTY_NEWLINE); + + if (verbose && endp->allocated) { + dump_rtp_end("Net->BTS", vty, &endp->bts_state, &endp->bts_end); + dump_rtp_end("BTS->Net", vty, &endp->net_state, &endp->net_end); + } + } +} + +DEFUN(show_mcgp, show_mgcp_cmd, + "show mgcp [stats]", + SHOW_STR + "Display information about the MGCP Media Gateway\n" + "Include Statistics\n") +{ + struct mgcp_trunk_config *trunk; + int show_stats = argc >= 1; + + dump_trunk(vty, &g_cfg->trunk, show_stats); + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) + dump_trunk(vty, trunk, show_stats); + + if (g_cfg->osmux) + vty_out(vty, "Osmux used CID: %d%s", osmux_used_cid(), VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp, + cfg_mgcp_cmd, + "mgcp", + "Configure the MGCP") +{ + vty->node = MGCP_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_local_ip, + cfg_mgcp_local_ip_cmd, + "local ip A.B.C.D", + "Local options for the SDP record\n" + IP_STR + "IPv4 Address to use in SDP record\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->local_ip, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bts_ip, + cfg_mgcp_bts_ip_cmd, + "bts ip A.B.C.D", + "BTS Audio source/destination options\n" + IP_STR + "IPv4 Address of the BTS\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ip, argv[0]); + inet_aton(g_cfg->bts_ip, &g_cfg->bts_in); + return CMD_SUCCESS; +} + +#define BIND_STR "Listen/Bind related socket option\n" +DEFUN(cfg_mgcp_bind_ip, + cfg_mgcp_bind_ip_cmd, + "bind ip A.B.C.D", + BIND_STR + IP_STR + "IPv4 Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->source_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_port, + cfg_mgcp_bind_port_cmd, + "bind port <0-65534>", + BIND_STR + "Port information\n" + "UDP port to listen for MGCP messages\n") +{ + unsigned int port = atoi(argv[0]); + g_cfg->source_port = port; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_early, + cfg_mgcp_bind_early_cmd, + "bind early (0|1)", + BIND_STR + "Bind local ports on start up\n" + "Bind on demand\n" "Bind on startup\n") +{ + vty_out(vty, "bind early is deprecated, remove it from the config.\n"); + return CMD_WARNING; +} + +static void parse_base(struct mgcp_port_range *range, const char **argv) +{ + unsigned int port = atoi(argv[0]); + range->mode = PORT_ALLOC_STATIC; + range->base_port = port; +} + +static void parse_range(struct mgcp_port_range *range, const char **argv) +{ + range->mode = PORT_ALLOC_DYNAMIC; + range->range_start = atoi(argv[0]); + range->range_end = atoi(argv[1]); + range->last_port = g_cfg->bts_ports.range_start; +} + + +#define RTP_STR "RTP configuration\n" +#define BTS_START_STR "First UDP port allocated for the BTS side\n" +#define NET_START_STR "First UDP port allocated for the NET side\n" +#define UDP_PORT_STR "UDP Port number\n" +DEFUN(cfg_mgcp_rtp_bts_base_port, + cfg_mgcp_rtp_bts_base_port_cmd, + "rtp bts-base <0-65534>", + RTP_STR + BTS_START_STR + UDP_PORT_STR) +{ + parse_base(&g_cfg->bts_ports, argv); + return CMD_SUCCESS; +} + +#define RANGE_START_STR "Start of the range of ports\n" +#define RANGE_END_STR "End of the range of ports\n" +DEFUN(cfg_mgcp_rtp_bts_range, + cfg_mgcp_rtp_bts_range_cmd, + "rtp bts-range <0-65534> <0-65534>", + RTP_STR "Range of ports to use for the BTS side\n" + RANGE_START_STR RANGE_END_STR) +{ + parse_range(&g_cfg->bts_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_range, + cfg_mgcp_rtp_net_range_cmd, + "rtp net-range <0-65534> <0-65534>", + RTP_STR "Range of ports to use for the NET side\n" + RANGE_START_STR RANGE_END_STR) +{ + parse_range(&g_cfg->net_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_base_port, + cfg_mgcp_rtp_net_base_port_cmd, + "rtp net-base <0-65534>", + RTP_STR NET_START_STR UDP_PORT_STR) +{ + parse_base(&g_cfg->net_ports, argv); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_rtp_bts_base_port, cfg_mgcp_rtp_base_port_cmd, + "rtp base <0-65534>", + RTP_STR BTS_START_STR UDP_PORT_STR) + +DEFUN(cfg_mgcp_rtp_transcoder_range, + cfg_mgcp_rtp_transcoder_range_cmd, + "rtp transcoder-range <0-65534> <0-65534>", + RTP_STR "Range of ports to use for the Transcoder\n" + RANGE_START_STR RANGE_END_STR) +{ + parse_range(&g_cfg->transcoder_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_transcoder_base, + cfg_mgcp_rtp_transcoder_base_cmd, + "rtp transcoder-base <0-65534>", + RTP_STR "First UDP port allocated for the Transcoder side\n" + UDP_PORT_STR) +{ + parse_base(&g_cfg->transcoder_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_bts_bind_ip, + cfg_mgcp_rtp_bts_bind_ip_cmd, + "rtp bts-bind-ip A.B.C.D", + RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ports.bind_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_no_bts_bind_ip, + cfg_mgcp_rtp_no_bts_bind_ip_cmd, + "no rtp bts-bind-ip", + NO_STR RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n") +{ + talloc_free(g_cfg->bts_ports.bind_addr); + g_cfg->bts_ports.bind_addr = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_bind_ip, + cfg_mgcp_rtp_net_bind_ip_cmd, + "rtp net-bind-ip A.B.C.D", + RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->net_ports.bind_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_no_net_bind_ip, + cfg_mgcp_rtp_no_net_bind_ip_cmd, + "no rtp net-bind-ip", + NO_STR RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n") +{ + talloc_free(g_cfg->net_ports.bind_addr); + g_cfg->net_ports.bind_addr = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_ip_dscp, + cfg_mgcp_rtp_ip_dscp_cmd, + "rtp ip-dscp <0-255>", + RTP_STR + "Apply IP_TOS to the audio stream (including Osmux)\n" "The DSCP value\n") +{ + int dscp = atoi(argv[0]); + g_cfg->endp_dscp = dscp; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_rtp_ip_dscp, cfg_mgcp_rtp_ip_tos_cmd, + "rtp ip-tos <0-255>", + RTP_STR + "Apply IP_TOS to the audio stream\n" "The DSCP value\n") + +#define FORCE_PTIME_STR "Force a fixed ptime for packets sent to the BTS" +DEFUN(cfg_mgcp_rtp_force_ptime, + cfg_mgcp_rtp_force_ptime_cmd, + "rtp force-ptime (10|20|40)", + RTP_STR FORCE_PTIME_STR + "The required ptime (packet duration) in ms\n" + "10 ms\n20 ms\n40 ms\n") +{ + g_cfg->bts_force_ptime = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_rtp_force_ptime, + cfg_mgcp_no_rtp_force_ptime_cmd, + "no rtp force-ptime", + NO_STR RTP_STR FORCE_PTIME_STR) +{ + g_cfg->bts_force_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_sdp_fmtp_extra, + cfg_mgcp_sdp_fmtp_extra_cmd, + "sdp audio fmtp-extra .NAME", + "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n" + "Extra Information\n") +{ + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_fmtp_extra, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_allow_transcoding, + cfg_mgcp_allow_transcoding_cmd, + "allow-transcoding", + "Allow transcoding\n") +{ + g_cfg->trunk.no_audio_transcoding = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_allow_transcoding, + cfg_mgcp_no_allow_transcoding_cmd, + "no allow-transcoding", + NO_STR "Allow transcoding\n") +{ + g_cfg->trunk.no_audio_transcoding = 1; + return CMD_SUCCESS; +} + +#define SDP_STR "SDP File related options\n" +#define AUDIO_STR "Audio payload options\n" +DEFUN(cfg_mgcp_sdp_payload_number, + cfg_mgcp_sdp_payload_number_cmd, + "sdp audio-payload number <0-255>", + SDP_STR AUDIO_STR + "Number\n" "Payload number\n") +{ + unsigned int payload = atoi(argv[0]); + g_cfg->trunk.audio_payload = payload; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_number, cfg_mgcp_sdp_payload_number_cmd_old, + "sdp audio payload number <0-255>", + SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload number\n") + + +DEFUN(cfg_mgcp_sdp_payload_name, + cfg_mgcp_sdp_payload_name_cmd, + "sdp audio-payload name NAME", + SDP_STR AUDIO_STR "Name\n" "Payload name\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_name, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_name, cfg_mgcp_sdp_payload_name_cmd_old, + "sdp audio payload name NAME", + SDP_STR AUDIO_STR AUDIO_STR "Name\n" "Payload name\n") + +DEFUN(cfg_mgcp_sdp_payload_send_ptime, + cfg_mgcp_sdp_payload_send_ptime_cmd, + "sdp audio-payload send-ptime", + SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + g_cfg->trunk.audio_send_ptime = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_sdp_payload_send_ptime, + cfg_mgcp_no_sdp_payload_send_ptime_cmd, + "no sdp audio-payload send-ptime", + NO_STR SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + g_cfg->trunk.audio_send_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_sdp_payload_send_name, + cfg_mgcp_sdp_payload_send_name_cmd, + "sdp audio-payload send-name", + SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + g_cfg->trunk.audio_send_name = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_sdp_payload_send_name, + cfg_mgcp_no_sdp_payload_send_name_cmd, + "no sdp audio-payload send-name", + NO_STR SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + g_cfg->trunk.audio_send_name = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_loop, + cfg_mgcp_loop_cmd, + "loop (0|1)", + "Loop audio for all endpoints on main trunk\n" + "Don't Loop\n" "Loop\n") +{ + if (g_cfg->osmux) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + g_cfg->trunk.audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_force_realloc, + cfg_mgcp_force_realloc_cmd, + "force-realloc (0|1)", + "Force endpoint reallocation when the endpoint is still seized\n" + "Don't force reallocation\n" "force reallocation\n") +{ + g_cfg->trunk.force_realloc = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_number_endp, + cfg_mgcp_number_endp_cmd, + "number endpoints <0-65534>", + "Number options\n" "Endpoints available\n" "Number endpoints\n") +{ + /* + 1 as we start counting at one */ + g_cfg->trunk.number_endpoints = atoi(argv[0]) + 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_omit_rtcp, + cfg_mgcp_omit_rtcp_cmd, + "rtcp-omit", + RTCP_OMIT_STR) +{ + g_cfg->trunk.omit_rtcp = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_omit_rtcp, + cfg_mgcp_no_omit_rtcp_cmd, + "no rtcp-omit", + NO_STR RTCP_OMIT_STR) +{ + g_cfg->trunk.omit_rtcp = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_patch_rtp_ssrc, + cfg_mgcp_patch_rtp_ssrc_cmd, + "rtp-patch ssrc", + RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + g_cfg->trunk.force_constant_ssrc = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp_ssrc, + cfg_mgcp_no_patch_rtp_ssrc_cmd, + "no rtp-patch ssrc", + NO_STR RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + g_cfg->trunk.force_constant_ssrc = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_patch_rtp_ts, + cfg_mgcp_patch_rtp_ts_cmd, + "rtp-patch timestamp", + RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + g_cfg->trunk.force_aligned_timing = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp_ts, + cfg_mgcp_no_patch_rtp_ts_cmd, + "no rtp-patch timestamp", + NO_STR RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + g_cfg->trunk.force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp, + cfg_mgcp_no_patch_rtp_cmd, + "no rtp-patch", + NO_STR RTP_PATCH_STR) +{ + g_cfg->trunk.force_constant_ssrc = 0; + g_cfg->trunk.force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_keepalive, + cfg_mgcp_rtp_keepalive_cmd, + "rtp keep-alive <1-120>", + RTP_STR RTP_KEEPALIVE_STR + "Keep alive interval in secs\n" + ) +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_keepalive_once, + cfg_mgcp_rtp_keepalive_once_cmd, + "rtp keep-alive once", + RTP_STR RTP_KEEPALIVE_STR + "Send dummy packet only once after CRCX/MDCX\n" + ) +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, MGCP_KEEPALIVE_ONCE); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_rtp_keepalive, + cfg_mgcp_no_rtp_keepalive_cmd, + "no rtp keep-alive", + NO_STR RTP_STR RTP_KEEPALIVE_STR + ) +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, 0); + return CMD_SUCCESS; +} + + + +#define CALL_AGENT_STR "Callagent information\n" +DEFUN(cfg_mgcp_agent_addr, + cfg_mgcp_agent_addr_cmd, + "call-agent ip A.B.C.D", + CALL_AGENT_STR IP_STR + "IPv4 Address of the callagent\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->call_agent_addr, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_agent_addr, cfg_mgcp_agent_addr_cmd_old, + "call agent ip A.B.C.D", + CALL_AGENT_STR CALL_AGENT_STR IP_STR + "IPv4 Address of the callagent\n") + + +DEFUN(cfg_mgcp_transcoder, + cfg_mgcp_transcoder_cmd, + "transcoder-mgw A.B.C.D", + "Use a MGW to detranscoder RTP\n" + "The IP address of the MGW") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->transcoder_ip, argv[0]); + inet_aton(g_cfg->transcoder_ip, &g_cfg->transcoder_in); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_transcoder, + cfg_mgcp_no_transcoder_cmd, + "no transcoder-mgw", + NO_STR "Disable the transcoding\n") +{ + if (g_cfg->transcoder_ip) { + LOGP(DLMGCP, LOGL_NOTICE, "Disabling transcoding on future calls.\n"); + talloc_free(g_cfg->transcoder_ip); + g_cfg->transcoder_ip = NULL; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_transcoder_remote_base, + cfg_mgcp_transcoder_remote_base_cmd, + "transcoder-remote-base <0-65534>", + "Set the base port for the transcoder\n" "The RTP base port on the transcoder") +{ + g_cfg->transcoder_remote_base = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_trunk, cfg_mgcp_trunk_cmd, + "trunk <1-64>", + "Configure a SS7 trunk\n" "Trunk Nr\n") +{ + struct mgcp_trunk_config *trunk; + int index = atoi(argv[0]); + + trunk = mgcp_trunk_num(g_cfg, index); + if (!trunk) + trunk = mgcp_trunk_alloc(g_cfg, index); + + if (!trunk) { + vty_out(vty, "%%Unable to allocate trunk %u.%s", + index, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->node = TRUNK_NODE; + vty->index = trunk; + return CMD_SUCCESS; +} + +static int config_write_trunk(struct vty *vty) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + vty_out(vty, " trunk %d%s", trunk->trunk_nr, VTY_NEWLINE); + vty_out(vty, " sdp audio-payload number %d%s", + trunk->audio_payload, VTY_NEWLINE); + vty_out(vty, " sdp audio-payload name %s%s", + trunk->audio_name, VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-ptime%s", + trunk->audio_send_ptime ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-name%s", + trunk->audio_send_name ? "" : "no ", VTY_NEWLINE); + + if (trunk->keepalive_interval == MGCP_KEEPALIVE_ONCE) + vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE); + else if (trunk->keepalive_interval) + vty_out(vty, " rtp keep-alive %d%s", + trunk->keepalive_interval, VTY_NEWLINE); + else + vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE); + vty_out(vty, " loop %d%s", + trunk->audio_loop, VTY_NEWLINE); + vty_out(vty, " force-realloc %d%s", + trunk->force_realloc, VTY_NEWLINE); + if (trunk->omit_rtcp) + vty_out(vty, " rtcp-omit%s", VTY_NEWLINE); + else + vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE); + if (trunk->force_constant_ssrc || trunk->force_aligned_timing) { + vty_out(vty, " %srtp-patch ssrc%s", + trunk->force_constant_ssrc ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %srtp-patch timestamp%s", + trunk->force_aligned_timing ? "" : "no ", VTY_NEWLINE); + } else + vty_out(vty, " no rtp-patch%s", VTY_NEWLINE); + if (trunk->audio_fmtp_extra) + vty_out(vty, " sdp audio fmtp-extra %s%s", + trunk->audio_fmtp_extra, VTY_NEWLINE); + vty_out(vty, " %sallow-transcoding%s", + trunk->no_audio_transcoding ? "no " : "", VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_fmtp_extra, + cfg_trunk_sdp_fmtp_extra_cmd, + "sdp audio fmtp-extra .NAME", + "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n" + "Extra Information\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(g_cfg, &trunk->audio_fmtp_extra, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_payload_number, + cfg_trunk_payload_number_cmd, + "sdp audio-payload number <0-255>", + SDP_STR AUDIO_STR "Number\n" "Payload Number\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + unsigned int payload = atoi(argv[0]); + + trunk->audio_payload = payload; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_trunk_payload_number, cfg_trunk_payload_number_cmd_old, + "sdp audio payload number <0-255>", + SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload Number\n") + +DEFUN(cfg_trunk_payload_name, + cfg_trunk_payload_name_cmd, + "sdp audio-payload name NAME", + SDP_STR AUDIO_STR "Payload\n" "Payload Name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + + osmo_talloc_replace_string(g_cfg, &trunk->audio_name, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_trunk_payload_name, cfg_trunk_payload_name_cmd_old, + "sdp audio payload name NAME", + SDP_STR AUDIO_STR AUDIO_STR "Payload\n" "Payload Name\n") + + +DEFUN(cfg_trunk_loop, + cfg_trunk_loop_cmd, + "loop (0|1)", + "Loop audio for all endpoints on this trunk\n" + "Don't Loop\n" "Loop\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + + if (g_cfg->osmux) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + trunk->audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_payload_send_ptime, + cfg_trunk_sdp_payload_send_ptime_cmd, + "sdp audio-payload send-ptime", + SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_ptime = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_sdp_payload_send_ptime, + cfg_trunk_no_sdp_payload_send_ptime_cmd, + "no sdp audio-payload send-ptime", + NO_STR SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_payload_send_name, + cfg_trunk_sdp_payload_send_name_cmd, + "sdp audio-payload send-name", + SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_name = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_sdp_payload_send_name, + cfg_trunk_no_sdp_payload_send_name_cmd, + "no sdp audio-payload send-name", + NO_STR SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_name = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_omit_rtcp, + cfg_trunk_omit_rtcp_cmd, + "rtcp-omit", + RTCP_OMIT_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->omit_rtcp = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_omit_rtcp, + cfg_trunk_no_omit_rtcp_cmd, + "no rtcp-omit", + NO_STR RTCP_OMIT_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->omit_rtcp = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_patch_rtp_ssrc, + cfg_trunk_patch_rtp_ssrc_cmd, + "rtp-patch ssrc", + RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp_ssrc, + cfg_trunk_no_patch_rtp_ssrc_cmd, + "no rtp-patch ssrc", + NO_STR RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_patch_rtp_ts, + cfg_trunk_patch_rtp_ts_cmd, + "rtp-patch timestamp", + RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_aligned_timing = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp_ts, + cfg_trunk_no_patch_rtp_ts_cmd, + "no rtp-patch timestamp", + NO_STR RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp, + cfg_trunk_no_patch_rtp_cmd, + "no rtp-patch", + NO_STR RTP_PATCH_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 0; + trunk->force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_rtp_keepalive, + cfg_trunk_rtp_keepalive_cmd, + "rtp keep-alive <1-120>", + RTP_STR RTP_KEEPALIVE_STR + "Keep-alive interval in secs\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_rtp_keepalive_once, + cfg_trunk_rtp_keepalive_once_cmd, + "rtp keep-alive once", + RTP_STR RTP_KEEPALIVE_STR + "Send dummy packet only once after CRCX/MDCX\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_rtp_keepalive, + cfg_trunk_no_rtp_keepalive_cmd, + "no rtp keep-alive", + NO_STR RTP_STR RTP_KEEPALIVE_STR + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_allow_transcoding, + cfg_trunk_allow_transcoding_cmd, + "allow-transcoding", + "Allow transcoding\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->no_audio_transcoding = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_allow_transcoding, + cfg_trunk_no_allow_transcoding_cmd, + "no allow-transcoding", + NO_STR "Allow transcoding\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->no_audio_transcoding = 1; + return CMD_SUCCESS; +} + +DEFUN(loop_endp, + loop_endp_cmd, + "loop-endpoint <0-64> NAME (0|1)", + "Loop a given endpoint\n" "Trunk number\n" + "The name in hex of the endpoint\n" "Disable the loop\n" "Enable the loop\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Loopback number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + + endp = &trunk->endpoints[endp_no]; + int loop = atoi(argv[2]); + + if (loop) + endp->conn_mode = MGCP_CONN_LOOPBACK; + else + endp->conn_mode = endp->orig_mode; + + /* Handle it like a MDCX, switch on SSRC patching if enabled */ + mgcp_rtp_end_config(endp, 1, &endp->bts_end); + mgcp_rtp_end_config(endp, 1, &endp->net_end); + + return CMD_SUCCESS; +} + +DEFUN(tap_call, + tap_call_cmd, + "tap-call <0-64> ENDPOINT (bts-in|bts-out|net-in|net-out) A.B.C.D <0-65534>", + "Forward data on endpoint to a different system\n" "Trunk number\n" + "The endpoint in hex\n" + "Forward the data coming from the bts\n" + "Forward the data coming from the bts leaving to the network\n" + "Forward the data coming from the net\n" + "Forward the data coming from the net leaving to the bts\n" + "destination IP of the data\n" "destination port\n") +{ + struct mgcp_rtp_tap *tap; + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + int port = 0; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + + if (strcmp(argv[2], "bts-in") == 0) { + port = MGCP_TAP_BTS_IN; + } else if (strcmp(argv[2], "bts-out") == 0) { + port = MGCP_TAP_BTS_OUT; + } else if (strcmp(argv[2], "net-in") == 0) { + port = MGCP_TAP_NET_IN; + } else if (strcmp(argv[2], "net-out") == 0) { + port = MGCP_TAP_NET_OUT; + } else { + vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE); + return CMD_WARNING; + } + + tap = &endp->taps[port]; + memset(&tap->forward, 0, sizeof(tap->forward)); + inet_aton(argv[3], &tap->forward.sin_addr); + tap->forward.sin_port = htons(atoi(argv[4])); + tap->enabled = 1; + return CMD_SUCCESS; +} + +DEFUN(free_endp, free_endp_cmd, + "free-endpoint <0-64> NUMBER", + "Free the given endpoint\n" "Trunk number\n" + "Endpoint number in hex.\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + mgcp_release_endp(endp); + return CMD_SUCCESS; +} + +DEFUN(reset_endp, reset_endp_cmd, + "reset-endpoint <0-64> NUMBER", + "Reset the given endpoint\n" "Trunk number\n" + "Endpoint number in hex.\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + int endp_no, rc; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + rc = mgcp_send_reset_ep(endp, ENDPOINT_NUMBER(endp)); + if (rc < 0) { + vty_out(vty, "Error %d sending reset.%s", rc, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(reset_all_endp, reset_all_endp_cmd, + "reset-all-endpoints", + "Reset all endpoints\n") +{ + int rc; + + rc = mgcp_send_reset_all(g_cfg); + if (rc < 0) { + vty_out(vty, "Error %d during endpoint reset.%s", + rc, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +#define OSMUX_STR "RTP multiplexing\n" +DEFUN(cfg_mgcp_osmux, + cfg_mgcp_osmux_cmd, + "osmux (on|off|only)", + OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n") +{ + if (strcmp(argv[0], "off") == 0) { + g_cfg->osmux = OSMUX_USAGE_OFF; + return CMD_SUCCESS; + } + + if (strcmp(argv[0], "on") == 0) + g_cfg->osmux = OSMUX_USAGE_ON; + else if (strcmp(argv[0], "only") == 0) + g_cfg->osmux = OSMUX_USAGE_ONLY; + + if (g_cfg->trunk.audio_loop) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_ip, + cfg_mgcp_osmux_ip_cmd, + "osmux bind-ip A.B.C.D", + OSMUX_STR IP_STR "IPv4 Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->osmux_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_batch_factor, + cfg_mgcp_osmux_batch_factor_cmd, + "osmux batch-factor <1-8>", + OSMUX_STR "Batching factor\n" "Number of messages in the batch\n") +{ + g_cfg->osmux_batch = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_batch_size, + cfg_mgcp_osmux_batch_size_cmd, + "osmux batch-size <1-65535>", + OSMUX_STR "batch size\n" "Batch size in bytes\n") +{ + g_cfg->osmux_batch_size = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_port, + cfg_mgcp_osmux_port_cmd, + "osmux port <1-65535>", + OSMUX_STR "port\n" "UDP port\n") +{ + g_cfg->osmux_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_dummy, + cfg_mgcp_osmux_dummy_cmd, + "osmux dummy (on|off)", + OSMUX_STR "Dummy padding\n" "Enable dummy padding\n" "Disable dummy padding\n") +{ + if (strcmp(argv[0], "on") == 0) + g_cfg->osmux_dummy = 1; + else if (strcmp(argv[0], "off") == 0) + g_cfg->osmux_dummy = 0; + + return CMD_SUCCESS; +} + +int mgcp_vty_init(void) +{ + install_element_ve(&show_mgcp_cmd); + install_element(ENABLE_NODE, &loop_endp_cmd); + install_element(ENABLE_NODE, &tap_call_cmd); + install_element(ENABLE_NODE, &free_endp_cmd); + install_element(ENABLE_NODE, &reset_endp_cmd); + install_element(ENABLE_NODE, &reset_all_endp_cmd); + + install_element(CONFIG_NODE, &cfg_mgcp_cmd); + install_node(&mgcp_node, config_write_mgcp); + + install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bts_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_no_bts_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_no_net_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_base_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_force_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_rtp_force_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_once_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_rtp_keepalive_cmd); + install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd); + install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_transcoder_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_transcoder_cmd); + install_element(MGCP_NODE, &cfg_mgcp_transcoder_remote_base_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_loop_cmd); + install_element(MGCP_NODE, &cfg_mgcp_force_realloc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_omit_rtcp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_omit_rtcp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ssrc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ssrc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ts_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ts_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_fmtp_extra_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_factor_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_size_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_dummy_cmd); + install_element(MGCP_NODE, &cfg_mgcp_allow_transcoding_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_allow_transcoding_cmd); + + + install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd); + install_node(&trunk_node, config_write_trunk); + install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_cmd); + install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_once_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_rtp_keepalive_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd_old); + install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd_old); + install_element(TRUNK_NODE, &cfg_trunk_loop_cmd); + install_element(TRUNK_NODE, &cfg_trunk_omit_rtcp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_omit_rtcp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ssrc_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ssrc_cmd); + install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ts_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ts_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_fmtp_extra_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_ptime_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_ptime_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_allow_transcoding_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_allow_transcoding_cmd); + + return 0; +} + +static int allocate_trunk(struct mgcp_trunk_config *trunk) +{ + int i; + struct mgcp_config *cfg = trunk->cfg; + + if (mgcp_endpoints_allocate(trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to allocate %d endpoints on trunk %d.\n", + trunk->number_endpoints, trunk->trunk_nr); + return -1; + } + + /* early bind */ + for (i = 1; i < trunk->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &trunk->endpoints[i]; + + if (cfg->bts_ports.mode == PORT_ALLOC_STATIC) { + cfg->last_bts_port += 2; + if (mgcp_bind_bts_rtp_port(endp, cfg->last_bts_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, + "Failed to bind: %d\n", cfg->last_bts_port); + return -1; + } + endp->bts_end.local_alloc = PORT_ALLOC_STATIC; + } + + if (cfg->net_ports.mode == PORT_ALLOC_STATIC) { + cfg->last_net_port += 2; + if (mgcp_bind_net_rtp_port(endp, cfg->last_net_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, + "Failed to bind: %d\n", cfg->last_net_port); + return -1; + } + endp->net_end.local_alloc = PORT_ALLOC_STATIC; + } + + if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL && + cfg->transcoder_ip && cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) { + int rtp_port; + + /* network side */ + rtp_port = rtp_calculate_port(ENDPOINT_NUMBER(endp), + cfg->transcoder_ports.base_port); + if (mgcp_bind_trans_net_rtp_port(endp, rtp_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port); + return -1; + } + endp->trans_net.local_alloc = PORT_ALLOC_STATIC; + + /* bts side */ + rtp_port = rtp_calculate_port(endp_back_channel(ENDPOINT_NUMBER(endp)), + cfg->transcoder_ports.base_port); + if (mgcp_bind_trans_bts_rtp_port(endp, rtp_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port); + return -1; + } + endp->trans_bts.local_alloc = PORT_ALLOC_STATIC; + } + } + + return 0; +} + +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg, + enum mgcp_role role) +{ + int rc; + struct mgcp_trunk_config *trunk; + + cfg->osmux_port = OSMUX_PORT; + cfg->osmux_batch = 4; + cfg->osmux_batch_size = OSMUX_BATCH_DEFAULT_MAX; + + g_cfg = cfg; + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + + if (!g_cfg->bts_ip) + fprintf(stderr, "No BTS ip address specified. This will allow everyone to connect.\n"); + + if (!g_cfg->source_addr) { + fprintf(stderr, "You need to specify a bind address.\n"); + return -1; + } + + /* initialize the last ports */ + g_cfg->last_bts_port = rtp_calculate_port(0, g_cfg->bts_ports.base_port); + g_cfg->last_net_port = rtp_calculate_port(0, g_cfg->net_ports.base_port); + + if (allocate_trunk(&g_cfg->trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to initialize the virtual trunk.\n"); + return -1; + } + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + if (allocate_trunk(trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize E1 trunk %d.\n", trunk->trunk_nr); + return -1; + } + } + cfg->role = role; + + return 0; +} + diff --git a/src/libosmo-mgcp-client/Makefile.am b/src/libosmo-mgcp-client/Makefile.am new file mode 100644 index 0000000..1e4e764 --- /dev/null +++ b/src/libosmo-mgcp-client/Makefile.am @@ -0,0 +1,34 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +# This is not at all related to the release version, but a range of supported +# API versions. Read TODO_RELEASE in the source tree's root! +MGCP_CLIENT_LIBVERSION=2:0:0 + +lib_LTLIBRARIES = \ + libosmo-mgcp-client.la \ + $(NULL) + +libosmo_mgcp_client_la_SOURCES = \ + mgcp_client.c \ + mgcp_client_vty.c \ + $(NULL) + +libosmo_mgcp_client_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(MGCP_CLIENT_LIBVERSION) diff --git a/src/libosmo-mgcp-client/mgcp_client.c b/src/libosmo-mgcp-client/mgcp_client.c new file mode 100644 index 0000000..f8c55ac --- /dev/null +++ b/src/libosmo-mgcp-client/mgcp_client.c @@ -0,0 +1,766 @@ +/* mgcp_utils - common functions to setup an MGCP connection + */ +/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/byteswap.h> + +#include <osmocom/mgcp_client/mgcp_client.h> +#include <osmocom/mgcp_client/mgcp_client_internal.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <unistd.h> +#include <string.h> + +void mgcp_client_conf_init(struct mgcp_client_conf *conf) +{ + /* NULL and -1 default to MGCP_CLIENT_*_DEFAULT values */ + *conf = (struct mgcp_client_conf){ + .local_addr = NULL, + .local_port = -1, + .remote_addr = NULL, + .remote_port = -1, + .first_endpoint = 0, + .last_endpoint = 0, + .bts_base = 0, + }; +} + +/* Test if a given endpoint id is currently in use */ +static bool endpoint_in_use(uint16_t id, struct mgcp_client *client) +{ + struct mgcp_inuse_endpoint *endpoint; + llist_for_each_entry(endpoint, &client->inuse_endpoints, entry) { + if (endpoint->id == id) + return true; + } + + return false; +} + +/* Find and seize an unsused endpoint id */ +int mgcp_client_next_endpoint(struct mgcp_client *client) +{ + int i; + uint16_t first_endpoint = client->actual.first_endpoint; + uint16_t last_endpoint = client->actual.last_endpoint; + struct mgcp_inuse_endpoint *endpoint; + + /* Use the maximum permitted range if the VTY + * configuration does not specify a range */ + if (client->actual.last_endpoint == 0) { + first_endpoint = 1; + last_endpoint = 65534; + } + + /* Test the permitted endpoint range for an endpoint + * number that is not in use. When a suitable endpoint + * number can be found, seize it by adding it to the + * inuse list. */ + for (i=first_endpoint;i<last_endpoint;i++) + { + if (endpoint_in_use(i,client) == false) { + endpoint = talloc_zero(client, struct mgcp_inuse_endpoint); + endpoint->id = i; + llist_add_tail(&endpoint->entry, &client->inuse_endpoints); + return endpoint->id; + } + } + + /* All endpoints are busy! */ + return -EINVAL; +} + +/* Release a seized endpoint id to make it available again for other calls */ +void mgcp_client_release_endpoint(uint16_t id, struct mgcp_client *client) +{ + struct mgcp_inuse_endpoint *endpoint; + struct mgcp_inuse_endpoint *endpoint_tmp; + llist_for_each_entry_safe(endpoint, endpoint_tmp, &client->inuse_endpoints, entry) { + if (endpoint->id == id) { + llist_del(&endpoint->entry); + talloc_free(endpoint); + } + } +} + +static void mgcp_client_handle_response(struct mgcp_client *mgcp, + struct mgcp_response_pending *pending, + struct mgcp_response *response) +{ + if (!pending) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot handle NULL response\n"); + return; + } + if (pending->response_cb) + pending->response_cb(response, pending->priv); + else + LOGP(DLMGCP, LOGL_INFO, "MGCP response ignored (NULL cb)\n"); + talloc_free(pending); +} + +static int mgcp_response_parse_head(struct mgcp_response *r, struct msgb *msg) +{ + int comment_pos; + char *end; + + if (mgcp_msg_terminate_nul(msg)) + goto response_parse_failure; + + r->body = (char *)msg->data; + + if (sscanf(r->body, "%3d %u %n", + &r->head.response_code, &r->head.trans_id, + &comment_pos) != 2) + goto response_parse_failure; + + r->head.comment = r->body + comment_pos; + end = strchr(r->head.comment, '\r'); + if (!end) + goto response_parse_failure; + /* Mark the end of the comment */ + *end = '\0'; + r->body = end + 1; + if (r->body[0] == '\n') + r->body ++; + return 0; + +response_parse_failure: + LOGP(DLMGCP, LOGL_ERROR, + "Failed to parse MGCP response header\n"); + return -EINVAL; +} + +/* TODO undup against mgcp_protocol.c:mgcp_check_param() */ +static bool mgcp_line_is_valid(const char *line) +{ + const size_t line_len = strlen(line); + if (line[0] == '\0') + return true; + + if (line_len < 2 + || line[1] != '=') { + LOGP(DLMGCP, LOGL_ERROR, + "Wrong MGCP option format: '%s'\n", + line); + return false; + } + + return true; +} + +/* Parse a line like "m=audio 16002 RTP/AVP 98" */ +static int mgcp_parse_audio_port(struct mgcp_response *r, const char *line) +{ + if (sscanf(line, "m=audio %hu", + &r->audio_port) != 1) + goto response_parse_failure; + + return 0; + +response_parse_failure: + LOGP(DLMGCP, LOGL_ERROR, + "Failed to parse MGCP response header (audio port)\n"); + return -EINVAL; +} + +/* Parse a line like "c=IN IP4 10.11.12.13" */ +static int mgcp_parse_audio_ip(struct mgcp_response *r, const char *line) +{ + struct in_addr ip_test; + + if (strlen(line) < 16) + goto response_parse_failure; + + /* The current implementation strictly supports IPV4 only ! */ + if (memcmp("c=IN IP4 ", line, 9) != 0) + goto response_parse_failure; + + /* Extract IP-Address */ + strncpy(r->audio_ip, line + 9, sizeof(r->audio_ip)); + r->audio_ip[sizeof(r->audio_ip) - 1] = '\0'; + + /* Check IP-Address */ + if (inet_aton(r->audio_ip, &ip_test) == 0) + goto response_parse_failure; + + return 0; + +response_parse_failure: + LOGP(DLMGCP, LOGL_ERROR, + "Failed to parse MGCP response header (audio ip)\n"); + return -EINVAL; +} + +int mgcp_response_parse_params(struct mgcp_response *r) +{ + char *line; + int rc; + OSMO_ASSERT(r->body); + char *data = strstr(r->body, "\n\n"); + + if (!data) { + LOGP(DLMGCP, LOGL_ERROR, + "MGCP response: cannot find start of parameters\n"); + return -EINVAL; + } + + /* Advance to after the \n\n, replace the second \n with \0. That's + * where the parameters start. */ + data ++; + *data = '\0'; + data ++; + + for_each_non_empty_line(line, data) { + if (!mgcp_line_is_valid(line)) + return -EINVAL; + + switch (line[0]) { + case 'm': + rc = mgcp_parse_audio_port(r, line); + if (rc) + return rc; + break; + case 'c': + rc = mgcp_parse_audio_ip(r, line); + if (rc) + return rc; + break; + default: + /* skip unhandled parameters */ + break; + } + } + return 0; +} + +static struct mgcp_response_pending *mgcp_client_response_pending_get( + struct mgcp_client *mgcp, + struct mgcp_response *r) +{ + struct mgcp_response_pending *pending; + if (!r) + return NULL; + llist_for_each_entry(pending, &mgcp->responses_pending, entry) { + if (pending->trans_id == r->head.trans_id) { + llist_del(&pending->entry); + return pending; + } + } + return NULL; +} + +/* Feed an MGCP message into the receive processing. + * Parse the head and call any callback registered for the transaction id found + * in the MGCP message. This is normally called directly from the internal + * mgcp_do_read that reads from the socket connected to the MGCP gateway. This + * function is published mainly to be able to feed data from the test suite. + */ +int mgcp_client_rx(struct mgcp_client *mgcp, struct msgb *msg) +{ + struct mgcp_response r = { 0 }; + struct mgcp_response_pending *pending; + int rc; + + rc = mgcp_response_parse_head(&r, msg); + if (rc) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot parse MGCP response\n"); + return -1; + } + + pending = mgcp_client_response_pending_get(mgcp, &r); + if (!pending) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find matching MGCP transaction for trans_id %d\n", + r.head.trans_id); + return -1; + } + + mgcp_client_handle_response(mgcp, pending, &r); + return 0; +} + +static int mgcp_do_read(struct osmo_fd *fd) +{ + struct mgcp_client *mgcp = fd->data; + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(4096, 128, "mgcp_from_gw"); + if (!msg) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n"); + return -1; + } + + ret = read(fd->fd, msg->data, 4096 - 128); + if (ret <= 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno)); + msgb_free(msg); + return -1; + } else if (ret > 4096 - 128) { + LOGP(DLMGCP, LOGL_ERROR, "Too much data: %d\n", ret); + msgb_free(msg); + return -1; + } + + msg->l2h = msgb_put(msg, ret); + ret = mgcp_client_rx(mgcp, msg); + talloc_free(msg); + return ret; +} + +static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg) +{ + int ret; + static char strbuf[4096]; + unsigned int l = msg->len < sizeof(strbuf) ? msg->len : sizeof(strbuf); + unsigned int i; + + strncpy(strbuf, (const char*)msg->data, l); + for (i = 0; i < sizeof(strbuf); i++) { + if (strbuf[i] == '\n' || strbuf[i] == '\r') { + strbuf[i] = '\0'; + break; + } + } + DEBUGP(DLMGCP, "Tx MGCP msg to MGCP GW: '%s'\n", strbuf); + + LOGP(DLMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len); + + ret = write(fd->fd, msg->data, msg->len); + if (ret != msg->len) + LOGP(DLMGCP, LOGL_ERROR, "Failed to forward message to MGCP" + " GW: %s\n", strerror(errno)); + + return ret; +} + +struct mgcp_client *mgcp_client_init(void *ctx, + struct mgcp_client_conf *conf) +{ + struct mgcp_client *mgcp; + + mgcp = talloc_zero(ctx, struct mgcp_client); + + INIT_LLIST_HEAD(&mgcp->responses_pending); + INIT_LLIST_HEAD(&mgcp->inuse_endpoints); + + mgcp->next_trans_id = 1; + + mgcp->actual.local_addr = conf->local_addr ? conf->local_addr : + MGCP_CLIENT_LOCAL_ADDR_DEFAULT; + mgcp->actual.local_port = conf->local_port >= 0 ? (uint16_t)conf->local_port : + MGCP_CLIENT_LOCAL_PORT_DEFAULT; + + mgcp->actual.remote_addr = conf->remote_addr ? conf->remote_addr : + MGCP_CLIENT_REMOTE_ADDR_DEFAULT; + mgcp->actual.remote_port = conf->remote_port >= 0 ? (uint16_t)conf->remote_port : + MGCP_CLIENT_REMOTE_PORT_DEFAULT; + + mgcp->actual.first_endpoint = conf->first_endpoint > 0 ? (uint16_t)conf->first_endpoint : 0; + mgcp->actual.last_endpoint = conf->last_endpoint > 0 ? (uint16_t)conf->last_endpoint : 0; + mgcp->actual.bts_base = conf->bts_base > 0 ? (uint16_t)conf->bts_base : 4000; + + return mgcp; +} + +int mgcp_client_connect(struct mgcp_client *mgcp) +{ + int on; + struct sockaddr_in addr; + struct osmo_wqueue *wq; + int rc; + + if (!mgcp) { + LOGP(DLMGCP, LOGL_FATAL, "MGCPGW client not initialized properly\n"); + return -EINVAL; + } + + wq = &mgcp->wq; + + wq->bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (wq->bfd.fd < 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno); + return -errno; + } + + on = 1; + if (setsockopt(wq->bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { + LOGP(DLMGCP, LOGL_FATAL, + "Failed to initialize socket for MGCP GW: %s\n", + strerror(errno)); + rc = -errno; + goto error_close_fd; + } + + /* bind socket */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + inet_aton(mgcp->actual.local_addr, &addr.sin_addr); + addr.sin_port = htons(mgcp->actual.local_port); + if (bind(wq->bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DLMGCP, LOGL_FATAL, + "Failed to bind for MGCP GW to %s %u\n", + mgcp->actual.local_addr, mgcp->actual.local_port); + rc = -errno; + goto error_close_fd; + } + + /* connect to the remote */ + inet_aton(mgcp->actual.remote_addr, &addr.sin_addr); + addr.sin_port = htons(mgcp->actual.remote_port); + if (connect(wq->bfd.fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + LOGP(DLMGCP, LOGL_FATAL, + "Failed to connect to MGCP GW at %s %u: %s\n", + mgcp->actual.remote_addr, mgcp->actual.remote_port, + strerror(errno)); + rc = -errno; + goto error_close_fd; + } + + mgcp->remote_addr = htonl(addr.sin_addr.s_addr); + + osmo_wqueue_init(wq, 10); + wq->bfd.when = BSC_FD_READ; + wq->bfd.data = mgcp; + wq->read_cb = mgcp_do_read; + wq->write_cb = mgcp_do_write; + + if (osmo_fd_register(&wq->bfd) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to register BFD\n"); + rc = -EIO; + goto error_close_fd; + } + LOGP(DLMGCP, LOGL_INFO, "MGCP GW connection: %s:%u -> %s:%u\n", + mgcp->actual.local_addr, mgcp->actual.local_port, + mgcp->actual.remote_addr, mgcp->actual.remote_port); + + return 0; +error_close_fd: + close(wq->bfd.fd); + wq->bfd.fd = -1; + return rc; +} + +const char *mgcp_client_remote_addr_str(struct mgcp_client *mgcp) +{ + return mgcp->actual.remote_addr; +} + +uint16_t mgcp_client_remote_port(struct mgcp_client *mgcp) +{ + return mgcp->actual.remote_port; +} + +/* Return the MGCP GW binary IPv4 address in network byte order. */ +uint32_t mgcp_client_remote_addr_n(struct mgcp_client *mgcp) +{ + return mgcp->remote_addr; +} + +struct mgcp_response_pending * mgcp_client_pending_add( + struct mgcp_client *mgcp, + mgcp_trans_id_t trans_id, + mgcp_response_cb_t response_cb, + void *priv) +{ + struct mgcp_response_pending *pending; + + pending = talloc_zero(mgcp, struct mgcp_response_pending); + pending->trans_id = trans_id; + pending->response_cb = response_cb; + pending->priv = priv; + llist_add_tail(&pending->entry, &mgcp->responses_pending); + + return pending; +} + +/* Send the MGCP message in msg to the MGCP GW and handle a response with + * response_cb. NOTE: the response_cb still needs to call + * mgcp_response_parse_params(response) to get the parsed parameters -- to + * potentially save some CPU cycles, only the head line has been parsed when + * the response_cb is invoked. */ +int mgcp_client_tx(struct mgcp_client *mgcp, struct msgb *msg, + mgcp_response_cb_t response_cb, void *priv) +{ + struct mgcp_response_pending *pending; + mgcp_trans_id_t trans_id; + int rc; + + trans_id = msg->cb[MSGB_CB_MGCP_TRANS_ID]; + if (!trans_id) { + LOGP(DLMGCP, LOGL_ERROR, + "Unset transaction id in mgcp send request\n"); + talloc_free(msg); + return -EINVAL; + } + + pending = mgcp_client_pending_add(mgcp, trans_id, response_cb, priv); + + if (msgb_l2len(msg) > 4096) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot send, MGCP message too large: %u\n", + msgb_l2len(msg)); + msgb_free(msg); + rc = -EINVAL; + goto mgcp_tx_error; + } + + rc = osmo_wqueue_enqueue(&mgcp->wq, msg); + if (rc) { + LOGP(DLMGCP, LOGL_FATAL, "Could not queue message to MGCP GW\n"); + msgb_free(msg); + goto mgcp_tx_error; + } else + LOGP(DLMGCP, LOGL_INFO, "Queued %u bytes for MGCP GW\n", + msgb_l2len(msg)); + return 0; + +mgcp_tx_error: + /* Pass NULL to response cb to indicate an error */ + mgcp_client_handle_response(mgcp, pending, NULL); + return -1; +} + +static struct msgb *mgcp_msg_from_buf(mgcp_trans_id_t trans_id, + const char *buf, int len) +{ + struct msgb *msg; + + if (len > (4096 - 128)) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot send to MGCP GW:" + " message too large: %d\n", len); + return NULL; + } + + msg = msgb_alloc_headroom(4096, 128, "MGCP tx"); + OSMO_ASSERT(msg); + + char *dst = (char*)msgb_put(msg, len); + memcpy(dst, buf, len); + msg->l2h = msg->data; + msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id; + + return msg; +} + +static struct msgb *mgcp_msg_from_str(mgcp_trans_id_t trans_id, + const char *fmt, ...) +{ + static char compose[4096 - 128]; + va_list ap; + int len; + OSMO_ASSERT(fmt); + + va_start(ap, fmt); + len = vsnprintf(compose, sizeof(compose), fmt, ap); + va_end(ap); + if (len >= sizeof(compose)) { + LOGP(DLMGCP, LOGL_ERROR, + "Message too large: trans_id=%u len=%d\n", + trans_id, len); + return NULL; + } + if (len < 1) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to compose message: trans_id=%u len=%d\n", + trans_id, len); + return NULL; + } + return mgcp_msg_from_buf(trans_id, compose, len); +} + +static mgcp_trans_id_t mgcp_client_next_trans_id(struct mgcp_client *mgcp) +{ + /* avoid zero trans_id to distinguish from unset trans_id */ + if (!mgcp->next_trans_id) + mgcp->next_trans_id ++; + return mgcp->next_trans_id ++; +} + +struct msgb *mgcp_msg_crcx(struct mgcp_client *mgcp, + uint16_t rtp_endpoint, unsigned int call_id, + enum mgcp_connection_mode mode) +{ + mgcp_trans_id_t trans_id = mgcp_client_next_trans_id(mgcp); + return mgcp_msg_from_str(trans_id, + "CRCX %u %x@mgw MGCP 1.0\r\n" + "C: %x\r\n" + "L: p:20, a:AMR, nt:IN\r\n" + "M: %s\r\n" + , + trans_id, + rtp_endpoint, + call_id, + mgcp_client_cmode_name(mode)); +} + +struct msgb *mgcp_msg_mdcx(struct mgcp_client *mgcp, + uint16_t rtp_endpoint, const char *rtp_conn_addr, + uint16_t rtp_port, enum mgcp_connection_mode mode) + +{ + mgcp_trans_id_t trans_id = mgcp_client_next_trans_id(mgcp); + return mgcp_msg_from_str(trans_id, + "MDCX %u %x@mgw MGCP 1.0\r\n" + "M: %s\r\n" + "\r\n" + "c=IN IP4 %s\r\n" + "m=audio %u RTP/AVP 255\r\n" + , + trans_id, + rtp_endpoint, + mgcp_client_cmode_name(mode), + rtp_conn_addr, + rtp_port); +} + +struct msgb *mgcp_msg_dlcx(struct mgcp_client *mgcp, uint16_t rtp_endpoint, + unsigned int call_id) +{ + mgcp_trans_id_t trans_id = mgcp_client_next_trans_id(mgcp); + return mgcp_msg_from_str(trans_id, + "DLCX %u %x@mgw MGCP 1.0\r\n" + "C: %x\r\n", trans_id, rtp_endpoint, call_id); +} + +#define MGCP_CRCX_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT | \ + MGCP_MSG_PRESENCE_CALL_ID | \ + MGCP_MSG_PRESENCE_CONN_ID | \ + MGCP_MSG_PRESENCE_CONN_MODE) +#define MGCP_MDCX_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT | \ + MGCP_MSG_PRESENCE_CONN_ID) +#define MGCP_DLCX_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT) +#define MGCP_AUEP_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT) +#define MGCP_RSIP_MANDATORY 0 /* none */ + +struct msgb *mgcp_msg_gen(struct mgcp_client *mgcp, struct mgcp_msg *mgcp_msg) +{ + mgcp_trans_id_t trans_id = mgcp_client_next_trans_id(mgcp); + uint32_t mandatory_mask; + struct msgb *msg = msgb_alloc_headroom(4096, 128, "MGCP tx"); + int rc = 0; + + msg->l2h = msg->data; + msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id; + + /* Add command verb */ + switch (mgcp_msg->verb) { + case MGCP_VERB_CRCX: + mandatory_mask = MGCP_CRCX_MANDATORY; + rc += msgb_printf(msg, "CRCX %u", trans_id); + break; + case MGCP_VERB_MDCX: + mandatory_mask = MGCP_MDCX_MANDATORY; + rc += msgb_printf(msg, "MDCX %u", trans_id); + break; + case MGCP_VERB_DLCX: + mandatory_mask = MGCP_DLCX_MANDATORY; + rc += msgb_printf(msg, "DLCX %u", trans_id); + break; + case MGCP_VERB_AUEP: + mandatory_mask = MGCP_AUEP_MANDATORY; + rc += msgb_printf(msg, "AUEP %u", trans_id); + break; + case MGCP_VERB_RSIP: + mandatory_mask = MGCP_RSIP_MANDATORY; + rc += msgb_printf(msg, "RSIP %u", trans_id); + break; + default: + LOGP(DLMGCP, LOGL_ERROR, + "Invalid command verb, can not generate MGCP message\n"); + msgb_free(msg); + return NULL; + } + + /* Check if mandatory fields are missing */ + if (!((mgcp_msg->presence & mandatory_mask) == mandatory_mask)) { + LOGP(DLMGCP, LOGL_ERROR, + "One or more missing mandatory fields, can not generate MGCP message\n"); + msgb_free(msg); + return NULL; + } + + /* Add endpoint name */ + if (mgcp_msg->presence & MGCP_MSG_PRESENCE_ENDPOINT) + rc += msgb_printf(msg, " %s", mgcp_msg->endpoint); + + /* Add protocol version */ + rc += msgb_printf(msg, " MGCP 1.0\r\n"); + + /* Add call id */ + if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CALL_ID) + rc += msgb_printf(msg, "C: %x\r\n", mgcp_msg->call_id); + + /* Add connection id */ + if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CONN_ID) + rc += msgb_printf(msg, "I: %u\r\n", mgcp_msg->conn_id); + + /* Add local connection options */ + if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CONN_ID + && mgcp_msg->verb == MGCP_VERB_CRCX) + rc += msgb_printf(msg, "L: p:20, a:AMR, nt:IN\r\n"); + + /* Add mode */ + if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CONN_MODE) + rc += + msgb_printf(msg, "M: %s\r\n", + mgcp_client_cmode_name(mgcp_msg->conn_mode)); + + /* Add RTP address and port (SDP) */ + if (mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_IP + && mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_PORT) { + rc += msgb_printf(msg, "\r\n"); + rc += msgb_printf(msg, "c=IN IP4 %s\r\n", mgcp_msg->audio_ip); + rc += + msgb_printf(msg, "m=audio %u RTP/AVP 255\r\n", + mgcp_msg->audio_port); + } + + if (rc != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "message buffer to small, can not generate MGCP message\n"); + msgb_free(msg); + msg = NULL; + } + + return msg; +} + +struct mgcp_client_conf *mgcp_client_conf_actual(struct mgcp_client *mgcp) +{ + return &mgcp->actual; +} + +const struct value_string mgcp_client_connection_mode_strs[] = { + { MGCP_CONN_NONE, "none" }, + { MGCP_CONN_RECV_SEND, "sendrecv" }, + { MGCP_CONN_SEND_ONLY, "sendonly" }, + { MGCP_CONN_RECV_ONLY, "recvonly" }, + { MGCP_CONN_LOOPBACK, "loopback" }, + { 0, NULL } +}; diff --git a/src/libosmo-mgcp-client/mgcp_client_vty.c b/src/libosmo-mgcp-client/mgcp_client_vty.c new file mode 100644 index 0000000..f8129c0 --- /dev/null +++ b/src/libosmo-mgcp-client/mgcp_client_vty.c @@ -0,0 +1,206 @@ +/* MGCP client interface to quagga VTY */ +/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de> + * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c) + * (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2011 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <inttypes.h> +#include <stdlib.h> +#include <talloc.h> + +#include <osmocom/vty/command.h> +#include <osmocom/core/utils.h> + +#include <osmocom/mgcp_client/mgcp_client.h> + +#define MGW_STR "Configure MGCP connection to Media Gateway\n" + +void *global_mgcp_client_ctx = NULL; +struct mgcp_client_conf *global_mgcp_client_conf = NULL; + +DEFUN(cfg_mgw_local_ip, cfg_mgw_local_ip_cmd, + "mgw local-ip A.B.C.D", + MGW_STR "local bind to connect to MGW from\n" + "local bind IP address\n") +{ + if (!global_mgcp_client_conf) + return CMD_ERR_NOTHING_TODO; + OSMO_ASSERT(global_mgcp_client_ctx); + global_mgcp_client_conf->local_addr = + talloc_strdup(global_mgcp_client_ctx, argv[0]); + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_mgw_local_ip, cfg_mgcpgw_local_ip_cmd, + "mgcpgw local-ip A.B.C.D", + MGW_STR "local bind to connect to MGCP gateway with\n" + "local bind IP address\n") + +DEFUN(cfg_mgw_local_port, cfg_mgw_local_port_cmd, + "mgw local-port <0-65535>", + MGW_STR "local port to connect to MGW from\n" + "local bind port\n") +{ + if (!global_mgcp_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcp_client_conf->local_port = atoi(argv[0]); + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_mgw_local_port, cfg_mgcpgw_local_port_cmd, + "mgcpgw local-port <0-65535>", + MGW_STR "local bind to connect to MGCP gateway with\n" + "local bind port\n") + +DEFUN(cfg_mgw_remote_ip, cfg_mgw_remote_ip_cmd, + "mgw remote-ip A.B.C.D", + MGW_STR "remote IP address to reach the MGW at\n" + "remote IP address\n") +{ + if (!global_mgcp_client_conf) + return CMD_ERR_NOTHING_TODO; + OSMO_ASSERT(global_mgcp_client_ctx); + global_mgcp_client_conf->remote_addr = + talloc_strdup(global_mgcp_client_ctx, argv[0]); + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_mgw_remote_ip, cfg_mgcpgw_remote_ip_cmd, + "mgcpgw remote-ip A.B.C.D", + MGW_STR "remote bind to connect to MGCP gateway with\n" + "remote bind IP address\n") + +DEFUN(cfg_mgw_remote_port, cfg_mgw_remote_port_cmd, + "mgw remote-port <0-65535>", + MGW_STR "remote port to reach the MGW at\n" + "remote port\n") +{ + if (!global_mgcp_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcp_client_conf->remote_port = atoi(argv[0]); + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_mgw_remote_port, cfg_mgcpgw_remote_port_cmd, + "mgcpgw remote-port <0-65535>", + MGW_STR "remote bind to connect to MGCP gateway with\n" + "remote bind port\n") + +DEFUN(cfg_mgw_endpoint_range, cfg_mgw_endpoint_range_cmd, + "mgw endpoint-range <1-65534> <1-65534>", + MGW_STR "usable range of endpoint identifiers\n" + "set first usable endpoint identifier\n" + "set last usable endpoint identifier\n") +{ + uint16_t first_endpoint = atoi(argv[0]); + uint16_t last_endpoint = atoi(argv[1]); + + if (last_endpoint < first_endpoint) { + vty_out(vty, "last endpoint must be greater than first endpoint!%s", + VTY_NEWLINE); + return CMD_SUCCESS; + } + + global_mgcp_client_conf->first_endpoint = first_endpoint; + global_mgcp_client_conf->last_endpoint = last_endpoint; + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_mgw_endpoint_range, cfg_mgcpgw_endpoint_range_cmd, + "mgcpgw endpoint-range <1-65534> <1-65534>", + MGW_STR "usable range of endpoint identifiers\n" + "set first useable endpoint identifier\n" + "set the last useable endpoint identifier\n") + +#define BTS_START_STR "First UDP port allocated for the BTS side\n" +#define UDP_PORT_STR "UDP Port number\n" +DEFUN(cfg_mgw_rtp_bts_base_port, + cfg_mgw_rtp_bts_base_port_cmd, + "mgw bts-base <0-65534>", + MGW_STR + BTS_START_STR + UDP_PORT_STR) +{ + global_mgcp_client_conf->bts_base = atoi(argv[0]); + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_mgw_rtp_bts_base_port, + cfg_mgcpgw_rtp_bts_base_port_cmd, + "mgcpgw bts-base <0-65534>", + MGW_STR + BTS_START_STR + UDP_PORT_STR) + +int mgcp_client_config_write(struct vty *vty, const char *indent) +{ + const char *addr; + int port; + uint16_t first_endpoint; + uint16_t last_endpoint; + uint16_t bts_base; + + addr = global_mgcp_client_conf->local_addr; + if (addr) + vty_out(vty, "%smgw local-ip %s%s", indent, addr, + VTY_NEWLINE); + port = global_mgcp_client_conf->local_port; + if (port >= 0) + vty_out(vty, "%smgw local-port %u%s", indent, + (uint16_t)port, VTY_NEWLINE); + + addr = global_mgcp_client_conf->remote_addr; + if (addr) + vty_out(vty, "%smgw remote-ip %s%s", indent, addr, + VTY_NEWLINE); + port = global_mgcp_client_conf->remote_port; + if (port >= 0) + vty_out(vty, "%smgw remote-port %u%s", indent, + (uint16_t)port, VTY_NEWLINE); + + first_endpoint = global_mgcp_client_conf->first_endpoint; + last_endpoint = global_mgcp_client_conf->last_endpoint; + if (last_endpoint != 0) { + vty_out(vty, "%smgw endpoint-range %u %u%s", indent, + first_endpoint, last_endpoint, VTY_NEWLINE); + } + + bts_base = global_mgcp_client_conf->bts_base; + if (bts_base) { + vty_out(vty, "%smgw bts-base %u%s", indent, + bts_base, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +void mgcp_client_vty_init(void *talloc_ctx, int node, struct mgcp_client_conf *conf) +{ + global_mgcp_client_ctx = talloc_ctx; + global_mgcp_client_conf = conf; + + install_element(node, &cfg_mgw_local_ip_cmd); + install_element(node, &cfg_mgw_local_port_cmd); + install_element(node, &cfg_mgw_remote_ip_cmd); + install_element(node, &cfg_mgw_remote_port_cmd); + install_element(node, &cfg_mgw_endpoint_range_cmd); + install_element(node, &cfg_mgw_rtp_bts_base_port_cmd); + + /* deprecated 'mgcpgw' commands */ + install_element(node, &cfg_mgcpgw_local_ip_cmd); + install_element(node, &cfg_mgcpgw_local_port_cmd); + install_element(node, &cfg_mgcpgw_remote_ip_cmd); + install_element(node, &cfg_mgcpgw_remote_port_cmd); + install_element(node, &cfg_mgcpgw_endpoint_range_cmd); + install_element(node, &cfg_mgcpgw_rtp_bts_base_port_cmd); +} diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am new file mode 100644 index 0000000..fce0e1b --- /dev/null +++ b/src/libosmo-mgcp/Makefile.am @@ -0,0 +1,46 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +# This is not at all related to the release version, but a range of supported +# API versions. Read TODO_RELEASE in the source tree's root! +MGCP_LIBVERSION=1:0:0 + +lib_LTLIBRARIES = \ + libosmo-mgcp.la \ + $(NULL) + +noinst_HEADERS = \ + g711common.h \ + $(NULL) + +libosmo_mgcp_la_SOURCES = \ + mgcp_protocol.c \ + mgcp_network.c \ + mgcp_vty.c \ + mgcp_osmux.c \ + mgcp_sdp.c \ + mgcp_msg.c \ + mgcp_conn.c \ + mgcp_stat.c \ + mgcp_ep.c \ + $(NULL) + +libosmo_mgcp_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(MGCP_LIBVERSION) diff --git a/src/libosmo-mgcp/g711common.h b/src/libosmo-mgcp/g711common.h new file mode 100644 index 0000000..cb35fc6 --- /dev/null +++ b/src/libosmo-mgcp/g711common.h @@ -0,0 +1,187 @@ +/* + * PCM - A-Law conversion + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * Wrapper for linphone Codec class by Simon Morlat <simon.morlat@linphone.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; /*7 = 4 + 3*/ + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +/* + * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + * G711 is designed for 13 bits input signal, this function add extra shifting to take this into account. + */ + +static inline unsigned char s16_to_alaw(int pcm_val) +{ + int mask; + int seg; + unsigned char aval; + + if (pcm_val >= 0) { + mask = 0xD5; + } else { + mask = 0x55; + pcm_val = -pcm_val; + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + } + + if (pcm_val < 256) /*256 = 32 << 3*/ + aval = pcm_val >> 4; /*4 = 1 + 3*/ + else { + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + } + return aval ^ mask; +} + +/* + * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM + * + */ +static inline int alaw_to_s16(unsigned char a_val) +{ + int t; + int seg; + + a_val ^= 0x55; + t = a_val & 0x7f; + if (t < 16) + t = (t << 4) + 8; + else { + seg = (t >> 4) & 0x07; + t = ((t & 0x0f) << 4) + 0x108; + t <<= seg -1; + } + return ((a_val & 0x80) ? t : -t); +} +/* + * s16_to_ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + +static inline unsigned char s16_to_ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + if (pcm_val < 0) { + pcm_val = 0x84 - pcm_val; + mask = 0x7f; + } else { + pcm_val += 0x84; + mask = 0xff; + } + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + return uval ^ mask; +} + +/* + * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +static inline int ulaw_to_s16(unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & 0x0f) << 3) + 0x84; + t <<= (u_val & 0x70) >> 4; + + return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84)); +} diff --git a/src/libosmo-mgcp/mgcp_conn.c b/src/libosmo-mgcp/mgcp_conn.c new file mode 100644 index 0000000..e0eec63 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_conn.c @@ -0,0 +1,287 @@ +/* Message connection list handling */ + +/* + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/mgcp/mgcp_conn.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/mgcp_common.h> +#include <osmocom/mgcp/mgcp_ep.h> + +/* Reset codec state and free memory */ +static void mgcp_rtp_codec_reset(struct mgcp_rtp_codec *codec) +{ + codec->payload_type = -1; + codec->subtype_name = NULL; + codec->audio_name = NULL; + codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; + codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; + codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; + codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; + + /* see also mgcp_sdp.c, mgcp_set_audio_info() */ + talloc_free(codec->subtype_name); + talloc_free(codec->audio_name); +} + +/* Reset states, free memory, set defaults and reset codec state */ +static void mgcp_rtp_conn_reset(struct mgcp_conn_rtp *conn) +{ + struct mgcp_rtp_end *end = &conn->end; + + conn->type = MGCP_RTP_DEFAULT; + conn->osmux.allocated_cid = -1; + + end->rtp.fd = -1; + end->rtcp.fd = -1; + end->local_port = 0; + end->packets_rx = 0; + end->octets_rx = 0; + end->packets_tx = 0; + end->octets_tx = 0; + end->dropped_packets = 0; + end->rtp_port = end->rtcp_port = 0; + talloc_free(end->fmtp_extra); + end->fmtp_extra = NULL; + + /* Set default values */ + end->frames_per_packet = 0; /* unknown */ + end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS; + end->output_enabled = 0; + + mgcp_rtp_codec_reset(&end->codec); + mgcp_rtp_codec_reset(&end->alt_codec); +} + +/*! allocate a new connection list entry. + * \param[in] ctx talloc context + * \param[in] endp associated endpoint + * \param[in] id identification number of the connection + * \param[in] type connection type (e.g. MGCP_CONN_TYPE_RTP) + * \returns pointer to allocated connection, NULL on error */ +struct mgcp_conn *mgcp_conn_alloc(void *ctx, struct mgcp_endpoint *endp, + uint32_t id, enum mgcp_conn_type type, + char *name) +{ + struct mgcp_conn *conn; + OSMO_ASSERT(endp); + OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL); + OSMO_ASSERT(strlen(name) < sizeof(conn->name)); + + /* Do not allow more then two connections */ + if (llist_count(&endp->conns) >= endp->type->max_conns) + return NULL; + + /* Prevent duplicate connection IDs */ + if (mgcp_conn_get(endp, id)) + return NULL; + + /* Create new connection and add it to the list */ + conn = talloc_zero(ctx, struct mgcp_conn); + if (!conn) + return NULL; + conn->endp = endp; + conn->type = type; + conn->mode = MGCP_CONN_NONE; + conn->mode_orig = MGCP_CONN_NONE; + conn->id = id; + conn->u.rtp.conn = conn; + strcpy(conn->name, name); + + switch (type) { + case MGCP_CONN_TYPE_RTP: + mgcp_rtp_conn_reset(&conn->u.rtp); + break; + default: + /* NOTE: This should never be called with an + * invalid type, its up to the programmer + * to ensure propery types */ + OSMO_ASSERT(false); + } + + llist_add(&conn->entry, &endp->conns); + + return conn; +} + +/*! find a connection by its ID. + * \param[in] endp associated endpoint + * \param[in] id identification number of the connection + * \returns pointer to allocated connection, NULL if not found */ +struct mgcp_conn *mgcp_conn_get(struct mgcp_endpoint *endp, uint32_t id) +{ + OSMO_ASSERT(endp); + OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL); + + struct mgcp_conn *conn; + + llist_for_each_entry(conn, &endp->conns, entry) { + if (conn->id == id) + return conn; + } + + return NULL; +} + +/*! find an RTP connection by its ID. + * \param[in] endp associated endpoint + * \param[in] id identification number of the connection + * \returns pointer to allocated connection, NULL if not found */ +struct mgcp_conn_rtp *mgcp_conn_get_rtp(struct mgcp_endpoint *endp, uint32_t id) +{ + OSMO_ASSERT(endp); + OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL); + + struct mgcp_conn *conn; + + conn = mgcp_conn_get(endp, id); + if (!conn) + return NULL; + + if (conn->type == MGCP_CONN_TYPE_RTP) + return &conn->u.rtp; + + return NULL; +} + +/*! free a connection by its ID. + * \param[in] endp associated endpoint + * \param[in] id identification number of the connection */ +void mgcp_conn_free(struct mgcp_endpoint *endp, uint32_t id) +{ + OSMO_ASSERT(endp); + OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL); + + struct mgcp_conn *conn; + + conn = mgcp_conn_get(endp, id); + if (!conn) + return; + + switch (conn->type) { + case MGCP_CONN_TYPE_RTP: + osmux_disable_conn(&conn->u.rtp); + osmux_release_cid(&conn->u.rtp); + mgcp_free_rtp_port(&conn->u.rtp.end); + break; + default: + /* NOTE: This should never be called with an + * invalid type, its up to the programmer + * to ensure propery types */ + OSMO_ASSERT(false); + } + + llist_del(&conn->entry); + talloc_free(conn); +} + +/*! free oldest connection in the list. + * \param[in] endp associated endpoint */ +void mgcp_conn_free_oldest(struct mgcp_endpoint *endp) +{ + OSMO_ASSERT(endp); + OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL); + + struct mgcp_conn *conn; + + if (llist_empty(&endp->conns)) + return; + + conn = llist_last_entry(&endp->conns, struct mgcp_conn, entry); + if (!conn) + return; + + mgcp_conn_free(endp, conn->id); +} + +/*! free all connections at once. + * \param[in] endp associated endpoint */ +void mgcp_conn_free_all(struct mgcp_endpoint *endp) +{ + OSMO_ASSERT(endp); + OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL); + + struct mgcp_conn *conn; + struct mgcp_conn *conn_tmp; + + /* Drop all items in the list */ + llist_for_each_entry_safe(conn, conn_tmp, &endp->conns, entry) { + mgcp_conn_free(endp, conn->id); + } + + return; +} + +/*! dump basic connection information to human readble string. + * \param[in] conn to dump + * \returns human readble string */ +char *mgcp_conn_dump(struct mgcp_conn *conn) +{ + static char str[256]; + + if (!conn) { + snprintf(str, sizeof(str), "(null connection)"); + return str; + } + + switch (conn->type) { + case MGCP_CONN_TYPE_RTP: + /* Dump RTP connection */ + snprintf(str, sizeof(str), "(%s/rtp, id:%u, ip:%s, " + "rtp:%u rtcp:%u)", + conn->name, + conn->id, + inet_ntoa(conn->u.rtp.end.addr), + ntohs(conn->u.rtp.end.rtp_port), + ntohs(conn->u.rtp.end.rtcp_port)); + break; + + default: + /* Should not happen, we should be able to dump + * every possible connection type. */ + snprintf(str, sizeof(str), "(unknown connection type)"); + break; + } + + return str; +} + +/*! find destination connection on a specific endpoint. + * \param[in] conn to search a destination for + * \returns destination connection, NULL on failure */ +struct mgcp_conn *mgcp_find_dst_conn(struct mgcp_conn *conn) +{ + struct mgcp_endpoint *endp; + struct mgcp_conn *partner_conn; + endp = conn->endp; + + /*! NOTE: This simply works by grabbing the first connection that is + * not the supplied connection, which is suitable for endpoints that + * do not serve more than two connections. */ + + llist_for_each_entry(partner_conn, &endp->conns, entry) { + if (conn != partner_conn) { + return partner_conn; + } + } + + return NULL; +} diff --git a/src/libosmo-mgcp/mgcp_ep.c b/src/libosmo-mgcp/mgcp_ep.c new file mode 100644 index 0000000..72ca691 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_ep.c @@ -0,0 +1,32 @@ +/* Endpoint types */ + +/* + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/mgcp/mgcp_ep.h> +#include <osmocom/mgcp/mgcp_internal.h> + +/* Endpoint typeset definition */ +const struct mgcp_endpoint_typeset ep_typeset = { + /* Specify endpoint properties for RTP endpoint */ + .rtp.max_conns = 2, + .rtp.dispatch_rtp_cb = mgcp_dispatch_rtp_bridge_cb +}; diff --git a/src/libosmo-mgcp/mgcp_msg.c b/src/libosmo-mgcp/mgcp_msg.c new file mode 100644 index 0000000..d686bca --- /dev/null +++ b/src/libosmo-mgcp/mgcp_msg.c @@ -0,0 +1,405 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* Message parser/generator utilities */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <limits.h> + +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/mgcp_common.h> +#include <osmocom/mgcp/mgcp_msg.h> +#include <osmocom/mgcp/mgcp_conn.h> + +/*! Display an mgcp message on the log output. + * \param[in] message mgcp message string + * \param[in] len message mgcp message string length + * \param[in] preamble string to display in logtext in front of each line */ +void mgcp_disp_msg(unsigned char *message, unsigned int len, char *preamble) +{ + unsigned char line[80]; + unsigned char *ptr; + unsigned int consumed = 0; + unsigned int consumed_line = 0; + unsigned int line_count = 0; + + if (!log_check_level(DLMGCP, LOGL_DEBUG)) + return; + + while (1) { + memset(line, 0, sizeof(line)); + ptr = line; + consumed_line = 0; + do { + if (*message != '\n' && *message != '\r') { + *ptr = *message; + ptr++; + } + message++; + consumed++; + consumed_line++; + } while (*message != '\n' && consumed < len + && consumed_line < sizeof(line)); + + if (strlen((const char *)line)) { + LOGP(DLMGCP, LOGL_DEBUG, "%s: line #%02u: %s\n", + preamble, line_count, line); + line_count++; + } + + if (consumed >= len) + return; + } +} + +/*! Parse connection mode. + * \param[in] mode as string (recvonly, sendrecv, sendonly or loopback) + * \param[in] endp pointer to endpoint (only used for log output) + * \param[out] associated connection to be modified accordingly + * \returns 0 on success, -1 on error */ +int mgcp_parse_conn_mode(const char *mode, struct mgcp_endpoint *endp, + struct mgcp_conn *conn) +{ + int ret = 0; + + if (!mode) { + LOGP(DLMGCP, LOGL_ERROR, + "endpoint:%x missing connection mode\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + if (!conn) + return -1; + if (!endp) + return -1; + + if (strcmp(mode, "recvonly") == 0) + conn->mode = MGCP_CONN_RECV_ONLY; + else if (strcmp(mode, "sendrecv") == 0) + conn->mode = MGCP_CONN_RECV_SEND; + else if (strcmp(mode, "sendonly") == 0) + conn->mode = MGCP_CONN_SEND_ONLY; + else if (strcmp(mode, "loopback") == 0) + conn->mode = MGCP_CONN_LOOPBACK; + else { + LOGP(DLMGCP, LOGL_ERROR, + "endpoint:%x unknown connection mode: '%s'\n", + ENDPOINT_NUMBER(endp), mode); + ret = -1; + } + + /* Special handling für RTP connections */ + if (conn->type == MGCP_CONN_TYPE_RTP) { + conn->u.rtp.end.output_enabled = + conn->mode & MGCP_CONN_SEND_ONLY ? 1 : 0; + } + + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint:%x conn:%s\n", + ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn)); + + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint:%x connection mode '%s' %d\n", + ENDPOINT_NUMBER(endp), mode, conn->mode); + + /* Special handling für RTP connections */ + if (conn->type == MGCP_CONN_TYPE_RTP) { + LOGP(DLMGCP, LOGL_DEBUG, "endpoint:%x output_enabled %d\n", + ENDPOINT_NUMBER(endp), conn->u.rtp.end.output_enabled); + } + + /* The VTY might change the connection mode at any time, so we have + * to hold a copy of the original connection mode */ + conn->mode_orig = conn->mode; + + return ret; +} + +/* We have a null terminated string with the endpoint name here. We only + * support two kinds. Simple ones as seen on the BSC level and the ones + * seen on the trunk side. (helper function for find_endpoint()) */ +static struct mgcp_endpoint *find_e1_endpoint(struct mgcp_config *cfg, + const char *mgcp) +{ + char *rest = NULL; + struct mgcp_trunk_config *tcfg; + int trunk, endp; + + trunk = strtoul(mgcp + 6, &rest, 10); + if (rest == NULL || rest[0] != '/' || trunk < 1) { + LOGP(DLMGCP, LOGL_ERROR, "Wrong trunk name '%s'\n", mgcp); + return NULL; + } + + endp = strtoul(rest + 1, &rest, 10); + if (rest == NULL || rest[0] != '@') { + LOGP(DLMGCP, LOGL_ERROR, "Wrong endpoint name '%s'\n", mgcp); + return NULL; + } + + /* signalling is on timeslot 1 */ + if (endp == 1) + return NULL; + + tcfg = mgcp_trunk_num(cfg, trunk); + if (!tcfg) { + LOGP(DLMGCP, LOGL_ERROR, "The trunk %d is not declared.\n", + trunk); + return NULL; + } + + if (!tcfg->endpoints) { + LOGP(DLMGCP, LOGL_ERROR, + "Endpoints of trunk %d not allocated.\n", trunk); + return NULL; + } + + if (endp < 1 || endp >= tcfg->number_endpoints) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to find endpoint '%s'\n", + mgcp); + return NULL; + } + + return &tcfg->endpoints[endp]; +} + +/* Search the endpoint pool for the endpoint that had been selected via the + * MGCP message (helper function for mgcp_analyze_header()) */ +static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg, + const char *mgcp) +{ + char *endptr = NULL; + unsigned int gw = INT_MAX; + + if (strncmp(mgcp, "ds/e1", 5) == 0) + return find_e1_endpoint(cfg, mgcp); + + gw = strtoul(mgcp, &endptr, 16); + if (gw > 0 && gw < cfg->trunk.number_endpoints && endptr[0] == '@') + return &cfg->trunk.endpoints[gw]; + + LOGP(DLMGCP, LOGL_ERROR, "Not able to find the endpoint: '%s'\n", mgcp); + return NULL; +} + +/*! Analyze and parse the the hader of an MGCP messeage string. + * \param[out] pdata caller provided memory to store the parsing results + * \param[in] data mgcp message string + * \returns when the status line was complete and transaction_id and + * endp out parameters are set, -1 on error */ +int mgcp_parse_header(struct mgcp_parse_data *pdata, char *data) +{ + int i = 0; + char *elem, *save = NULL; + + /*! This function will parse the header part of the received + * MGCP message. The parsing results are stored in pdata. + * The function will also automatically search the pool with + * available endpoints in order to find an endpoint that matches + * the endpoint string in in the header */ + + OSMO_ASSERT(data); + pdata->trans = "000000"; + + for (elem = strtok_r(data, " ", &save); elem; + elem = strtok_r(NULL, " ", &save)) { + switch (i) { + case 0: + pdata->trans = elem; + break; + case 1: + pdata->endp = find_endpoint(pdata->cfg, elem); + if (!pdata->endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Unable to find Endpoint `%s'\n", elem); + return -1; + } + break; + case 2: + if (strcmp("MGCP", elem)) { + LOGP(DLMGCP, LOGL_ERROR, + "MGCP header parsing error\n"); + return -1; + } + break; + case 3: + if (strcmp("1.0", elem)) { + LOGP(DLMGCP, LOGL_ERROR, "MGCP version `%s' " + "not supported\n", elem); + return -1; + } + break; + } + i++; + } + + if (i != 4) { + LOGP(DLMGCP, LOGL_ERROR, "MGCP status line too short.\n"); + pdata->trans = "000000"; + pdata->endp = NULL; + return -1; + } + + return 0; +} + +/*! Extract OSMUX CID from an MGCP parameter line (string). + * \param[in] line single parameter line from the MGCP message + * \returns OSMUX CID, -1 on error */ +int mgcp_parse_osmux_cid(const char *line) +{ + int osmux_cid; + + if (sscanf(line + 2, "Osmux: %u", &osmux_cid) != 1) + return -1; + + if (osmux_cid > OSMUX_CID_MAX) { + LOGP(DLMGCP, LOGL_ERROR, "Osmux ID too large: %u > %u\n", + osmux_cid, OSMUX_CID_MAX); + return -1; + } + LOGP(DLMGCP, LOGL_DEBUG, "bsc-nat offered Osmux CID %u\n", osmux_cid); + + return osmux_cid; +} + +/*! Check MGCP parameter line (string) for plausibility. + * \param[in] endp pointer to endpoint (only used for log output) + * \param[in] line single parameter line from the MGCP message + * \returns 1 when line seems plausible, 0 on error */ +int mgcp_check_param(const struct mgcp_endpoint *endp, const char *line) +{ + const size_t line_len = strlen(line); + if (line[0] != '\0' && line_len < 2) { + LOGP(DLMGCP, LOGL_ERROR, + "Wrong MGCP option format: '%s' on 0x%x\n", + line, ENDPOINT_NUMBER(endp)); + return 0; + } + + /* FIXME: A couple more checks wouldn't hurt... */ + + return 1; +} + +/*! Check if the specified callid seems plausible. + * \param[in] endp pointer to endpoint + * \param{in] callid to verify + * \returns 1 when callid seems plausible, 0 on error */ +int mgcp_verify_call_id(struct mgcp_endpoint *endp, const char *callid) +{ + /*! This function compares the supplied callid with the called that is + * stored in the endpoint structure. */ + + if (!endp) + return -1; + if (!callid) + return -1; + if (!endp->callid) + return -1; + + if (strcmp(endp->callid, callid) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "endpoint:%x CallIDs does not match '%s' != '%s'\n", + ENDPOINT_NUMBER(endp), endp->callid, callid); + return -1; + } + + return 0; +} + +/*! Check if the specified connection id seems plausible. + * \param[in] endp pointer to endpoint + * \param{in] connection id to verify + * \returns 1 when connection id seems plausible, 0 on error */ +int mgcp_verify_ci(struct mgcp_endpoint *endp, const char *ci) +{ + uint32_t id; + + if (!endp) + return -1; + + id = strtoul(ci, NULL, 10); + + if (mgcp_conn_get(endp, id)) + return 0; + + LOGP(DLMGCP, LOGL_ERROR, + "endpoint:%x No connection found under ConnectionIdentifier %u\n", + ENDPOINT_NUMBER(endp), id); + + return -1; +} + +/*! Extract individual lines from MCGP message. + * \param[in] str MGCP message string, consisting of multiple lines + * \param{in] saveptr pointer to next line in str + * \returns line, NULL when done */ +char *mgcp_strline(char *str, char **saveptr) +{ + char *result; + + /*! The function must be called with *str set to the input string + * for the first line. After that saveptr will be initalized. + * all consecutive lines are extracted by calling the function + * with str set to NULL. When done, the function will return NULL + * to indicate that all lines have been parsed. */ + + if (str) + *saveptr = str; + + result = *saveptr; + + if (*saveptr != NULL) { + *saveptr = strpbrk(*saveptr, "\r\n"); + + if (*saveptr != NULL) { + char *eos = *saveptr; + + if ((*saveptr)[0] == '\r' && (*saveptr)[1] == '\n') + (*saveptr)++; + (*saveptr)++; + if ((*saveptr)[0] == '\0') + *saveptr = NULL; + + *eos = '\0'; + } + } + + return result; +} + +/*! Parse CI from a given string. + * \param[out] caller provided memory to store the result + * \param{in] string containing the connection id + * \returns 0 on success, -1 on error */ +int mgcp_parse_ci(uint32_t *conn_id, const char *ci) +{ + + OSMO_ASSERT(conn_id); + + if (!ci) + return -1; + + *conn_id = strtoul(ci, NULL, 10); + + return 0; +} diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c new file mode 100644 index 0000000..d51b829 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_network.c @@ -0,0 +1,1264 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <limits.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/netif/rtp.h> +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/mgcp/mgcp_common.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/mgcp_stat.h> +#include <osmocom/mgcp/osmux.h> +#include <osmocom/mgcp/mgcp_conn.h> +#include <osmocom/mgcp/mgcp_ep.h> +#include <osmocom/mgcp/debug.h> + +#define RTP_SEQ_MOD (1 << 16) +#define RTP_MAX_DROPOUT 3000 +#define RTP_MAX_MISORDER 100 +#define RTP_BUF_SIZE 4096 + +enum { + MGCP_PROTO_RTP, + MGCP_PROTO_RTCP, +}; + +/*! Determine the local rtp bind IP-address. + * \param[out] addr caller provided memory to store the resulting IP-Address + * \param[in] endp mgcp endpoint, that holds a copy of the VTY parameters + * + * The local bind IP-address is automatically selected by probing the + * IP-Address of the interface that is pointing towards the remote IP-Address, + * if no remote IP-Address is known yet, the statically configured + * IP-Addresses are used as fallback. */ +void mgcp_get_local_addr(char *addr, struct mgcp_conn_rtp *conn) +{ + + struct mgcp_endpoint *endp; + int rc; + endp = conn->conn->endp; + + /* Try probing the local IP-Address */ + if (endp->cfg->net_ports.bind_addr_probe && conn->end.addr.s_addr != 0) { + rc = osmo_sock_local_ip(addr, inet_ntoa(conn->end.addr)); + if (rc < 0) + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x CI:%i local interface auto detection failed, using configured addresses...\n", + ENDPOINT_NUMBER(endp), conn->conn->id); + else { + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x CI:%i selected local rtp bind ip %s by probing using remote ip %s\n", + ENDPOINT_NUMBER(endp), conn->conn->id, addr, + inet_ntoa(conn->end.addr)); + return; + } + } + + /* Select from preconfigured IP-Addresses */ + if (endp->cfg->net_ports.bind_addr) { + /* Check there is a bind IP for the RTP traffic configured, + * if so, use that IP-Address */ + strncpy(addr, endp->cfg->net_ports.bind_addr, INET_ADDRSTRLEN); + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x CI:%i using configured rtp bind ip as local bind ip %s\n", + ENDPOINT_NUMBER(endp), conn->conn->id, addr); + } else { + /* No specific bind IP is configured for the RTP traffic, so + * assume the IP where we listen for incoming MGCP messages + * as bind IP */ + strncpy(addr, endp->cfg->source_addr, INET_ADDRSTRLEN); + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x CI:%i using mgcp bind ip as local rtp bind ip: %s\n", + ENDPOINT_NUMBER(endp), conn->conn->id, addr); + } +} + +/* This does not need to be a precision timestamp and + * is allowed to wrap quite fast. The returned value is + * 1/codec_rate seconds. */ +static uint32_t get_current_ts(unsigned codec_rate) +{ + struct timespec tp; + uint64_t ret; + + if (!codec_rate) + return 0; + + memset(&tp, 0, sizeof(tp)); + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) + LOGP(DRTP, LOGL_NOTICE, "Getting the clock failed.\n"); + + /* convert it to 1/unit seconds */ + ret = tp.tv_sec; + ret *= codec_rate; + ret += (int64_t) tp.tv_nsec * codec_rate / 1000 / 1000 / 1000; + + return ret; +} + +/*! send udp packet. + * \param[in] fd associated file descriptor + * \param[in] addr destination ip-address + * \param[in] port destination UDP port + * \param[in] buf buffer that holds the data to be send + * \param[in] len length of the data to be sent + * \returns bytes sent, -1 on error */ +int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len) +{ + struct sockaddr_in out; + + LOGP(DRTP, LOGL_DEBUG, + "sending %i bytes length packet to %s:%u ...\n", + len, inet_ntoa(*addr), ntohs(port)); + + out.sin_family = AF_INET; + out.sin_port = port; + memcpy(&out.sin_addr, addr, sizeof(*addr)); + + return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out)); +} + +/*! send RTP dummy packet (to keep NAT connection open). + * \param[in] endp mcgp endpoint that holds the RTP connection + * \param[in] conn associated RTP connection + * \returns bytes sent, -1 on error */ +int mgcp_send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) +{ + static char buf[] = { MGCP_DUMMY_LOAD }; + int rc; + int was_rtcp = 0; + + OSMO_ASSERT(endp); + OSMO_ASSERT(conn); + + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x sending dummy packet...\n", ENDPOINT_NUMBER(endp)); + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x conn:%s\n", + ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn)); + + rc = mgcp_udp_send(conn->end.rtp.fd, &conn->end.addr, + conn->end.rtp_port, buf, 1); + + if (rc == -1) + goto failed; + + if (endp->tcfg->omit_rtcp) + return rc; + + was_rtcp = 1; + rc = mgcp_udp_send(conn->end.rtcp.fd, &conn->end.addr, + conn->end.rtcp_port, buf, 1); + + if (rc >= 0) + return rc; + +failed: + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x Failed to send dummy %s packet.\n", + ENDPOINT_NUMBER(endp), was_rtcp ? "RTCP" : "RTP"); + + return -1; +} + +/* Compute timestamp alignment error */ +static int32_t ts_alignment_error(struct mgcp_rtp_stream_state *sstate, + int ptime, uint32_t timestamp) +{ + int32_t timestamp_delta; + + if (ptime == 0) + return 0; + + /* Align according to: T - Tlast = k * Tptime */ + timestamp_delta = timestamp - sstate->last_timestamp; + + return timestamp_delta % ptime; +} + +/* Check timestamp and sequence number for plausibility */ +static int check_rtp_timestamp(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_stream_state *sstate, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + uint16_t seq, uint32_t timestamp, + const char *text, int32_t * tsdelta_out) +{ + int32_t tsdelta; + int32_t timestamp_error; + + /* Not fully intialized, skip */ + if (sstate->last_tsdelta == 0 && timestamp == sstate->last_timestamp) + return 0; + + if (seq == sstate->last_seq) { + if (timestamp != sstate->last_timestamp) { + sstate->err_ts_counter += 1; + LOGP(DRTP, LOGL_ERROR, + "The %s timestamp delta is != 0 but the sequence " + "number %d is the same, " + "TS offset: %d, SeqNo offset: %d " + "on 0x%x SSRC: %u timestamp: %u " + "from %s:%d\n", + text, seq, + state->timestamp_offset, state->seq_offset, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } + return 0; + } + + tsdelta = + (int32_t)(timestamp - sstate->last_timestamp) / + (int16_t)(seq - sstate->last_seq); + + if (tsdelta == 0) { + /* Don't update *tsdelta_out */ + LOGP(DRTP, LOGL_NOTICE, + "The %s timestamp delta is %d " + "on 0x%x SSRC: %u timestamp: %u " + "from %s:%d\n", + text, tsdelta, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + + return 0; + } + + if (sstate->last_tsdelta != tsdelta) { + if (sstate->last_tsdelta) { + LOGP(DRTP, LOGL_INFO, + "The %s timestamp delta changes from %d to %d " + "on 0x%x SSRC: %u timestamp: %u from %s:%d\n", + text, sstate->last_tsdelta, tsdelta, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } + } + + if (tsdelta_out) + *tsdelta_out = tsdelta; + + timestamp_error = + ts_alignment_error(sstate, state->packet_duration, timestamp); + + if (timestamp_error) { + sstate->err_ts_counter += 1; + LOGP(DRTP, LOGL_NOTICE, + "The %s timestamp has an alignment error of %d " + "on 0x%x SSRC: %u " + "SeqNo delta: %d, TS delta: %d, dTS/dSeq: %d " + "from %s:%d. ptime: %d\n", + text, timestamp_error, + ENDPOINT_NUMBER(endp), sstate->ssrc, + (int16_t)(seq - sstate->last_seq), + (int32_t)(timestamp - sstate->last_timestamp), + tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + state->packet_duration); + } + return 1; +} + +/* Set the timestamp offset according to the packet duration. */ +static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + int16_t delta_seq, uint32_t in_timestamp) +{ + int32_t tsdelta = state->packet_duration; + int timestamp_offset; + uint32_t out_timestamp; + + if (tsdelta == 0) { + tsdelta = state->out_stream.last_tsdelta; + if (tsdelta != 0) { + LOGP(DRTP, LOGL_NOTICE, + "A fixed packet duration is not available on 0x%x, " + "using last output timestamp delta instead: %d " + "from %s:%d\n", + ENDPOINT_NUMBER(endp), tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } else { + tsdelta = rtp_end->codec.rate * 20 / 1000; + LOGP(DRTP, LOGL_NOTICE, + "Fixed packet duration and last timestamp delta " + "are not available on 0x%x, " + "using fixed 20ms instead: %d " + "from %s:%d\n", + ENDPOINT_NUMBER(endp), tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } + } + + out_timestamp = state->out_stream.last_timestamp + delta_seq * tsdelta; + timestamp_offset = out_timestamp - in_timestamp; + + if (state->timestamp_offset != timestamp_offset) { + state->timestamp_offset = timestamp_offset; + + LOGP(DRTP, LOGL_NOTICE, + "Timestamp offset change on 0x%x SSRC: %u " + "SeqNo delta: %d, TS offset: %d, " + "from %s:%d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + delta_seq, state->timestamp_offset, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } + + return timestamp_offset; +} + +/* Set the timestamp offset according to the packet duration. */ +static int align_rtp_timestamp_offset(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + uint32_t timestamp) +{ + int ts_error = 0; + int ts_check = 0; + int ptime = state->packet_duration; + + /* Align according to: T + Toffs - Tlast = k * Tptime */ + + ts_error = ts_alignment_error(&state->out_stream, ptime, + timestamp + state->timestamp_offset); + + /* If there is an alignment error, we have to compensate it */ + if (ts_error) { + state->timestamp_offset += ptime - ts_error; + + LOGP(DRTP, LOGL_NOTICE, + "Corrected timestamp alignment error of %d on 0x%x SSRC: %u " + "new TS offset: %d, " + "from %s:%d\n", + ts_error, + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->timestamp_offset, inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); + } + + /* Check we really managed to compensate the timestamp + * offset. There should not be any remaining error, failing + * here would point to a serous problem with the alingnment + * error computation fuction */ + ts_check = ts_alignment_error(&state->out_stream, ptime, + timestamp + state->timestamp_offset); + OSMO_ASSERT(ts_check == 0); + + /* Return alignment error before compensation */ + return ts_error; +} + +/*! dummy callback to disable transcoding (see also cfg->rtp_processing_cb). + * \param[in] associated endpoint + * \param[in] destination RTP end + * \param[in,out] pointer to buffer with voice data + * \param[in] voice data length + * \param[in] maxmimum size of caller provided voice data buffer + * \returns ignores input parameters, return always 0 */ +int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size) +{ + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x transcoding disabled\n", + ENDPOINT_NUMBER(endp)); + return 0; +} + +/*! dummy callback to disable transcoding (see also cfg->setup_rtp_processing_cb). + * \param[in] associated endpoint + * \param[in] destination RTP end + * \param[in] source RTP end + * \returns ignores input parameters, return always 0 */ +int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end) +{ + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x transcoding disabled\n", + ENDPOINT_NUMBER(endp)); + return 0; +} + +void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, + int *payload_type, + const char **audio_name, + const char **fmtp_extra, + struct mgcp_conn_rtp *conn) +{ + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x conn:%s using format defaults\n", + ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn)); + + *payload_type = conn->end.codec.payload_type; + *audio_name = conn->end.codec.audio_name; + *fmtp_extra = conn->end.fmtp_extra; +} + +void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, const uint16_t seq, + const int32_t transit, const uint32_t ssrc) +{ + int32_t d; + + /* initialize or re-initialize */ + if (!state->stats_initialized || state->stats_ssrc != ssrc) { + state->stats_initialized = 1; + state->stats_base_seq = seq; + state->stats_max_seq = seq - 1; + state->stats_ssrc = ssrc; + state->stats_jitter = 0; + state->stats_transit = transit; + state->stats_cycles = 0; + } else { + uint16_t udelta; + + /* The below takes the shape of the validation of + * Appendix A. Check if there is something weird with + * the sequence number, otherwise check for a wrap + * around in the sequence number. + * It can't wrap during the initialization so let's + * skip it here. The Appendix A probably doesn't have + * this issue because of the probation. */ + udelta = seq - state->stats_max_seq; + if (udelta < RTP_MAX_DROPOUT) { + if (seq < state->stats_max_seq) + state->stats_cycles += RTP_SEQ_MOD; + } else if (udelta <= RTP_SEQ_MOD - RTP_MAX_MISORDER) { + LOGP(DRTP, LOGL_NOTICE, + "RTP seqno made a very large jump on 0x%x delta: %u\n", + ENDPOINT_NUMBER(endp), udelta); + } + } + + /* Calculate the jitter between the two packages. The TS should be + * taken closer to the read function. This was taken from the + * Appendix A of RFC 3550. Timestamp and arrival_time have a 1/rate + * resolution. */ + d = transit - state->stats_transit; + state->stats_transit = transit; + if (d < 0) + d = -d; + state->stats_jitter += d - ((state->stats_jitter + 8) >> 4); + state->stats_max_seq = seq; +} + +/* The RFC 3550 Appendix A assumes there are multiple sources but + * some of the supported endpoints (e.g. the nanoBTS) can only handle + * one source and this code will patch RTP header to appear as if there + * is only one source. + * There is also no probation period for new sources. Every RTP header + * we receive will be seen as a switch in streams. */ +void mgcp_patch_and_count(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, char *data, int len) +{ + uint32_t arrival_time; + int32_t transit; + uint16_t seq; + uint32_t timestamp, ssrc; + struct rtp_hdr *rtp_hdr; + int payload = rtp_end->codec.payload_type; + + if (len < sizeof(*rtp_hdr)) + return; + + rtp_hdr = (struct rtp_hdr *)data; + seq = ntohs(rtp_hdr->sequence); + timestamp = ntohl(rtp_hdr->timestamp); + arrival_time = get_current_ts(rtp_end->codec.rate); + ssrc = ntohl(rtp_hdr->ssrc); + transit = arrival_time - timestamp; + + mgcp_rtp_annex_count(endp, state, seq, transit, ssrc); + + if (!state->initialized) { + state->initialized = 1; + state->in_stream.last_seq = seq - 1; + state->in_stream.ssrc = state->orig_ssrc = ssrc; + state->in_stream.last_tsdelta = 0; + state->packet_duration = + mgcp_rtp_packet_duration(endp, rtp_end); + state->out_stream = state->in_stream; + state->out_stream.last_timestamp = timestamp; + state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */ + LOGP(DRTP, LOGL_INFO, + "endpoint:%x initializing stream, SSRC: %u timestamp: %u " + "pkt-duration: %d, from %s:%d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->seq_offset, state->packet_duration, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + if (state->packet_duration == 0) { + state->packet_duration = + rtp_end->codec.rate * 20 / 1000; + LOGP(DRTP, LOGL_NOTICE, + "endpoint:%x fixed packet duration is not available, " + "using fixed 20ms instead: %d from %s:%d\n", + ENDPOINT_NUMBER(endp), state->packet_duration, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } + } else if (state->in_stream.ssrc != ssrc) { + LOGP(DRTP, LOGL_NOTICE, + "endpoint:%x SSRC changed: %u -> %u " + "from %s:%d\n", + ENDPOINT_NUMBER(endp), + state->in_stream.ssrc, rtp_hdr->ssrc, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + + state->in_stream.ssrc = ssrc; + if (rtp_end->force_constant_ssrc) { + int16_t delta_seq; + + /* Always increment seqno by 1 */ + state->seq_offset = + (state->out_stream.last_seq + 1) - seq; + + /* Estimate number of packets that would have been sent */ + delta_seq = + (arrival_time - state->in_stream.last_arrival_time + + state->packet_duration / 2) / + state->packet_duration; + + adjust_rtp_timestamp_offset(endp, state, rtp_end, addr, + delta_seq, timestamp); + + state->patch_ssrc = 1; + ssrc = state->orig_ssrc; + if (rtp_end->force_constant_ssrc != -1) + rtp_end->force_constant_ssrc -= 1; + + LOGP(DRTP, LOGL_NOTICE, + "endpoint:%x SSRC patching enabled, SSRC: %u " + "SeqNo offset: %d, TS offset: %d " + "from %s:%d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->seq_offset, state->timestamp_offset, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + } + + state->in_stream.last_tsdelta = 0; + } else { + /* Compute current per-packet timestamp delta */ + check_rtp_timestamp(endp, state, &state->in_stream, rtp_end, + addr, seq, timestamp, "input", + &state->in_stream.last_tsdelta); + + if (state->patch_ssrc) + ssrc = state->orig_ssrc; + } + + /* Save before patching */ + state->in_stream.last_timestamp = timestamp; + state->in_stream.last_seq = seq; + state->in_stream.last_arrival_time = arrival_time; + + if (rtp_end->force_aligned_timing && + state->out_stream.ssrc == ssrc && state->packet_duration) + /* Align the timestamp offset */ + align_rtp_timestamp_offset(endp, state, rtp_end, addr, + timestamp); + + /* Store the updated SSRC back to the packet */ + if (state->patch_ssrc) + rtp_hdr->ssrc = htonl(ssrc); + + /* Apply the offset and store it back to the packet. + * This won't change anything if the offset is 0, so the conditional is + * omitted. */ + seq += state->seq_offset; + rtp_hdr->sequence = htons(seq); + timestamp += state->timestamp_offset; + rtp_hdr->timestamp = htonl(timestamp); + + /* Check again, whether the timestamps are still valid */ + if (state->out_stream.ssrc == ssrc) + check_rtp_timestamp(endp, state, &state->out_stream, rtp_end, + addr, seq, timestamp, "output", + &state->out_stream.last_tsdelta); + + /* Save output values */ + state->out_stream.last_seq = seq; + state->out_stream.last_timestamp = timestamp; + state->out_stream.ssrc = ssrc; + + if (payload < 0) + return; + +#if 0 + DEBUGP(DRTP, + "endpoint:%x payload hdr payload %u -> endp payload %u\n", + ENDPOINT_NUMBER(endp), rtp_hdr->payload_type, payload); + rtp_hdr->payload_type = payload; +#endif +} + +/* Forward data to a debug tap. This is debug function that is intended for + * debugging the voice traffic with tools like gstreamer */ +static void forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf, + int len) +{ + int rc; + + if (!tap->enabled) + return; + + rc = sendto(fd, buf, len, 0, (struct sockaddr *)&tap->forward, + sizeof(tap->forward)); + + if (rc < 0) + LOGP(DRTP, LOGL_ERROR, + "Forwarding tapped (debug) voice data failed.\n"); +} + +/*! Send RTP/RTCP data to a specified destination connection. + * \param[in] endp associated endpoint (for configuration, logging) + * \param[in] is_rtp flag to specify if the packet is of type RTP or RTCP + * \param[in] spoofed source address (set to NULL to disable) + * \param[in] buf buffer that contains the RTP/RTCP data + * \param[in] len length of the buffer that contains the RTP/RTCP data + * \param[in] conn_src associated source connection + * \param[in] conn_dst associated destination connection + * \returns 0 on success, -1 on ERROR */ +int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr, + char *buf, int len, struct mgcp_conn_rtp *conn_src, + struct mgcp_conn_rtp *conn_dst) +{ + /*! When no destination connection is available (e.g. when only one + * connection in loopback mode exists), then the source connection + * shall be specified as destination connection */ + + struct mgcp_trunk_config *tcfg = endp->tcfg; + struct mgcp_rtp_end *rtp_end; + struct mgcp_rtp_state *rtp_state; + char *dest_name; + + OSMO_ASSERT(conn_src); + OSMO_ASSERT(conn_dst); + + if (is_rtp) { + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x delivering RTP packet...\n", + ENDPOINT_NUMBER(endp)); + } else { + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x delivering RTCP packet...\n", + ENDPOINT_NUMBER(endp)); + } + + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x loop:%d, mode:%d ", + ENDPOINT_NUMBER(endp), tcfg->audio_loop, conn_src->conn->mode); + if (conn_src->conn->mode == MGCP_CONN_LOOPBACK) + LOGPC(DRTP, LOGL_DEBUG, "(loopback)\n"); + else + LOGPC(DRTP, LOGL_DEBUG, "\n"); + + /* Note: In case of loopback configuration, both, the source and the + * destination will point to the same connection. */ + rtp_end = &conn_dst->end; + rtp_state = &conn_src->state; + dest_name = conn_dst->conn->name; + + if (!rtp_end->output_enabled) { + rtp_end->dropped_packets += 1; + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x output disabled, drop to %s %s " + "rtp_port:%u rtcp_port:%u\n", + ENDPOINT_NUMBER(endp), + dest_name, + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), ntohs(rtp_end->rtcp_port) + ); + } else if (is_rtp) { + int cont; + int nbytes = 0; + int buflen = len; + do { + /* Run transcoder */ + cont = endp->cfg->rtp_processing_cb(endp, rtp_end, + buf, &buflen, + RTP_BUF_SIZE); + if (cont < 0) + break; + + if (addr) + mgcp_patch_and_count(endp, rtp_state, rtp_end, + addr, buf, buflen); + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x process/send to %s %s " + "rtp_port:%u rtcp_port:%u\n", + ENDPOINT_NUMBER(endp), dest_name, + inet_ntoa(rtp_end->addr), ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + + /* Forward a copy of the RTP data to a debug ip/port */ + forward_data(rtp_end->rtp.fd, &conn_src->tap_out, + buf, buflen); + + /* FIXME: HACK HACK HACK. See OS#2459. + * The ip.access nano3G needs the first RTP payload's first two bytes to read hex + * 'e400', or it will reject the RAB assignment. It seems to not harm other femto + * cells (as long as we patch only the first RTP payload in each stream). + */ + if (!rtp_state->patched_first_rtp_payload) { + uint8_t *data = (uint8_t *) & buf[12]; + data[0] = 0xe4; + data[1] = 0x00; + rtp_state->patched_first_rtp_payload = true; + } + + len = mgcp_udp_send(rtp_end->rtp.fd, + &rtp_end->addr, + rtp_end->rtp_port, buf, buflen); + + if (len <= 0) + return len; + + conn_dst->end.packets_tx += 1; + conn_dst->end.octets_tx += len; + + nbytes += len; + buflen = cont; + } while (buflen > 0); + return nbytes; + } else if (!tcfg->omit_rtcp) { + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x send to %s %s rtp_port:%u rtcp_port:%u\n", + ENDPOINT_NUMBER(endp), + dest_name, + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), ntohs(rtp_end->rtcp_port) + ); + + len = mgcp_udp_send(rtp_end->rtcp.fd, + &rtp_end->addr, + rtp_end->rtcp_port, buf, len); + + conn_dst->end.packets_tx += 1; + conn_dst->end.octets_tx += len; + + return len; + } + + return 0; +} + +/* Helper function for mgcp_recv(), + Receive one RTP Packet + Originating address from file descriptor */ +static int receive_from(struct mgcp_endpoint *endp, int fd, + struct sockaddr_in *addr, char *buf, int bufsize) +{ + int rc; + socklen_t slen = sizeof(*addr); + struct sockaddr_in addr_sink; + char buf_sink[RTP_BUF_SIZE]; + bool tossed = false; + + if (!addr) + addr = &addr_sink; + if (!buf) { + tossed = true; + buf = buf_sink; + bufsize = sizeof(buf_sink); + } + + rc = recvfrom(fd, buf, bufsize, 0, (struct sockaddr *)addr, &slen); + + LOGP(DRTP, LOGL_DEBUG, + "receiving %u bytes length packet from %s:%u ...\n", + rc, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); + + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x failed to receive packet, errno: %d/%s\n", + ENDPOINT_NUMBER(endp), errno, strerror(errno)); + return -1; + } + + if (tossed) { + LOGP(DRTP, LOGL_ERROR, "endpoint:%x packet tossed\n", + ENDPOINT_NUMBER(endp)); + } + + return rc; +} + +/* Check if the origin (addr) matches the address/port data of the RTP + * connections. */ +static int check_rtp_origin(struct mgcp_conn_rtp *conn, + struct sockaddr_in *addr) +{ + struct mgcp_endpoint *endp; + endp = conn->conn->endp; + + /* Note: Check if the inbound RTP data comes from the same host to + * which we send our outgoing RTP traffic. */ + if (memcmp(&addr->sin_addr, &conn->end.addr, sizeof(addr->sin_addr)) + != 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x data from wrong address: %s, ", + ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr)); + LOGPC(DRTP, LOGL_ERROR, "expected: %s\n", + inet_ntoa(conn->end.addr)); + LOGP(DRTP, LOGL_ERROR, "endpoint:%x packet tossed\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + /* Note: Usually the remote remote port of the data we receive will be + * the same as the remote port where we transmit outgoing RTP traffic + * to (set by MDCX). We use this to check the origin of the data for + * plausibility. */ + if (conn->end.rtp_port != addr->sin_port && + conn->end.rtcp_port != addr->sin_port) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x data from wrong source port: %d, ", + ENDPOINT_NUMBER(endp), ntohs(addr->sin_port)); + LOGPC(DRTP, LOGL_ERROR, + "expected: %d for RTP or %d for RTCP\n", + ntohs(conn->end.rtp_port), ntohs(conn->end.rtcp_port)); + LOGP(DRTP, LOGL_ERROR, "endpoint:%x packet tossed\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + return 0; +} + +/* Check the if the destination address configuration of an RTP connection + * makes sense */ +static int check_rtp_destin(struct mgcp_conn_rtp *conn) +{ + struct mgcp_endpoint *endp; + endp = conn->conn->endp; + + if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x destination IP-address is invalid\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + if (conn->end.rtp_port == 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x destination rtp port is invalid\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + return 0; +} + +/* Receive RTP data from a specified source connection and dispatch it to a + * destination connection. */ +static int mgcp_recv(int *proto, struct sockaddr_in *addr, char *buf, + unsigned int buf_size, struct osmo_fd *fd) +{ + struct mgcp_endpoint *endp; + struct mgcp_conn_rtp *conn; + struct mgcp_trunk_config *tcfg; + int rc; + + conn = (struct mgcp_conn_rtp*) fd->data; + endp = conn->conn->endp; + tcfg = endp->tcfg; + + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x receiving RTP/RTCP packet...\n", + ENDPOINT_NUMBER(endp)); + + rc = receive_from(endp, fd->fd, addr, buf, buf_size); + if (rc <= 0) + return -1; + *proto = fd == &conn->end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; + + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x ", ENDPOINT_NUMBER(endp)); + LOGPC(DRTP, LOGL_DEBUG, "receiveing from %s %s %d\n", + conn->conn->name, inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x conn:%s\n", ENDPOINT_NUMBER(endp), + mgcp_conn_dump(conn->conn)); + + /* Check if the origin of the RTP packet seems plausible */ + if (tcfg->rtp_accept_all == 0) { + if (check_rtp_origin(conn, addr) != 0) + return -1; + } + + /* Filter out dummy message */ + if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { + LOGP(DRTP, LOGL_NOTICE, + "endpoint:%x dummy message received\n", + ENDPOINT_NUMBER(endp)); + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x packet tossed\n", ENDPOINT_NUMBER(endp)); + return 0; + } + + /* Increment RX statistics */ + conn->end.packets_rx += 1; + conn->end.octets_rx += rc; + + /* Forward a copy of the RTP data to a debug ip/port */ + forward_data(fd->fd, &conn->tap_in, buf, rc); + + return rc; +} + +/* Send RTP data. Possible options are standard RTP packet + * transmission or trsmission via an osmux connection */ +static int mgcp_send_rtp(int proto, struct sockaddr_in *addr, char *buf, + unsigned int buf_size, + struct mgcp_conn_rtp *conn_src, + struct mgcp_conn_rtp *conn_dst) +{ + struct mgcp_endpoint *endp; + endp = conn_src->conn->endp; + + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x destin conn:%s\n", + ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn_dst->conn)); + + /* Before we try to deliver the packet, we check if the destination + * port and IP-Address make sense at all. If not, we will be unable + * to deliver the packet. */ + if (check_rtp_destin(conn_dst) != 0) + return -1; + + /* Depending on the RTP connection type, deliver the RTP packet to the + * destination connection. */ + switch (conn_dst->type) { + case MGCP_RTP_DEFAULT: + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x endpoint type is MGCP_RTP_DEFAULT, " + "using mgcp_send() to forward data directly\n", + ENDPOINT_NUMBER(endp)); + return mgcp_send(endp, proto == MGCP_PROTO_RTP, + addr, buf, buf_size, conn_src, conn_dst); + case MGCP_OSMUX_BSC_NAT: + case MGCP_OSMUX_BSC: + LOGP(DRTP, LOGL_DEBUG, + "endpoint:%x endpoint type is MGCP_OSMUX_BSC_NAT, " + "using osmux_xfrm_to_osmux() to forward data through OSMUX\n", + ENDPOINT_NUMBER(endp)); + return osmux_xfrm_to_osmux(buf, buf_size, conn_dst); + } + + /* If the data has not been handled/forwarded until here, it will + * be discarded, this should not happen, normally the MGCP type + * should be properly set */ + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x bad MGCP type -- data discarded!\n", + ENDPOINT_NUMBER(endp)); + + return -1; +} + +/*! dispatch incoming RTP packet to opposite RTP connection. + * \param[in] proto protocol (MGCP_CONN_TYPE_RTP or MGCP_CONN_TYPE_RTCP) + * \param[in] addr socket address where the RTP packet has been received from + * \param[in] buf buffer that hold the RTP payload + * \param[in] buf_size size data length of buf + * \param[in] conn originating connection + * \returns 0 on success, -1 on ERROR */ +int mgcp_dispatch_rtp_bridge_cb(int proto, struct sockaddr_in *addr, char *buf, + unsigned int buf_size, struct mgcp_conn *conn) +{ + struct mgcp_conn *conn_dst; + struct mgcp_endpoint *endp; + endp = conn->endp; + + /*! NOTE: This callback function implements the endpoint specific + * dispatch bahviour of an rtp bridge/proxy endpoint. It is assumed + * that the endpoint will hold only two connections. This premise + * is used to determine the opposite connection (it is always the + * connection that is not the originating connection). Once the + * destination connection is known the RTP packet is sent via + * the destination connection. */ + + /* Find a destination connection. */ + /* NOTE: This code path runs every time an RTP packet is received. The + * function mgcp_find_dst_conn() we use to determine the detination + * connection will iterate the connection list inside the endpoint. + * Since list iterations are quite costly, we will figure out the + * destination only once and use the optional private data pointer of + * the connection to cache the destination connection pointer. */ + if (!conn->priv) { + conn_dst = mgcp_find_dst_conn(conn); + conn->priv = conn_dst; + } else { + conn_dst = (struct mgcp_conn *)conn->priv; + } + + /* There is no destination conn, stop here */ + if (!conn_dst) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x unable to find destination conn\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + /* The destination conn is not an RTP connection */ + if (conn_dst->type != MGCP_CONN_TYPE_RTP) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x unable to find suitable destination conn\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + /* Dispatch RTP packet to destination RTP connection */ + return mgcp_send_rtp(proto, addr, buf, + buf_size, &conn->u.rtp, &conn_dst->u.rtp); + +} + +/* Handle incoming RTP data from NET */ +static int rtp_data_net(struct osmo_fd *fd, unsigned int what) +{ + /* NOTE: This is a generic implementation. RTP data is received. In + * case of loopback the data is just sent back to its origin. All + * other cases implement endpoint specific behaviour (e.g. how is the + * destination connection determined?). That specific behaviour is + * implemented by the callback function that is called at the end of + * the function */ + + struct mgcp_conn_rtp *conn_src; + struct mgcp_endpoint *endp; + struct sockaddr_in addr; + + char buf[RTP_BUF_SIZE]; + int proto; + int len; + + conn_src = (struct mgcp_conn_rtp *)fd->data; + OSMO_ASSERT(conn_src); + endp = conn_src->conn->endp; + OSMO_ASSERT(endp); + + LOGP(DRTP, LOGL_DEBUG, "endpoint:%x source conn:%s\n", + ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn_src->conn)); + + /* Receive packet */ + len = mgcp_recv(&proto, &addr, buf, sizeof(buf), fd); + if (len < 0) + return -1; + + /* Check if the connection is in loopback mode, if yes, just send the + * incoming data back to the origin */ + if (conn_src->conn->mode == MGCP_CONN_LOOPBACK) { + return mgcp_send_rtp(proto, &addr, buf, + len, conn_src, conn_src); + } + + /* Execute endpoint specific implementation that handles the + * dispatching of the RTP data */ + return endp->type->dispatch_rtp_cb(proto, &addr, buf, len, + conn_src->conn); +} + +/*! set IP Type of Service parameter. + * \param[in] fd associated file descriptor + * \param[in] tos dscp value + * \returns 0 on success, -1 on ERROR */ +int mgcp_set_ip_tos(int fd, int tos) +{ + int ret; + ret = setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + + if (ret < 0) + return -1; + return 0; +} + +/*! bind RTP port to osmo_fd. + * \param[in] source_addr source (local) address to bind on + * \param[in] fd associated file descriptor + * \param[in] port to bind on + * \returns 0 on success, -1 on ERROR */ +int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port) +{ + struct sockaddr_in addr; + int on = 1; + + fd->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd->fd < 0) { + LOGP(DRTP, LOGL_ERROR, "failed to create UDP port (%s:%i).\n", + source_addr, port); + return -1; + } else { + LOGP(DRTP, LOGL_DEBUG, + "created UDP port (%s:%i).\n", source_addr, port); + } + + if (setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) { + LOGP(DRTP, LOGL_ERROR, + "failed to set socket options (%s:%i).\n", source_addr, + port); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_aton(source_addr, &addr.sin_addr); + + if (bind(fd->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(fd->fd); + fd->fd = -1; + LOGP(DRTP, LOGL_ERROR, "failed to bind UDP port (%s:%i).\n", + source_addr, port); + return -1; + } else { + LOGP(DRTP, LOGL_DEBUG, + "bound UDP port (%s:%i).\n", source_addr, port); + } + + return 0; +} + +/* Bind RTP and RTCP port (helper function for mgcp_bind_net_rtp_port()) */ +static int bind_rtp(struct mgcp_config *cfg, const char *source_addr, + struct mgcp_rtp_end *rtp_end, int endpno) +{ + /* NOTE: The port that is used for RTCP is the RTP port incremented by one + * (e.g. RTP-Port = 16000 ==> RTCP-Port = 16001) */ + + if (mgcp_create_bind(source_addr, &rtp_end->rtp, + rtp_end->local_port) != 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x failed to create RTP port: %s:%d\n", endpno, + source_addr, rtp_end->local_port); + goto cleanup0; + } + + if (mgcp_create_bind(source_addr, &rtp_end->rtcp, + rtp_end->local_port + 1) != 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x failed to create RTCP port: %s:%d\n", endpno, + source_addr, rtp_end->local_port + 1); + goto cleanup1; + } + + /* Set Type of Service (DSCP-Value) as configured via VTY */ + mgcp_set_ip_tos(rtp_end->rtp.fd, cfg->endp_dscp); + mgcp_set_ip_tos(rtp_end->rtcp.fd, cfg->endp_dscp); + + rtp_end->rtp.when = BSC_FD_READ; + if (osmo_fd_register(&rtp_end->rtp) != 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x failed to register RTP port %d\n", endpno, + rtp_end->local_port); + goto cleanup2; + } + + rtp_end->rtcp.when = BSC_FD_READ; + if (osmo_fd_register(&rtp_end->rtcp) != 0) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x failed to register RTCP port %d\n", endpno, + rtp_end->local_port + 1); + goto cleanup3; + } + + return 0; + +cleanup3: + osmo_fd_unregister(&rtp_end->rtp); +cleanup2: + close(rtp_end->rtcp.fd); + rtp_end->rtcp.fd = -1; +cleanup1: + close(rtp_end->rtp.fd); + rtp_end->rtp.fd = -1; +cleanup0: + return -1; +} + +/*! bind RTP port to endpoint/connection. + * \param[in] endp endpoint that holds the RTP connection + * \param[in] rtp_port port number to bind on + * \param[in] conn associated RTP connection + * \returns 0 on success, -1 on ERROR */ +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port, + struct mgcp_conn_rtp *conn) +{ + char name[512]; + struct mgcp_rtp_end *end; + char local_ip_addr[INET_ADDRSTRLEN]; + + snprintf(name, sizeof(name), "%s-%u", conn->conn->name, conn->conn->id); + end = &conn->end; + + if (end->rtp.fd != -1 || end->rtcp.fd != -1) { + LOGP(DRTP, LOGL_ERROR, + "endpoint:%x %u was already bound on conn:%s\n", + ENDPOINT_NUMBER(endp), rtp_port, + mgcp_conn_dump(conn->conn)); + + /* Double bindings should never occour! Since we always allocate + * connections dynamically and free them when they are not + * needed anymore, there must be no previous binding leftover. + * Should there be a connection bound twice, we have a serious + * problem and must exit immediately! */ + OSMO_ASSERT(false); + } + + end->local_port = rtp_port; + end->rtp.cb = rtp_data_net; + end->rtp.data = conn; + end->rtcp.data = conn; + end->rtcp.cb = rtp_data_net; + + mgcp_get_local_addr(local_ip_addr, conn); + + return bind_rtp(endp->cfg, local_ip_addr, end, + ENDPOINT_NUMBER(endp)); +} + +/*! free allocated RTP and RTCP ports. + * \param[in] end RTP end */ +void mgcp_free_rtp_port(struct mgcp_rtp_end *end) +{ + if (end->rtp.fd != -1) { + close(end->rtp.fd); + end->rtp.fd = -1; + osmo_fd_unregister(&end->rtp); + } + + if (end->rtcp.fd != -1) { + close(end->rtcp.fd); + end->rtcp.fd = -1; + osmo_fd_unregister(&end->rtcp); + } +} diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c new file mode 100644 index 0000000..60ffe06 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_osmux.c @@ -0,0 +1,692 @@ +/* + * (C) 2012-2013 by Pablo Neira Ayuso <pablo@gnumonks.org> + * (C) 2012-2013 by On Waves ehf <http://www.on-waves.com> + * All rights not specifically granted under this license are reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + */ + +#include <stdio.h> /* for printf */ +#include <string.h> /* for memcpy */ +#include <stdlib.h> /* for abs */ +#include <inttypes.h> /* for PRIu64 */ +#include <netinet/in.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/netif/osmux.h> +#include <osmocom/netif/rtp.h> + +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/osmux.h> +#include <osmocom/mgcp/mgcp_conn.h> + +static struct osmo_fd osmux_fd; + +static LLIST_HEAD(osmux_handle_list); + +struct osmux_handle { + struct llist_head head; + struct osmux_in_handle *in; + struct in_addr rem_addr; + int rem_port; + int refcnt; +}; + +static void *osmux; + +/* Deliver OSMUX batch to the remote end */ +static void osmux_deliver_cb(struct msgb *batch_msg, void *data) +{ + struct osmux_handle *handle = data; + struct sockaddr_in out = { + .sin_family = AF_INET, + .sin_port = handle->rem_port, + }; + + memcpy(&out.sin_addr, &handle->rem_addr, sizeof(handle->rem_addr)); + sendto(osmux_fd.fd, batch_msg->data, batch_msg->len, 0, + (struct sockaddr *)&out, sizeof(out)); + msgb_free(batch_msg); +} + +/* Lookup existing OSMUX handle for specified destination address. */ +static struct osmux_handle * +osmux_handle_find_get(struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + llist_for_each_entry(h, &osmux_handle_list, head) { + if (memcmp(&h->rem_addr, addr, sizeof(struct in_addr)) == 0 && + h->rem_port == rem_port) { + LOGP(DLMGCP, LOGL_DEBUG, "using existing OSMUX handle " + "for addr=%s:%d\n", + inet_ntoa(*addr), ntohs(rem_port)); + h->refcnt++; + return h; + } + } + + return NULL; +} + +/* Put down no longer needed OSMUX handle */ +static void osmux_handle_put(struct osmux_in_handle *in) +{ + struct osmux_handle *h; + + llist_for_each_entry(h, &osmux_handle_list, head) { + if (h->in == in) { + if (--h->refcnt == 0) { + LOGP(DLMGCP, LOGL_INFO, + "Releasing unused osmux handle for %s:%d\n", + inet_ntoa(h->rem_addr), + ntohs(h->rem_port)); + LOGP(DLMGCP, LOGL_INFO, "Stats: " + "input RTP msgs: %u bytes: %"PRIu64" " + "output osmux msgs: %u bytes: %"PRIu64"\n", + in->stats.input_rtp_msgs, + in->stats.input_rtp_bytes, + in->stats.output_osmux_msgs, + in->stats.output_osmux_bytes); + llist_del(&h->head); + osmux_xfrm_input_fini(h->in); + talloc_free(h); + } + return; + } + } + LOGP(DLMGCP, LOGL_ERROR, "cannot find Osmux input handle %p\n", in); +} + +/* Allocate free OSMUX handle */ +static struct osmux_handle * +osmux_handle_alloc(struct mgcp_config *cfg, struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + h = talloc_zero(osmux, struct osmux_handle); + if (!h) + return NULL; + h->rem_addr = *addr; + h->rem_port = rem_port; + h->refcnt++; + + h->in = talloc_zero(h, struct osmux_in_handle); + if (!h->in) { + talloc_free(h); + return NULL; + } + + /* sequence number to start OSMUX message from */ + h->in->osmux_seq = 0; + + h->in->batch_factor = cfg->osmux_batch; + + /* If batch size is zero, the library defaults to 1470 bytes. */ + h->in->batch_size = cfg->osmux_batch_size; + h->in->deliver = osmux_deliver_cb; + osmux_xfrm_input_init(h->in); + h->in->data = h; + + llist_add(&h->head, &osmux_handle_list); + + LOGP(DLMGCP, LOGL_DEBUG, "created new OSMUX handle for addr=%s:%d\n", + inet_ntoa(*addr), ntohs(rem_port)); + + return h; +} + +/* Lookup existing handle for a specified address, if the handle can not be + * foud a the function will automatically allocate one */ +static struct osmux_in_handle * +osmux_handle_lookup(struct mgcp_config *cfg, struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + h = osmux_handle_find_get(addr, rem_port); + if (h != NULL) + return h->in; + + h = osmux_handle_alloc(cfg, addr, rem_port); + if (h == NULL) + return NULL; + + return h->in; +} + +/*! send RTP packet through OSMUX connection. + * \param[in] buf rtp data + * \param[in] buf_len length of rtp data + * \param[in] conn associated RTP connection + * \returns 0 on success, -1 on ERROR */ +int osmux_xfrm_to_osmux(char *buf, int buf_len, struct mgcp_conn_rtp *conn) +{ + int ret; + struct msgb *msg; + + msg = msgb_alloc(4096, "RTP"); + if (!msg) + return -1; + + memcpy(msg->data, buf, buf_len); + msgb_put(msg, buf_len); + + while ((ret = osmux_xfrm_input(conn->osmux.in, msg, conn->osmux.cid)) > 0) { + /* batch full, build and deliver it */ + osmux_xfrm_input_deliver(conn->osmux.in); + } + return 0; +} + +/* Lookup the endpoint that corresponds to the specified address (port) */ +static struct mgcp_endpoint * +endpoint_lookup(struct mgcp_config *cfg, int cid, + struct in_addr *from_addr, int type) +{ + struct mgcp_endpoint *endp = NULL; + int i; + struct mgcp_conn_rtp *conn_net = NULL; + struct mgcp_conn_rtp *conn_bts = NULL; + + for (i=0; i<cfg->trunk.number_endpoints; i++) { + struct in_addr *this; + + endp = &cfg->trunk.endpoints[i]; + +#if 0 + if (!tmp->allocated) + continue; +#endif + + switch(type) { + case MGCP_DEST_NET: + /* FIXME: Get rid of CONN_ID_XXX! */ + conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); + this = &conn_net->end.addr; + break; + case MGCP_DEST_BTS: + /* FIXME: Get rid of CONN_ID_XXX! */ + conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS); + this = &conn_bts->end.addr; + break; + default: + /* Should not ever happen */ + LOGP(DLMGCP, LOGL_ERROR, "Bad type %d. Fix your code.\n", type); + return NULL; + } + + /* FIXME: Get rid of CONN_ID_XXX! */ + conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); + if (conn_net->osmux.cid == cid && this->s_addr == from_addr->s_addr) + return endp; + } + + LOGP(DLMGCP, LOGL_ERROR, "Cannot find endpoint with cid=%d\n", cid); + + return NULL; +} + +static void scheduled_tx_net_cb(struct msgb *msg, void *data) +{ + struct mgcp_endpoint *endp = data; + struct mgcp_conn_rtp *conn_net = NULL; + struct mgcp_conn_rtp *conn_bts = NULL; + + /* FIXME: Get rid of CONN_ID_XXX! */ + conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS); + conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); + if (!conn_bts || !conn_net) + return; + + struct sockaddr_in addr = { + .sin_addr = conn_net->end.addr, + .sin_port = conn_net->end.rtp_port, + }; + + conn_bts->end.octets_tx += msg->len; + conn_bts->end.packets_tx++; + + /* Send RTP data to NET */ + /* FIXME: Get rid of conn_bts and conn_net! */ + mgcp_send(endp, 1, &addr, (char *)msg->data, msg->len, + conn_bts, conn_net); + msgb_free(msg); +} + +static void scheduled_tx_bts_cb(struct msgb *msg, void *data) +{ + struct mgcp_endpoint *endp = data; + struct mgcp_conn_rtp *conn_net = NULL; + struct mgcp_conn_rtp *conn_bts = NULL; + + /* FIXME: Get rid of CONN_ID_XXX! */ + conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS); + conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); + if (!conn_bts || !conn_net) + return; + + struct sockaddr_in addr = { + .sin_addr = conn_bts->end.addr, + .sin_port = conn_bts->end.rtp_port, + }; + + conn_net->end.octets_tx += msg->len; + conn_net->end.packets_tx++; + + /* Send RTP data to BTS */ + /* FIXME: Get rid of conn_bts and conn_net! */ + mgcp_send(endp, 1, &addr, (char *)msg->data, msg->len, + conn_net, conn_bts); + msgb_free(msg); +} + +static struct msgb *osmux_recv(struct osmo_fd *ofd, struct sockaddr_in *addr) +{ + struct msgb *msg; + socklen_t slen = sizeof(*addr); + int ret; + + msg = msgb_alloc(4096, "OSMUX"); + if (!msg) { + LOGP(DLMGCP, LOGL_ERROR, "cannot allocate message\n"); + return NULL; + } + ret = recvfrom(ofd->fd, msg->data, msg->data_len, 0, + (struct sockaddr *)addr, &slen); + if (ret <= 0) { + msgb_free(msg); + LOGP(DLMGCP, LOGL_ERROR, "cannot receive message\n"); + return NULL; + } + msgb_put(msg, ret); + + return msg; +} + +#define osmux_chunk_length(msg, rem) (rem - msg->len); + +int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct msgb *msg; + struct osmux_hdr *osmuxh; + struct llist_head list; + struct sockaddr_in addr; + struct mgcp_config *cfg = ofd->data; + uint32_t rem; + struct mgcp_conn_rtp *conn_net = NULL; + + msg = osmux_recv(ofd, &addr); + if (!msg) + return -1; + + /* not any further processing dummy messages */ + if (msg->data[0] == MGCP_DUMMY_LOAD) + goto out; + + rem = msg->len; + while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct mgcp_endpoint *endp; + + /* Yes, we use MGCP_DEST_NET to locate the origin */ + endp = endpoint_lookup(cfg, osmuxh->circuit_id, + &addr.sin_addr, MGCP_DEST_NET); + + /* FIXME: Get rid of CONN_ID_XXX! */ + conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); + if (!conn_net) + goto out; + + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find an endpoint for circuit_id=%d\n", + osmuxh->circuit_id); + goto out; + } + conn_net->osmux.stats.octets += osmux_chunk_length(msg, rem); + conn_net->osmux.stats.chunks++; + rem = msg->len; + + osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list); + osmux_tx_sched(&list, scheduled_tx_bts_cb, endp); + } +out: + msgb_free(msg); + return 0; +} + +/* This is called from the bsc-nat */ +static int osmux_handle_dummy(struct mgcp_config *cfg, struct sockaddr_in *addr, + struct msgb *msg) +{ + struct mgcp_endpoint *endp; + uint8_t osmux_cid; + struct mgcp_conn_rtp *conn_net = NULL; + + if (msg->len < 1 + sizeof(osmux_cid)) { + LOGP(DLMGCP, LOGL_ERROR, + "Discarding truncated Osmux dummy load\n"); + goto out; + } + + LOGP(DLMGCP, LOGL_DEBUG, "Received Osmux dummy load from %s\n", + inet_ntoa(addr->sin_addr)); + + if (!cfg->osmux) { + LOGP(DLMGCP, LOGL_ERROR, + "bsc wants to use Osmux but bsc-nat did not request it\n"); + goto out; + } + + /* extract the osmux CID from the dummy message */ + memcpy(&osmux_cid, &msg->data[1], sizeof(osmux_cid)); + + endp = endpoint_lookup(cfg, osmux_cid, &addr->sin_addr, MGCP_DEST_BTS); + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find endpoint for Osmux CID %d\n", osmux_cid); + goto out; + } + + conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); + if (!conn_net) + goto out; + + if (conn_net->osmux.state == OSMUX_STATE_ENABLED) + goto out; + + if (osmux_enable_conn(endp, conn_net, &addr->sin_addr, addr->sin_port) < 0 ) { + LOGP(DLMGCP, LOGL_ERROR, + "Could not enable osmux in endpoint %d\n", + ENDPOINT_NUMBER(endp)); + goto out; + } + + LOGP(DLMGCP, LOGL_INFO, "Enabling osmux in endpoint %d for %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); +out: + msgb_free(msg); + return 0; +} + +int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct msgb *msg; + struct osmux_hdr *osmuxh; + struct llist_head list; + struct sockaddr_in addr; + struct mgcp_config *cfg = ofd->data; + uint32_t rem; + struct mgcp_conn_rtp *conn_net = NULL; + + msg = osmux_recv(ofd, &addr); + if (!msg) + return -1; + + /* not any further processing dummy messages */ + if (msg->data[0] == MGCP_DUMMY_LOAD) + return osmux_handle_dummy(cfg, &addr, msg); + + rem = msg->len; + while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct mgcp_endpoint *endp; + + /* Yes, we use MGCP_DEST_BTS to locate the origin */ + endp = endpoint_lookup(cfg, osmuxh->circuit_id, + &addr.sin_addr, MGCP_DEST_BTS); + + /* FIXME: Get rid of CONN_ID_XXX! */ + conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); + if (!conn_net) + goto out; + + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find an endpoint for circuit_id=%d\n", + osmuxh->circuit_id); + goto out; + } + conn_net->osmux.stats.octets += osmux_chunk_length(msg, rem); + conn_net->osmux.stats.chunks++; + rem = msg->len; + + osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list); + osmux_tx_sched(&list, scheduled_tx_net_cb, endp); + } +out: + msgb_free(msg); + return 0; +} + +int osmux_init(int role, struct mgcp_config *cfg) +{ + int ret; + + switch(role) { + case OSMUX_ROLE_BSC: + osmux_fd.cb = osmux_read_from_bsc_nat_cb; + break; + case OSMUX_ROLE_BSC_NAT: + osmux_fd.cb = osmux_read_from_bsc_cb; + break; + default: + LOGP(DLMGCP, LOGL_ERROR, "wrong role for OSMUX\n"); + return -1; + } + osmux_fd.data = cfg; + + ret = mgcp_create_bind(cfg->osmux_addr, &osmux_fd, cfg->osmux_port); + if (ret < 0) { + LOGP(DLMGCP, LOGL_ERROR, "cannot bind OSMUX socket\n"); + return ret; + } + mgcp_set_ip_tos(osmux_fd.fd, cfg->endp_dscp); + osmux_fd.when |= BSC_FD_READ; + + ret = osmo_fd_register(&osmux_fd); + if (ret < 0) { + LOGP(DLMGCP, LOGL_ERROR, "cannot register OSMUX socket\n"); + return ret; + } + cfg->osmux_init = 1; + + return 0; +} + +/*! enable OSXMUX circuit for a specified connection. + * \param[in] endp mgcp endpoint (configuration) + * \param[in] conn connection to disable + * \param[in] addr IP address of remote OSMUX endpoint + * \param[in] port portnumber of the remote OSMUX endpoint + * \returns 0 on success, -1 on ERROR */ +int osmux_enable_conn(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn, + struct in_addr *addr, uint16_t port) +{ + /*! If osmux is enabled, initialize the output handler. This handler is + * used to reconstruct the RTP flow from osmux. The RTP SSRC is + * allocated based on the circuit ID (conn_net->osmux.cid), which is unique + * in the local scope to the BSC/BSC-NAT. We use it to divide the RTP + * SSRC space (2^32) by the 256 possible circuit IDs, then randomly + * select one value from that window. Thus, we have no chance to have + * overlapping RTP SSRC traveling to the BTSes behind the BSC, + * similarly, for flows traveling to the MSC. + */ + static const uint32_t rtp_ssrc_winlen = UINT32_MAX / 256; + uint16_t osmux_dummy = endp->cfg->osmux_dummy; + + /* Check if osmux is enabled for the specified connection */ + if (conn->osmux.state == OSMUX_STATE_DISABLED) { + LOGP(DLMGCP, LOGL_ERROR, "OSMUX not enabled for conn:%s\n", + mgcp_conn_dump(conn->conn)); + return -1; + } + + osmux_xfrm_output_init(&conn->osmux.out, + (conn->osmux.cid * rtp_ssrc_winlen) + + (random() % rtp_ssrc_winlen)); + + conn->osmux.in = osmux_handle_lookup(endp->cfg, addr, port); + if (!conn->osmux.in) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot allocate input osmux handle for conn:%s\n", + mgcp_conn_dump(conn->conn)); + return -1; + } + if (!osmux_xfrm_input_open_circuit(conn->osmux.in, conn->osmux.cid, osmux_dummy)) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot open osmux circuit %u for conn:%s\n", + conn->osmux.cid, mgcp_conn_dump(conn->conn)); + return -1; + } + + switch (endp->cfg->role) { + case MGCP_BSC_NAT: + conn->type = MGCP_OSMUX_BSC_NAT; + break; + case MGCP_BSC: + conn->type = MGCP_OSMUX_BSC; + break; + } + + conn->osmux.state = OSMUX_STATE_ENABLED; + + return 0; +} + +/*! disable OSXMUX circuit for a specified connection. + * \param[in] conn connection to disable */ +void osmux_disable_conn(struct mgcp_conn_rtp *conn) +{ + if (!conn) + return; + + if (conn->osmux.state != OSMUX_STATE_ENABLED) + return; + + LOGP(DLMGCP, LOGL_INFO, "Releasing connection %u using Osmux CID %u\n", + conn->conn->id, conn->osmux.cid); + osmux_xfrm_input_close_circuit(conn->osmux.in, conn->osmux.cid); + conn->osmux.state = OSMUX_STATE_DISABLED; + conn->osmux.cid = -1; + osmux_handle_put(conn->osmux.in); +} + +/*! relase OSXMUX cid, that had been allocated to this connection. + * \param[in] conn connection with OSMUX cid to release */ +void osmux_release_cid(struct mgcp_conn_rtp *conn) +{ + if (!conn) + return; + + if (conn->osmux.state != OSMUX_STATE_ENABLED) + return; + + if (conn->osmux.allocated_cid >= 0) + osmux_put_cid(conn->osmux.allocated_cid); + conn->osmux.allocated_cid = -1; +} + +/*! allocate OSXMUX cid to connection. + * \param[in] conn connection for which we allocate the OSMUX cid*/ +void osmux_allocate_cid(struct mgcp_conn_rtp *conn) +{ + osmux_release_cid(conn); + conn->osmux.allocated_cid = osmux_get_cid(); +} + +/*! send RTP dummy packet to OSMUX connection port. + * \param[in] endp mcgp endpoint that holds the RTP connection + * \param[in] conn associated RTP connection + * \returns bytes sent, -1 on error */ +int osmux_send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) +{ + char buf[1 + sizeof(uint8_t)]; + struct in_addr addr_unset = {}; + + /*! The dummy packet will not be sent via the actual OSMUX connection, + * instead it is sent out of band to port where the remote OSMUX + * multplexer is listening. The goal is to ensure that the connection + * is kept open */ + + /*! We don't need to send the dummy load for osmux so often as another + * endpoint may have already punched the hole in the firewall. This + * approach is simple though. */ + + buf[0] = MGCP_DUMMY_LOAD; + memcpy(&buf[1], &conn->osmux.cid, sizeof(conn->osmux.cid)); + + /* Wait until we have the connection information from MDCX */ + if (memcmp(&conn->end.addr, &addr_unset, sizeof(addr_unset)) == 0) + return 0; + + if (conn->osmux.state == OSMUX_STATE_ACTIVATING) { + if (osmux_enable_conn(endp, conn, &conn->end.addr, + htons(endp->cfg->osmux_port)) < 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Could not activate osmux for conn:%s\n", + mgcp_conn_dump(conn->conn)); + } + LOGP(DLMGCP, LOGL_ERROR, + "Osmux CID %u for %s:%u is now enabled\n", + conn->osmux.cid, inet_ntoa(conn->end.addr), + endp->cfg->osmux_port); + } + LOGP(DLMGCP, LOGL_DEBUG, + "sending OSMUX dummy load to %s CID %u\n", + inet_ntoa(conn->end.addr), conn->osmux.cid); + + return mgcp_udp_send(osmux_fd.fd, &conn->end.addr, + htons(endp->cfg->osmux_port), buf, sizeof(buf)); +} + +/*! bsc-nat allocates/releases the OSMUX cids (Circuit IDs). */ +static uint8_t osmux_cid_bitmap[(OSMUX_CID_MAX + 1) / 8]; + +/*! count the number of taken OSMUX cids. + * \returns number of OSMUX cids in use */ +int osmux_used_cid(void) +{ + int i, j, used = 0; + + for (i = 0; i < sizeof(osmux_cid_bitmap); i++) { + for (j = 0; j < 8; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + used += 1; + } + } + + return used; +} + +/*! take a free OSMUX cid. + * \returns OSMUX cid */ +int osmux_get_cid(void) +{ + int i, j; + + for (i = 0; i < sizeof(osmux_cid_bitmap); i++) { + for (j = 0; j < 8; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + continue; + + osmux_cid_bitmap[i] |= (1 << j); + LOGP(DLMGCP, LOGL_DEBUG, + "Allocating Osmux CID %u from pool\n", (i * 8) + j); + return (i * 8) + j; + } + } + + LOGP(DLMGCP, LOGL_ERROR, "All Osmux circuits are in use!\n"); + return -1; +} + +/*! put back a no longer used OSMUX cid. + * \param[in] osmux_cid OSMUX cid */ +void osmux_put_cid(uint8_t osmux_cid) +{ + LOGP(DLMGCP, LOGL_DEBUG, "Osmux CID %u is back to the pool\n", osmux_cid); + osmux_cid_bitmap[osmux_cid / 8] &= ~(1 << (osmux_cid % 8)); +} diff --git a/src/libosmo-mgcp/mgcp_protocol.c b/src/libosmo-mgcp/mgcp_protocol.c new file mode 100644 index 0000000..8c6bd6e --- /dev/null +++ b/src/libosmo-mgcp/mgcp_protocol.c @@ -0,0 +1,1293 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> + +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/mgcp/mgcp_common.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/mgcp_stat.h> +#include <osmocom/mgcp/mgcp_msg.h> +#include <osmocom/mgcp/mgcp_ep.h> +#include <osmocom/mgcp/mgcp_sdp.h> + +struct mgcp_request { + char *name; + struct msgb *(*handle_request) (struct mgcp_parse_data * data); + char *debug_name; +}; + +#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \ + { .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME }, + +static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *data); +static struct msgb *handle_create_con(struct mgcp_parse_data *data); +static struct msgb *handle_delete_con(struct mgcp_parse_data *data); +static struct msgb *handle_modify_con(struct mgcp_parse_data *data); +static struct msgb *handle_rsip(struct mgcp_parse_data *data); +static struct msgb *handle_noti_req(struct mgcp_parse_data *data); + +/* Initalize transcoder */ +static int setup_rtp_processing(struct mgcp_endpoint *endp, + struct mgcp_conn_rtp *conn) +{ + struct mgcp_config *cfg = endp->cfg; + struct mgcp_conn_rtp *conn_src = NULL; + struct mgcp_conn_rtp *conn_dst = conn; + struct mgcp_conn *_conn; + + if (conn->type != MGCP_RTP_DEFAULT) { + LOGP(DLMGCP, LOGL_NOTICE, + "endpoint:%x RTP-setup: Endpoint is not configured as RTP default, stopping here!\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + if (conn->conn->mode == MGCP_CONN_LOOPBACK) { + LOGP(DLMGCP, LOGL_NOTICE, + "endpoint:%x RTP-setup: Endpoint is in loopback mode, stopping here!\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + /* Find the "sister" connection */ + llist_for_each_entry(_conn, &endp->conns, entry) { + if (_conn->id != conn->conn->id) { + conn_src = &_conn->u.rtp; + break; + } + } + + return cfg->setup_rtp_processing_cb(endp, &conn_dst->end, + &conn_src->end); +} + +/* array of function pointers for handling various + * messages. In the future this might be binary sorted + * for performance reasons. */ +static const struct mgcp_request mgcp_requests[] = { + MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint") + MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection") + MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection") + MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection") + MGCP_REQUEST("RQNT", handle_noti_req, "NotificationRequest") + + /* SPEC extension */ + MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress") +}; + +/* Helper function to allocate some memory for responses and retransmissions */ +static struct msgb *mgcp_msgb_alloc(void) +{ + struct msgb *msg; + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + if (!msg) + LOGP(DLMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n"); + + return msg; +} + +/* Helper function for do_retransmission() and create_resp() */ +static struct msgb *do_retransmission(const struct mgcp_endpoint *endp) +{ + struct msgb *msg = mgcp_msgb_alloc(); + if (!msg) + return NULL; + + msg->l2h = msgb_put(msg, strlen(endp->last_response)); + memcpy(msg->l2h, endp->last_response, msgb_l2len(msg)); + mgcp_disp_msg(msg->l2h, msgb_l2len(msg), "Retransmitted response"); + return msg; +} + +static struct msgb *create_resp(struct mgcp_endpoint *endp, int code, + const char *txt, const char *msg, + const char *trans, const char *param, + const char *sdp) +{ + int len; + struct msgb *res; + + res = mgcp_msgb_alloc(); + if (!res) + return NULL; + + len = snprintf((char *)res->data, 2048, "%d %s%s%s\r\n%s", + code, trans, txt, param ? param : "", sdp ? sdp : ""); + if (len < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to sprintf MGCP response.\n"); + msgb_free(res); + return NULL; + } + + res->l2h = msgb_put(res, len); + LOGP(DLMGCP, LOGL_DEBUG, "Generated response: code=%d\n", code); + mgcp_disp_msg(res->l2h, msgb_l2len(res), "Generated response"); + + /* + * Remember the last transmission per endpoint. + */ + if (endp) { + struct mgcp_trunk_config *tcfg = endp->tcfg; + talloc_free(endp->last_response); + talloc_free(endp->last_trans); + endp->last_trans = talloc_strdup(tcfg->endpoints, trans); + endp->last_response = talloc_strndup(tcfg->endpoints, + (const char *)res->l2h, + msgb_l2len(res)); + } + + return res; +} + +static struct msgb *create_ok_resp_with_param(struct mgcp_endpoint *endp, + int code, const char *msg, + const char *trans, + const char *param) +{ + return create_resp(endp, code, " OK", msg, trans, param, NULL); +} + +static struct msgb *create_ok_response(struct mgcp_endpoint *endp, + int code, const char *msg, + const char *trans) +{ + return create_ok_resp_with_param(endp, code, msg, trans, NULL); +} + +static struct msgb *create_err_response(struct mgcp_endpoint *endp, + int code, const char *msg, + const char *trans) +{ + return create_resp(endp, code, " FAIL", msg, trans, NULL, NULL); +} + +/* Format MGCP response string (with SDP attached) */ +static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp, + struct mgcp_conn_rtp *conn, + const char *msg, + const char *trans_id) +{ + const char *addr = endp->cfg->local_ip; + struct msgb *sdp; + int rc; + struct msgb *result; + char osmux_extension[strlen("\nX-Osmux: 255") + 1]; + char local_ip_addr[INET_ADDRSTRLEN]; + + sdp = msgb_alloc_headroom(4096, 128, "sdp record"); + if (!sdp) + return NULL; + + if (!addr) { + mgcp_get_local_addr(local_ip_addr, conn); + addr = local_ip_addr; + } + + if (conn->osmux.state == OSMUX_STATE_NEGOTIATING) { + sprintf(osmux_extension, "\nX-Osmux: %u", conn->osmux.cid); + conn->osmux.state = OSMUX_STATE_ACTIVATING; + } else { + osmux_extension[0] = '\0'; + } + + rc = msgb_printf(sdp, "I: %u%s\n\n", conn->conn->id, osmux_extension); + if (rc < 0) + goto error; + + rc = mgcp_write_response_sdp(endp, conn, sdp, addr); + if (rc < 0) + goto error; + result = create_resp(endp, 200, " OK", msg, trans_id, NULL, (char*) sdp->data); + msgb_free(sdp); + return result; +error: + msgb_free(sdp); + return NULL; +} + +/* Send out dummy packet to keep the connection open, if the connection is an + * osmux connection, send the dummy packet via OSMUX */ +static void send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) +{ + if (conn->osmux.state != OSMUX_STATE_DISABLED) + osmux_send_dummy(endp, conn); + else + mgcp_send_dummy(endp, conn); +} + +/* handle incoming messages: + * - this can be a command (four letters, space, transaction id) + * - or a response (three numbers, space, transaction id) */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_parse_data pdata; + int i, code, handled = 0; + struct msgb *resp = NULL; + char *data; + + if (msgb_l2len(msg) < 4) { + LOGP(DLMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len); + return NULL; + } + + if (mgcp_msg_terminate_nul(msg)) + return NULL; + + mgcp_disp_msg(msg->l2h, msgb_l2len(msg), "Received message"); + + /* attempt to treat it as a response */ + if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) { + LOGP(DLMGCP, LOGL_DEBUG, "Response: Code: %d\n", code); + return NULL; + } + + msg->l3h = &msg->l2h[4]; + + /* + * Check for a duplicate message and respond. + */ + memset(&pdata, 0, sizeof(pdata)); + pdata.cfg = cfg; + data = mgcp_strline((char *)msg->l3h, &pdata.save); + pdata.found = mgcp_parse_header(&pdata, data); + if (pdata.endp && pdata.trans + && pdata.endp->last_trans + && strcmp(pdata.endp->last_trans, pdata.trans) == 0) { + return do_retransmission(pdata.endp); + } + + for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i) { + if (strncmp + (mgcp_requests[i].name, (const char *)&msg->l2h[0], + 4) == 0) { + handled = 1; + resp = mgcp_requests[i].handle_request(&pdata); + break; + } + } + + if (!handled) + LOGP(DLMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n", + &msg->l2h[0]); + + return resp; +} + +/* AUEP command handler, processes the received command */ +static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p) +{ + LOGP(DLMGCP, LOGL_NOTICE, "AUEP: auditing endpoint ...\n"); + + if (p->found != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "AUEP: failed to find the endpoint.\n"); + return create_err_response(NULL, 500, "AUEP", p->trans); + } else + return create_ok_response(p->endp, 200, "AUEP", p->trans); +} + +/* Try to find a free port by attemting to bind on it. Also handle the + * counter that points on the next free port. Since we have a pointer + * to the next free port, binding should work on the first attemt, + * neverless, try at least the next 200 ports before giving up */ +static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) +{ + int i; + struct mgcp_rtp_end *end; + struct mgcp_port_range *range; + + OSMO_ASSERT(conn); + end = &conn->end; + OSMO_ASSERT(end); + + range = &endp->cfg->net_ports; + + /* attempt to find a port */ + for (i = 0; i < 200; ++i) { + int rc; + + if (range->last_port >= range->range_end) + range->last_port = range->range_start; + + rc = mgcp_bind_net_rtp_port(endp, range->last_port, conn); + + range->last_port += 2; + if (rc == 0) { + return 0; + } + + } + + LOGP(DLMGCP, LOGL_ERROR, + "Allocating a RTP/RTCP port failed 200 times 0x%x.\n", + ENDPOINT_NUMBER(endp)); + return -1; +} + +/* Set the LCO from a string (see RFC 3435). + * The string is stored in the 'string' field. A NULL string is handled excatlyy + * like an empty string, the 'string' field is never NULL after this function + * has been called. */ +static void set_local_cx_options(void *ctx, struct mgcp_lco *lco, + const char *options) +{ + char *p_opt, *a_opt; + char codec[9]; + + talloc_free(lco->string); + talloc_free(lco->codec); + lco->codec = NULL; + lco->pkt_period_min = lco->pkt_period_max = 0; + lco->string = talloc_strdup(ctx, options ? options : ""); + + p_opt = strstr(lco->string, "p:"); + if (p_opt && sscanf(p_opt, "p:%d-%d", + &lco->pkt_period_min, &lco->pkt_period_max) == 1) + lco->pkt_period_max = lco->pkt_period_min; + + a_opt = strstr(lco->string, "a:"); + if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) + lco->codec = talloc_strdup(ctx, codec); + + LOGP(DLMGCP, LOGL_DEBUG, + "local CX options: lco->pkt_period_max: %i, lco->codec: %s\n", + lco->pkt_period_max, lco->codec); +} + +void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change, + struct mgcp_rtp_end *rtp) +{ + struct mgcp_trunk_config *tcfg = endp->tcfg; + + int patch_ssrc = expect_ssrc_change && tcfg->force_constant_ssrc; + + rtp->force_aligned_timing = tcfg->force_aligned_timing; + rtp->force_constant_ssrc = patch_ssrc ? 1 : 0; + + LOGP(DLMGCP, LOGL_DEBUG, + "Configuring RTP endpoint: local port %d%s%s\n", + ntohs(rtp->rtp_port), + rtp->force_aligned_timing ? ", force constant timing" : "", + rtp->force_constant_ssrc ? ", force constant ssrc" : ""); +} + +uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *rtp) +{ + int f = 0; + + /* Get the number of frames per channel and packet */ + if (rtp->frames_per_packet) + f = rtp->frames_per_packet; + else if (rtp->packet_duration_ms && rtp->codec.frame_duration_num) { + int den = 1000 * rtp->codec.frame_duration_num; + f = (rtp->packet_duration_ms * rtp->codec.frame_duration_den + + den / 2) + / den; + } + + return rtp->codec.rate * f * rtp->codec.frame_duration_num / + rtp->codec.frame_duration_den; +} + +static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line) +{ + if (!endp->cfg->osmux_init) { + if (osmux_init(OSMUX_ROLE_BSC, endp->cfg) < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot init OSMUX\n"); + return -1; + } + LOGP(DLMGCP, LOGL_NOTICE, "OSMUX socket has been set up\n"); + } + + return mgcp_parse_osmux_cid(line); +} + +/* CRCX command handler, processes the received command */ +static struct msgb *handle_create_con(struct mgcp_parse_data *p) +{ + struct mgcp_trunk_config *tcfg; + struct mgcp_endpoint *endp = p->endp; + int error_code = 400; + + const char *local_options = NULL; + const char *callid = NULL; + const char *ci = NULL; + const char *mode = NULL; + char *line; + int have_sdp = 0, osmux_cid = -1; + struct mgcp_conn_rtp *conn = NULL; + uint32_t conn_id; + char conn_name[512]; + + LOGP(DLMGCP, LOGL_NOTICE, "CRCX: creating new connection ...\n"); + + if (p->found != 0) + return create_err_response(NULL, 510, "CRCX", p->trans); + + /* parse CallID C: and LocalParameters L: */ + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'L': + local_options = (const char *)line + 3; + break; + case 'C': + callid = (const char *)line + 3; + break; + case 'I': + ci = (const char *)line + 3; + break; + case 'M': + mode = (const char *)line + 3; + break; + case 'X': + /* If osmoux is disabled, just skip setting it up */ + if (!p->endp->cfg->osmux) + break; + if (strncmp("Osmux: ", line + 2, strlen("Osmux: ")) == + 0) + osmux_cid = mgcp_osmux_setup(endp, line); + break; + case '\0': + have_sdp = 1; + goto mgcp_header_done; + default: + LOGP(DLMGCP, LOGL_NOTICE, + "CRCX: endpoint:%x unhandled option: '%c'/%d\n", + ENDPOINT_NUMBER(endp), *line, *line); + break; + } + } + +mgcp_header_done: + tcfg = p->endp->tcfg; + + /* Check parameters */ + if (!callid) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x insufficient parameters, missing callid\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + + if (!mode) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x insufficient parameters, missing mode\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + + if (!ci) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x insufficient parameters, missing connection id\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + + /* Check if we are able to accept the creation of another connection */ + if (llist_count(&endp->conns) >= endp->type->max_conns) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x endpoint full, max. %i connections allowed!\n", + endp->type->max_conns, ENDPOINT_NUMBER(endp)); + if (tcfg->force_realloc) { + /* There is no more room for a connection, make some + * room by blindly tossing the oldest of the two two + * connections */ + mgcp_conn_free_oldest(endp); + } else { + /* There is no more room for a connection, leave + * everything as it is and return with an error */ + return create_err_response(endp, 400, "CRCX", p->trans); + } + } + + /* Check if this endpoint already serves a call, if so, check if the + * callids match up so that we are sure that this is our call */ + if (endp->callid && mgcp_verify_call_id(endp, callid)) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x allready seized by other call (%s)\n", + ENDPOINT_NUMBER(endp), endp->callid); + if (tcfg->force_realloc) + /* This is not our call, toss everything by releasing + * the entire endpoint. (rude!) */ + mgcp_release_endp(endp); + else { + /* This is not our call, leave everything as it is and + * return with an error. */ + return create_err_response(endp, 400, "CRCX", p->trans); + } + } + + /* Set the callid, creation of another connection will only be possible + * when the callid matches up. (Connections are distinuished by their + * connection ids) */ + endp->callid = talloc_strdup(tcfg->endpoints, callid); + + /* Extract audio codec information */ + set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, + local_options); + + if (mgcp_parse_ci(&conn_id, ci)) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x insufficient parameters, missing ci (connectionIdentifier)\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + + /* Only accept another connection when the connection ID is different. */ + if (mgcp_conn_get_rtp(endp, conn_id)) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x there is already a connection with id %u present!\n", + conn_id, ENDPOINT_NUMBER(endp)); + if (tcfg->force_realloc) { + /* Ignore the existing connection by just freeing it */ + mgcp_conn_free(endp, conn_id); + } else { + /* There is already a connection with that ID present, + * leave everything as it is and return with an error. */ + return create_err_response(endp, 400, "CRCX", p->trans); + } + } + + snprintf(conn_name, sizeof(conn_name), "%s-%u", callid, conn_id); + mgcp_conn_alloc(NULL, endp, conn_id, MGCP_CONN_TYPE_RTP, + conn_name); + conn = mgcp_conn_get_rtp(endp, conn_id); + if (!conn) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x unable to allocate RTP connection\n", + ENDPOINT_NUMBER(endp)); + goto error2; + + } + + if (mgcp_parse_conn_mode(mode, endp, conn->conn) != 0) { + error_code = 517; + goto error2; + } + + /* Annotate Osmux circuit ID and set it to negotiating state until this + * is fully set up from the dummy load. */ + conn->osmux.state = OSMUX_STATE_DISABLED; + if (osmux_cid >= 0) { + conn->osmux.cid = osmux_cid; + conn->osmux.state = OSMUX_STATE_NEGOTIATING; + } else if (endp->cfg->osmux == OSMUX_USAGE_ONLY) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x osmux only and no osmux offered\n", + ENDPOINT_NUMBER(endp)); + goto error2; + } + + /* set up RTP media parameters */ + if (have_sdp) + mgcp_parse_sdp_data(endp, conn, p); + else if (endp->local_options.codec) + mgcp_set_audio_info(p->cfg, &conn->end.codec, + PTYPE_UNDEFINED, endp->local_options.codec); + conn->end.fmtp_extra = talloc_strdup(tcfg->endpoints, + tcfg->audio_fmtp_extra); + + if (p->cfg->force_ptime) { + conn->end.packet_duration_ms = p->cfg->force_ptime; + conn->end.force_output_ptime = 1; + } + + mgcp_rtp_end_config(endp, 0, &conn->end); + + if (allocate_port(endp, conn) != 0) { + goto error2; + } + + if (setup_rtp_processing(endp, conn) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x could not start RTP processing!\n", + ENDPOINT_NUMBER(endp)); + goto error2; + } + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_CRCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, + "CRCX: endpoint:%x CRCX rejected by policy\n", + ENDPOINT_NUMBER(endp)); + mgcp_release_endp(endp); + return create_err_response(endp, 400, "CRCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + LOGP(DLMGCP, LOGL_DEBUG, + "CRCX: endpoint:%x Creating connection: CI: %u port: %u\n", + ENDPOINT_NUMBER(endp), conn->conn->id, conn->end.local_port); + if (p->cfg->change_cb) + p->cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX); + + /* Send dummy packet, see also comments in mgcp_keepalive_timer_cb() */ + OSMO_ASSERT(tcfg->keepalive_interval >= MGCP_KEEPALIVE_ONCE); + if (conn->conn->mode & MGCP_CONN_RECV_ONLY + && tcfg->keepalive_interval != MGCP_KEEPALIVE_NEVER) + send_dummy(endp, conn); + + LOGP(DLMGCP, LOGL_NOTICE, + "CRCX: endpoint:%x connection successfully created\n", + ENDPOINT_NUMBER(endp)); + return create_response_with_sdp(endp, conn, "CRCX", p->trans); +error2: + mgcp_release_endp(endp); + LOGP(DLMGCP, LOGL_NOTICE, + "CRCX: endpoint:%x unable to create connection resource error\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, error_code, "CRCX", p->trans); +} + +/* MDCX command handler, processes the received command */ +static struct msgb *handle_modify_con(struct mgcp_parse_data *p) +{ + struct mgcp_endpoint *endp = p->endp; + int error_code = 500; + int silent = 0; + int have_sdp = 0; + char *line; + const char *ci = NULL; + const char *local_options = NULL; + const char *mode = NULL; + struct mgcp_conn_rtp *conn = NULL; + uint32_t conn_id; + + LOGP(DLMGCP, LOGL_NOTICE, "MDCX: modifying existing connection ...\n"); + + if (p->found != 0) + return create_err_response(NULL, 510, "MDCX", p->trans); + + if (llist_count(&endp->conns) <= 0) { + LOGP(DLMGCP, LOGL_ERROR, + "MDCX: endpoint:%x endpoint is not holding a connection.\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "MDCX", p->trans); + } + + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'C': + if (mgcp_verify_call_id(endp, line + 3) != 0) + goto error3; + break; + case 'I': + ci = (const char *)line + 3; + if (mgcp_verify_ci(endp, ci) != 0) + goto error3; + break; + case 'L': + local_options = (const char *)line + 3; + break; + case 'M': + mode = (const char *)line + 3; + break; + case 'Z': + silent = strcmp("noanswer", line + 3) == 0; + break; + case '\0': + have_sdp = 1; + goto mgcp_header_done; + break; + default: + LOGP(DLMGCP, LOGL_NOTICE, + "MDCX: endpoint:%x Unhandled MGCP option: '%c'/%d\n", + ENDPOINT_NUMBER(endp), line[0], line[0]); + break; + } + } + +mgcp_header_done: + if (mgcp_parse_ci(&conn_id, ci)) { + LOGP(DLMGCP, LOGL_ERROR, + "MDCX: endpoint:%x insufficient parameters, missing ci (connectionIdentifier)\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "MDCX", p->trans); + } + + conn = mgcp_conn_get_rtp(endp, conn_id); + if (!conn) + return create_err_response(endp, 400, "MDCX", p->trans); + + if (mode) { + if (mgcp_parse_conn_mode(mode, endp, conn->conn) != 0) { + error_code = 517; + goto error3; + } + } else + conn->conn->mode = conn->conn->mode_orig; + + if (have_sdp) + mgcp_parse_sdp_data(endp, conn, p); + + set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, + local_options); + + if (!have_sdp && endp->local_options.codec) + mgcp_set_audio_info(p->cfg, &conn->end.codec, + PTYPE_UNDEFINED, endp->local_options.codec); + + if (setup_rtp_processing(endp, conn) != 0) + goto error3; + + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_MDCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, + "MDCX: endpoint:%x rejected by policy\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(endp, 400, "MDCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + LOGP(DLMGCP, LOGL_DEBUG, + "MDCX: endpoint:%x defered by policy\n", + ENDPOINT_NUMBER(endp)); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + mgcp_rtp_end_config(endp, 1, &conn->end); + + /* modify */ + LOGP(DLMGCP, LOGL_DEBUG, + "MDCX: endpoint:%x modified conn:%s\n", + ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn)); + if (p->cfg->change_cb) + p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_MDCX); + + /* Send dummy packet, see also comments in mgcp_keepalive_timer_cb() */ + OSMO_ASSERT(endp->tcfg->keepalive_interval >= MGCP_KEEPALIVE_ONCE); + if (conn->conn->mode & MGCP_CONN_RECV_ONLY + && endp->tcfg->keepalive_interval != MGCP_KEEPALIVE_NEVER) + send_dummy(endp, conn); + + if (silent) + goto out_silent; + + LOGP(DLMGCP, LOGL_NOTICE, + "MDCX: endpoint:%x connection successfully modified\n", + ENDPOINT_NUMBER(endp)); + return create_response_with_sdp(endp, conn, "MDCX", p->trans); +error3: + return create_err_response(endp, error_code, "MDCX", p->trans); + +out_silent: + LOGP(DLMGCP, LOGL_DEBUG, "MDCX: endpoint:%x silent exit\n", + ENDPOINT_NUMBER(endp)); + return NULL; +} + +/* DLCX command handler, processes the received command */ +static struct msgb *handle_delete_con(struct mgcp_parse_data *p) +{ + struct mgcp_endpoint *endp = p->endp; + int error_code = 400; + int silent = 0; + char *line; + char stats[1048]; + const char *ci = NULL; + struct mgcp_conn_rtp *conn = NULL; + uint32_t conn_id; + + LOGP(DLMGCP, LOGL_NOTICE, + "DLCX: endpoint:%x deleting connection ...\n", + ENDPOINT_NUMBER(endp)); + + if (p->found != 0) + return create_err_response(NULL, error_code, "DLCX", p->trans); + + if (llist_count(&endp->conns) <= 0) { + LOGP(DLMGCP, LOGL_ERROR, + "DLCX: endpoint:%x endpoint is not holding a connection.\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "DLCX", p->trans); + } + + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'C': + if (mgcp_verify_call_id(endp, line + 3) != 0) + goto error3; + break; + case 'I': + ci = (const char *)line + 3; + if (mgcp_verify_ci(endp, ci) != 0) + goto error3; + break; + case 'Z': + silent = strcmp("noanswer", line + 3) == 0; + break; + default: + LOGP(DLMGCP, LOGL_NOTICE, + "DLCX: endpoint:%x Unhandled MGCP option: '%c'/%d\n", + ENDPOINT_NUMBER(endp), line[0], line[0]); + break; + } + } + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_DLCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, + "DLCX: endpoint:%x rejected by policy\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(endp, 400, "DLCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + /* When no connection id is supplied, we will interpret this as a + * wildcarded DLCX and drop all connections at once. (See also + * RFC3435 Section F.7) */ + if (!ci) { + LOGP(DLMGCP, LOGL_NOTICE, + "DLCX: endpoint:%x missing ci (connectionIdentifier), will remove all connections at once\n", + ENDPOINT_NUMBER(endp)); + + mgcp_release_endp(endp); + + /* Note: In this case we do not return any statistics, + * as we assume that the client is not interested in + * this case. */ + return create_ok_response(endp, 200, "DLCX", p->trans); + } + + /* Parse the connection id */ + if (mgcp_parse_ci(&conn_id, ci)) { + LOGP(DLMGCP, LOGL_ERROR, + "DLCX: endpoint:%x insufficient parameters, invalid ci (connectionIdentifier)\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "DLCX", p->trans); + } + + /* Find the connection */ + conn = mgcp_conn_get_rtp(endp, conn_id); + if (!conn) + goto error3; + + /* save the statistics of the current connection */ + mgcp_format_stats(stats, sizeof(stats), conn->conn); + + /* delete connection */ + LOGP(DLMGCP, LOGL_DEBUG, "DLCX: endpoint:%x deleting conn:%s\n", + ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn)); + mgcp_conn_free(endp, conn_id); + LOGP(DLMGCP, LOGL_NOTICE, + "DLCX: endpoint:%x connection successfully deleted\n", + ENDPOINT_NUMBER(endp)); + + /* When all connections are closed, the endpoint will be released + * in order to be ready to be used by another call. */ + if (llist_count(&endp->conns) <= 0) { + mgcp_release_endp(endp); + LOGP(DLMGCP, LOGL_DEBUG, + "DLCX: endpoint:%x endpoint released\n", + ENDPOINT_NUMBER(endp)); + } + + if (p->cfg->change_cb) + p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_DLCX); + + if (silent) + goto out_silent; + return create_ok_resp_with_param(endp, 250, "DLCX", p->trans, stats); + +error3: + return create_err_response(endp, error_code, "DLCX", p->trans); + +out_silent: + LOGP(DLMGCP, LOGL_DEBUG, "DLCX: endpoint:%x silent exit\n", + ENDPOINT_NUMBER(endp)); + return NULL; +} + +/* RSIP command handler, processes the received command */ +static struct msgb *handle_rsip(struct mgcp_parse_data *p) +{ + /* TODO: Also implement the resetting of a specific endpoint + * to make mgcp_send_reset_ep() work. Currently this will call + * mgcp_rsip_cb() in mgw_main.c, which sets reset_endpoints=1 + * to make read_call_agent() reset all endpoints when called + * next time. In order to selectively reset endpoints some + * mechanism to distinguish which endpoint shall be resetted + * is needed */ + + LOGP(DLMGCP, LOGL_NOTICE, "RSIP: resetting all endpoints ...\n"); + + if (p->found != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "RSIP: failed to find the endpoint.\n"); + return NULL; + } + + if (p->cfg->reset_cb) + p->cfg->reset_cb(p->endp->tcfg); + return NULL; +} + +static char extract_tone(const char *line) +{ + const char *str = strstr(line, "D/"); + if (!str) + return CHAR_MAX; + + return str[2]; +} + +/* This can request like DTMF detection and forward, fax detection... it + * can also request when the notification should be send and such. We don't + * do this right now. */ +static struct msgb *handle_noti_req(struct mgcp_parse_data *p) +{ + int res = 0; + char *line; + char tone = CHAR_MAX; + + LOGP(DLMGCP, LOGL_NOTICE, "RQNT: processing request for notification ...\n"); + + if (p->found != 0) + return create_err_response(NULL, 400, "RQNT", p->trans); + + for_each_line(line, p->save) { + switch (line[0]) { + case 'S': + tone = extract_tone(line); + break; + } + } + + /* we didn't see a signal request with a tone */ + if (tone == CHAR_MAX) + return create_ok_response(p->endp, 200, "RQNT", p->trans); + + if (p->cfg->rqnt_cb) + res = p->cfg->rqnt_cb(p->endp, tone); + + return res == 0 ? + create_ok_response(p->endp, 200, "RQNT", p->trans) : + create_err_response(p->endp, res, "RQNT", p->trans); +} + +/* Connection keepalive timer, will take care that dummy packets are send + * regulary, so that NAT connections stay open */ +static void mgcp_keepalive_timer_cb(void *_tcfg) +{ + struct mgcp_trunk_config *tcfg = _tcfg; + struct mgcp_conn *conn; + int i; + + LOGP(DLMGCP, LOGL_DEBUG, "triggered trunk %d keepalive timer\n", + tcfg->trunk_nr); + + /* Do not accept invalid configuration values + * valid is MGCP_KEEPALIVE_NEVER, MGCP_KEEPALIVE_ONCE and + * values greater 0 */ + OSMO_ASSERT(tcfg->keepalive_interval >= MGCP_KEEPALIVE_ONCE); + + /* The dummy packet functionality has been disabled, we will exit + * immediately, no further timer is scheduled, which means we will no + * longer send dummy packets even when we did before */ + if (tcfg->keepalive_interval == MGCP_KEEPALIVE_NEVER) + return; + + /* In cases where only one dummy packet is sent, we do not need + * the timer since the functions that handle the CRCX and MDCX are + * triggering the sending of the dummy packet. So we behave like in + * the MGCP_KEEPALIVE_NEVER case */ + if (tcfg->keepalive_interval == MGCP_KEEPALIVE_ONCE) + return; + + /* Send walk over all endpoints and send out dummy packets through + * every connection present on each endpoint */ + for (i = 1; i < tcfg->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &tcfg->endpoints[i]; + llist_for_each_entry(conn, &endp->conns, entry) { + if (conn->mode == MGCP_CONN_RECV_ONLY) + send_dummy(endp, &conn->u.rtp); + } + } + + /* Schedule the keepalive timer for the next round */ + LOGP(DLMGCP, LOGL_DEBUG, "rescheduling trunk %d keepalive timer\n", + tcfg->trunk_nr); + osmo_timer_schedule(&tcfg->keepalive_timer, tcfg->keepalive_interval, + 0); +} + +void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval) +{ + tcfg->keepalive_interval = interval; + osmo_timer_setup(&tcfg->keepalive_timer, mgcp_keepalive_timer_cb, tcfg); + + if (interval <= 0) + osmo_timer_del(&tcfg->keepalive_timer); + else + osmo_timer_schedule(&tcfg->keepalive_timer, + tcfg->keepalive_interval, 0); +} + +/*! allocate configuration with default values. + * (called once at startup by main function) */ +struct mgcp_config *mgcp_config_alloc(void) +{ + struct mgcp_config *cfg; + + cfg = talloc_zero(NULL, struct mgcp_config); + if (!cfg) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to allocate config.\n"); + return NULL; + } + + cfg->net_ports.range_start = RTP_PORT_DEFAULT_RANGE_START; + cfg->net_ports.range_end = RTP_PORT_DEFAULT_RANGE_END; + cfg->net_ports.last_port = cfg->net_ports.range_start; + + cfg->source_port = 2427; + cfg->source_addr = talloc_strdup(cfg, "0.0.0.0"); + cfg->osmux_addr = talloc_strdup(cfg, "0.0.0.0"); + + cfg->rtp_processing_cb = &mgcp_rtp_processing_default; + cfg->setup_rtp_processing_cb = &mgcp_setup_rtp_processing_default; + + cfg->get_net_downlink_format_cb = &mgcp_get_net_downlink_format_default; + + /* default trunk handling */ + cfg->trunk.cfg = cfg; + cfg->trunk.trunk_nr = 0; + cfg->trunk.trunk_type = MGCP_TRUNK_VIRTUAL; + cfg->trunk.audio_name = talloc_strdup(cfg, "AMR/8000"); + cfg->trunk.audio_payload = 126; + cfg->trunk.audio_send_ptime = 1; + cfg->trunk.audio_send_name = 1; + cfg->trunk.omit_rtcp = 0; + mgcp_trunk_set_keepalive(&cfg->trunk, MGCP_KEEPALIVE_ONCE); + + INIT_LLIST_HEAD(&cfg->trunks); + + return cfg; +} + +/*! allocate configuration with default values. + * (called once at startup by VTY) + * \param[in] cfg mgcp configuration + * \param[in] nr trunk number + * \returns pointer to allocated trunk configuration */ +struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + trunk = talloc_zero(cfg, struct mgcp_trunk_config); + if (!trunk) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to allocate.\n"); + return NULL; + } + + trunk->cfg = cfg; + trunk->trunk_type = MGCP_TRUNK_E1; + trunk->trunk_nr = nr; + trunk->audio_name = talloc_strdup(cfg, "AMR/8000"); + trunk->audio_payload = 126; + trunk->audio_send_ptime = 1; + trunk->audio_send_name = 1; + trunk->number_endpoints = 33; + trunk->omit_rtcp = 0; + mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE); + llist_add_tail(&trunk->entry, &cfg->trunks); + return trunk; +} + +/*! get trunk configuration by trunk number (index). + * \param[in] cfg mgcp configuration + * \param[in] index trunk number + * \returns pointer to trunk configuration, NULL on error */ +struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &cfg->trunks, entry) + if (trunk->trunk_nr == index) + return trunk; + + return NULL; +} + +/*! allocate endpoints and set default values. + * (called once at startup by VTY) + * \param[in] tcfg trunk configuration + * \returns 0 on success, -1 on failure */ +int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg) +{ + int i; + + tcfg->endpoints = _talloc_zero_array(tcfg->cfg, + sizeof(struct mgcp_endpoint), + tcfg->number_endpoints, + "endpoints"); + if (!tcfg->endpoints) + return -1; + + for (i = 0; i < tcfg->number_endpoints; ++i) { + INIT_LLIST_HEAD(&tcfg->endpoints[i].conns); + tcfg->endpoints[i].cfg = tcfg->cfg; + tcfg->endpoints[i].tcfg = tcfg; + + /* NOTE: Currently all endpoints are of type RTP, this will + * change when new variations are implemented */ + tcfg->endpoints[i].type = &ep_typeset.rtp; + } + + return 0; +} + +/*! relase endpoint, all open connections are closed. + * \param[in] endp endpoint to release */ +void mgcp_release_endp(struct mgcp_endpoint *endp) +{ + LOGP(DLMGCP, LOGL_DEBUG, "Releasing endpoint:%x\n", + ENDPOINT_NUMBER(endp)); + + /* Normally this function should only be called wehen + * all connections have been removed already. In case + * that there are still connections open (e.g. when + * RSIP is executed), free them all at once. */ + mgcp_conn_free_all(endp); + + /* Reset endpoint parameters and states */ + talloc_free(endp->callid); + endp->callid = NULL; + talloc_free(endp->local_options.string); + endp->local_options.string = NULL; + talloc_free(endp->local_options.codec); + endp->local_options.codec = NULL; +} + +static int send_agent(struct mgcp_config *cfg, const char *buf, int len) +{ + return write(cfg->gw_fd.bfd.fd, buf, len); +} + +/*! Reset all endpoints by sending RSIP message to self. + * (called by VTY) + * \param[in] endp trunk endpoint + * \param[in] endpoint number + * \returns 0 on success, -1 on error */ +int mgcp_send_reset_all(struct mgcp_config *cfg) +{ + int rc; + + static const char mgcp_reset[] = { + "RSIP 1 *@mgw MGCP 1.0\r\n" + }; + + rc = send_agent(cfg, mgcp_reset, sizeof mgcp_reset - 1); + if (rc <= 0) + return -1; + + return 0; +} + +/*! Reset a single endpoint by sending RSIP message to self. + * (called by VTY) + * \param[in] endp trunk endpoint + * \param[in] endpoint number + * \returns 0 on success, -1 on error */ +int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint) +{ + char buf[128]; + int len; + int rc; + + len = snprintf(buf, sizeof(buf), + "RSIP 39 %x@mgw MGCP 1.0\r\n", endpoint); + if (len < 0) + return -1; + + buf[sizeof(buf) - 1] = '\0'; + + rc = send_agent(endp->cfg, buf, len); + if (rc <= 0) + return -1; + + return 0; +} diff --git a/src/libosmo-mgcp/mgcp_sdp.c b/src/libosmo-mgcp/mgcp_sdp.c new file mode 100644 index 0000000..f45d6e7 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_sdp.c @@ -0,0 +1,409 @@ +/* + * Some SDP file parsing... + * + * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/msgb.h> +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/mgcp_msg.h> + +#include <errno.h> + +struct sdp_rtp_map { + /* the type */ + int payload_type; + /* null, static or later dynamic codec name */ + char *codec_name; + /* A pointer to the original line for later parsing */ + char *map_line; + + int rate; + int channels; +}; + +/*! Set codec configuration depending on payload type and codec name. + * \param[in] ctx talloc context. + * \param[out] codec configuration (caller provided memory). + * \param[in] payload_type codec type id (e.g. 3 for GSM, -1 when undefined). + * \param[in] audio_name audio codec name (e.g. "GSM/8000/1"). + * \returns 0 on success, -1 on failure. */ +int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, + int payload_type, const char *audio_name) +{ + int rate = codec->rate; + int channels = codec->channels; + char audio_codec[64]; + + talloc_free(codec->subtype_name); + codec->subtype_name = NULL; + talloc_free(codec->audio_name); + codec->audio_name = NULL; + + if (payload_type != PTYPE_UNDEFINED) + codec->payload_type = payload_type; + + if (!audio_name) { + switch (payload_type) { + case 0: + audio_name = "PCMU/8000/1"; + break; + case 3: + audio_name = "GSM/8000/1"; + break; + case 8: + audio_name = "PCMA/8000/1"; + break; + case 18: + audio_name = "G729/8000/1"; + break; + default: + /* Payload type is unknown, don't change rate and + * channels. */ + /* TODO: return value? */ + return 0; + } + } + + if (sscanf(audio_name, "%63[^/]/%d/%d", + audio_codec, &rate, &channels) < 1) + return -EINVAL; + + codec->rate = rate; + codec->channels = channels; + codec->subtype_name = talloc_strdup(ctx, audio_codec); + codec->audio_name = talloc_strdup(ctx, audio_name); + + if (!strcmp(audio_codec, "G729")) { + codec->frame_duration_num = 10; + codec->frame_duration_den = 1000; + } else { + codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; + codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; + } + + if (payload_type < 0) { + payload_type = 96; + if (rate == 8000 && channels == 1) { + if (!strcmp(audio_codec, "GSM")) + payload_type = 3; + else if (!strcmp(audio_codec, "PCMA")) + payload_type = 8; + else if (!strcmp(audio_codec, "PCMU")) + payload_type = 0; + else if (!strcmp(audio_codec, "G729")) + payload_type = 18; + } + + codec->payload_type = payload_type; + } + + if (channels != 1) + LOGP(DLMGCP, LOGL_NOTICE, + "Channels != 1 in SDP: '%s'\n", audio_name); + + return 0; +} + +static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) +{ + int i; + + for (i = 0; i < used; ++i) { + switch (codecs[i].payload_type) { + case 0: + codecs[i].codec_name = "PCMU"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 3: + codecs[i].codec_name = "GSM"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 8: + codecs[i].codec_name = "PCMA"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 18: + codecs[i].codec_name = "G729"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + } + } +} + +static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, + int payload, const char *audio_name) +{ + int i; + + for (i = 0; i < used; ++i) { + char audio_codec[64]; + int rate = -1; + int channels = -1; + if (codecs[i].payload_type != payload) + continue; + if (sscanf(audio_name, "%63[^/]/%d/%d", + audio_codec, &rate, &channels) < 1) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to parse '%s'\n", + audio_name); + continue; + } + + codecs[i].map_line = talloc_strdup(ctx, audio_name); + codecs[i].codec_name = talloc_strdup(ctx, audio_codec); + codecs[i].rate = rate; + codecs[i].channels = channels; + return; + } + + LOGP(DLMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload, + audio_name); +} + +/* Check if the codec matches what is set up in the trunk config */ +static int is_codec_compatible(const struct mgcp_endpoint *endp, + const struct sdp_rtp_map *codec) +{ + char *codec_str; + char audio_codec[64]; + + if (!codec->codec_name) + return 0; + + /* GSM, GSM/8000 and GSM/8000/1 should all be compatible... + * let's go by name first. */ + codec_str = endp->tcfg->audio_name; + if (sscanf(codec_str, "%63[^/]/%*d/%*d", audio_codec) < 1) + return 0; + + return strcasecmp(audio_codec, codec->codec_name) == 0; +} + +/*! Analyze SDP input string. + * \param[in] endp trunk endpoint. + * \param[out] conn associated rtp connection. + * \param[out] caller provided memory to store the parsing results. + * \returns 0 on success, -1 on failure. + * + * Note: In conn (conn->end) the function returns the packet duration, + * the rtp port and the rtcp port */ +int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, + struct mgcp_conn_rtp *conn, + struct mgcp_parse_data *p) +{ + struct sdp_rtp_map codecs[10]; + int codecs_used = 0; + char *line; + int maxptime = -1; + int i; + int codecs_assigned = 0; + void *tmp_ctx = talloc_new(NULL); + struct mgcp_rtp_end *rtp; + + int payload; + int ptime, ptime2 = 0; + char audio_name[64]; + int port, rc; + char ipv4[16]; + + OSMO_ASSERT(endp); + OSMO_ASSERT(conn); + OSMO_ASSERT(p); + + rtp = &conn->end; + memset(&codecs, 0, sizeof(codecs)); + + for_each_line(line, p->save) { + switch (line[0]) { + case 'o': + case 's': + case 't': + case 'v': + /* skip these SDP attributes */ + break; + case 'a': + if (sscanf(line, "a=rtpmap:%d %63s", + &payload, audio_name) == 2) { + codecs_update(tmp_ctx, codecs, + codecs_used, payload, audio_name); + } else + if (sscanf + (line, "a=ptime:%d-%d", &ptime, &ptime2) >= 1) { + if (ptime2 > 0 && ptime2 != ptime) + rtp->packet_duration_ms = 0; + else + rtp->packet_duration_ms = ptime; + } else if (sscanf(line, "a=maxptime:%d", &ptime2) + == 1) { + maxptime = ptime2; + } + break; + case 'm': + rc = sscanf(line, + "m=audio %d RTP/AVP %d %d %d %d %d %d %d %d %d %d", + &port, &codecs[0].payload_type, + &codecs[1].payload_type, + &codecs[2].payload_type, + &codecs[3].payload_type, + &codecs[4].payload_type, + &codecs[5].payload_type, + &codecs[6].payload_type, + &codecs[7].payload_type, + &codecs[8].payload_type, + &codecs[9].payload_type); + if (rc >= 2) { + rtp->rtp_port = htons(port); + rtp->rtcp_port = htons(port + 1); + codecs_used = rc - 1; + codecs_initialize(tmp_ctx, codecs, codecs_used); + } + break; + case 'c': + + if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) { + inet_aton(ipv4, &rtp->addr); + } + break; + default: + if (p->endp) + LOGP(DLMGCP, LOGL_NOTICE, + "Unhandled SDP option: '%c'/%d on 0x%x\n", + line[0], line[0], + ENDPOINT_NUMBER(p->endp)); + else + LOGP(DLMGCP, LOGL_NOTICE, + "Unhandled SDP option: '%c'/%d\n", + line[0], line[0]); + break; + } + } + + /* Now select the primary and alt_codec */ + for (i = 0; i < codecs_used && codecs_assigned < 2; ++i) { + struct mgcp_rtp_codec *codec = codecs_assigned == 0 ? + &rtp->codec : &rtp->alt_codec; + + if (endp->tcfg->no_audio_transcoding && + !is_codec_compatible(endp, &codecs[i])) { + LOGP(DLMGCP, LOGL_NOTICE, "Skipping codec %s\n", + codecs[i].codec_name); + continue; + } + + mgcp_set_audio_info(p->cfg, codec, + codecs[i].payload_type, codecs[i].map_line); + codecs_assigned += 1; + } + + if (codecs_assigned > 0) { + /* TODO/XXX: Store this per codec and derive it on use */ + if (maxptime >= 0 && maxptime * rtp->codec.frame_duration_den > + rtp->codec.frame_duration_num * 1500) { + /* more than 1 frame */ + rtp->packet_duration_ms = 0; + } + + LOGP(DLMGCP, LOGL_NOTICE, + "Got media info via SDP: port %d, payload %d (%s), " + "duration %d, addr %s\n", + ntohs(rtp->rtp_port), rtp->codec.payload_type, + rtp->codec.subtype_name ? rtp-> + codec.subtype_name : "unknown", rtp->packet_duration_ms, + inet_ntoa(rtp->addr)); + } + + talloc_free(tmp_ctx); + return codecs_assigned > 0; +} + +/*! Generate SDP response string. + * \param[in] endp trunk endpoint. + * \param[in] conn associated rtp connection. + * \param[out] sdp msg buffer to append resulting SDP string data. + * \param[in] addr IPV4 address string (e.g. 192.168.100.1). + * \returns 0 on success, -1 on failure. */ +int mgcp_write_response_sdp(const struct mgcp_endpoint *endp, + const struct mgcp_conn_rtp *conn, struct msgb *sdp, + const char *addr) +{ + const char *fmtp_extra; + const char *audio_name; + int payload_type; + int rc; + + OSMO_ASSERT(endp); + OSMO_ASSERT(conn); + OSMO_ASSERT(sdp); + OSMO_ASSERT(addr); + + /* FIXME: constify endp and conn args in get_net_donwlink_format_cb() */ + endp->cfg->get_net_downlink_format_cb((struct mgcp_endpoint *)endp, + &payload_type, &audio_name, + &fmtp_extra, + (struct mgcp_conn_rtp *)conn); + + rc = msgb_printf(sdp, + "v=0\r\n" + "o=- %u 23 IN IP4 %s\r\n" + "s=-\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n", conn->conn->id, addr, addr); + + if (rc < 0) + goto buffer_too_small; + + if (payload_type >= 0) { + rc = msgb_printf(sdp, "m=audio %d RTP/AVP %d\r\n", + conn->end.local_port, payload_type); + if (rc < 0) + goto buffer_too_small; + + if (audio_name && endp->tcfg->audio_send_name) { + rc = msgb_printf(sdp, "a=rtpmap:%d %s\r\n", + payload_type, audio_name); + + if (rc < 0) + goto buffer_too_small; + } + + if (fmtp_extra) { + rc = msgb_printf(sdp, "%s\r\n", fmtp_extra); + + if (rc < 0) + goto buffer_too_small; + } + } + if (conn->end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) { + rc = msgb_printf(sdp, "a=ptime:%u\r\n", + conn->end.packet_duration_ms); + if (rc < 0) + goto buffer_too_small; + } + + return 0; + +buffer_too_small: + LOGP(DLMGCP, LOGL_ERROR, "SDP messagebuffer too small\n"); + return -1; +} diff --git a/src/libosmo-mgcp/mgcp_stat.c b/src/libosmo-mgcp/mgcp_stat.c new file mode 100644 index 0000000..b84f5f2 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_stat.c @@ -0,0 +1,128 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The statistics generator */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/mgcp/mgcp_stat.h> +#include <limits.h> + +/* Helper function for mgcp_format_stats_rtp() to calculate packet loss */ +void calc_loss(struct mgcp_rtp_state *state, + struct mgcp_rtp_end *end, uint32_t *expected, + int *loss) +{ + *expected = state->stats_cycles + state->stats_max_seq; + *expected = *expected - state->stats_base_seq + 1; + + if (!state->stats_initialized) { + *expected = 0; + *loss = 0; + return; + } + + /* + * Make sure the sign is correct and use the biggest + * positive/negative number that fits. + */ + *loss = *expected - end->packets_rx; + if (*expected < end->packets_rx) { + if (*loss > 0) + *loss = INT_MIN; + } else { + if (*loss < 0) + *loss = INT_MAX; + } +} + +/* Helper function for mgcp_format_stats_rtp() to calculate jitter */ +uint32_t calc_jitter(struct mgcp_rtp_state *state) +{ + if (!state->stats_initialized) + return 0; + return state->stats_jitter >> 4; +} + +/* Generate statistics for an RTP connection */ +static void mgcp_format_stats_rtp(char *str, size_t str_len, + struct mgcp_conn_rtp *conn) +{ + uint32_t expected, jitter; + int ploss; + int nchars; + + calc_loss(&conn->state, &conn->end, &expected, &ploss); + jitter = calc_jitter(&conn->state); + + nchars = snprintf(str, str_len, + "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", + conn->end.packets_tx, conn->end.octets_tx, + conn->end.packets_rx, conn->end.octets_rx, + ploss, jitter); + if (nchars < 0 || nchars >= str_len) + goto truncate; + + str += nchars; + str_len -= nchars; + + /* Error Counter */ + nchars = snprintf(str, str_len, + "\r\nX-Osmo-CP: EC TI=%u, TO=%u", + conn->state.in_stream.err_ts_counter, + conn->state.out_stream.err_ts_counter); + if (nchars < 0 || nchars >= str_len) + goto truncate; + + str += nchars; + str_len -= nchars; + + if (conn->osmux.state == OSMUX_STATE_ENABLED) { + snprintf(str, str_len, + "\r\nX-Osmux-ST: CR=%u, BR=%u", + conn->osmux.stats.chunks, conn->osmux.stats.octets); + } + +truncate: + str[str_len - 1] = '\0'; +} + +/*! format statistics into an mgcp parameter string. + * \param[out] str resulting string + * \param[in] str_len length of the string buffer + * \param[in] conn connection to evaluate */ +void mgcp_format_stats(char *str, size_t str_len, struct mgcp_conn *conn) +{ + memset(str, 0, str_len); + if (!conn) + return; + + /* NOTE: At the moment we only support generating statistics for + * RTP connections. However, in the future we may also want to + * generate statistics for other connection types as well. Lets + * keep this option open: */ + switch (conn->type) { + case MGCP_CONN_TYPE_RTP: + mgcp_format_stats_rtp(str, str_len, &conn->u.rtp); + break; + default: + break; + } +} diff --git a/src/libosmo-mgcp/mgcp_vty.c b/src/libosmo-mgcp/mgcp_vty.c new file mode 100644 index 0000000..7ff1fdd --- /dev/null +++ b/src/libosmo-mgcp/mgcp_vty.c @@ -0,0 +1,1306 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2014 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/talloc.h> +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/mgcp/mgcp_common.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/vty.h> +#include <osmocom/mgcp/mgcp_conn.h> + +#include <string.h> + +#define RTCP_OMIT_STR "Drop RTCP packets in both directions\n" +#define RTP_PATCH_STR "Modify RTP packet header in both directions\n" +#define RTP_KEEPALIVE_STR "Send dummy UDP packet to net RTP destination\n" + +static struct mgcp_config *g_cfg = NULL; + +static struct mgcp_trunk_config *find_trunk(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + if (nr == 0) + trunk = &cfg->trunk; + else + trunk = mgcp_trunk_num(cfg, nr); + + return trunk; +} + +struct cmd_node mgcp_node = { + MGCP_NODE, + "%s(config-mgcp)# ", + 1, +}; + +struct cmd_node trunk_node = { + TRUNK_NODE, + "%s(config-mgcp-trunk)# ", + 1, +}; + +static int config_write_mgcp(struct vty *vty) +{ + vty_out(vty, "mgcp%s", VTY_NEWLINE); + if (g_cfg->local_ip) + vty_out(vty, " local ip %s%s", g_cfg->local_ip, VTY_NEWLINE); + vty_out(vty, " bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE); + vty_out(vty, " bind port %u%s", g_cfg->source_port, VTY_NEWLINE); + vty_out(vty, " rtp net-range %u %u%s", + g_cfg->net_ports.range_start, g_cfg->net_ports.range_end, + VTY_NEWLINE); + if (g_cfg->net_ports.bind_addr) + vty_out(vty, " rtp net-bind-ip %s%s", + g_cfg->net_ports.bind_addr, VTY_NEWLINE); + if (g_cfg->net_ports.bind_addr_probe) + vty_out(vty, " rtp ip-probing%s", VTY_NEWLINE); + else + vty_out(vty, " no rtp ip-probing%s", VTY_NEWLINE); + vty_out(vty, " rtp ip-dscp %d%s", g_cfg->endp_dscp, VTY_NEWLINE); + if (g_cfg->trunk.keepalive_interval == MGCP_KEEPALIVE_ONCE) + vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE); + else if (g_cfg->trunk.keepalive_interval) + vty_out(vty, " rtp keep-alive %d%s", + g_cfg->trunk.keepalive_interval, VTY_NEWLINE); + else + vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE); + + if (g_cfg->trunk.omit_rtcp) + vty_out(vty, " rtcp-omit%s", VTY_NEWLINE); + else + vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE); + if (g_cfg->trunk.force_constant_ssrc + || g_cfg->trunk.force_aligned_timing) { + vty_out(vty, " %srtp-patch ssrc%s", + g_cfg->trunk.force_constant_ssrc ? "" : "no ", + VTY_NEWLINE); + vty_out(vty, " %srtp-patch timestamp%s", + g_cfg->trunk.force_aligned_timing ? "" : "no ", + VTY_NEWLINE); + } else + vty_out(vty, " no rtp-patch%s", VTY_NEWLINE); + if (g_cfg->trunk.audio_payload != -1) + vty_out(vty, " sdp audio-payload number %d%s", + g_cfg->trunk.audio_payload, VTY_NEWLINE); + if (g_cfg->trunk.audio_name) + vty_out(vty, " sdp audio-payload name %s%s", + g_cfg->trunk.audio_name, VTY_NEWLINE); + if (g_cfg->trunk.audio_fmtp_extra) + vty_out(vty, " sdp audio fmtp-extra %s%s", + g_cfg->trunk.audio_fmtp_extra, VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-ptime%s", + g_cfg->trunk.audio_send_ptime ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-name%s", + g_cfg->trunk.audio_send_name ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " loop %u%s", ! !g_cfg->trunk.audio_loop, VTY_NEWLINE); + vty_out(vty, " number endpoints %u%s", + g_cfg->trunk.number_endpoints - 1, VTY_NEWLINE); + vty_out(vty, " %sallow-transcoding%s", + g_cfg->trunk.no_audio_transcoding ? "no " : "", VTY_NEWLINE); + if (g_cfg->call_agent_addr) + vty_out(vty, " call-agent ip %s%s", g_cfg->call_agent_addr, + VTY_NEWLINE); + if (g_cfg->force_ptime > 0) + vty_out(vty, " rtp force-ptime %d%s", g_cfg->force_ptime, + VTY_NEWLINE); + + switch (g_cfg->osmux) { + case OSMUX_USAGE_ON: + vty_out(vty, " osmux on%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_ONLY: + vty_out(vty, " osmux only%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_OFF: + default: + vty_out(vty, " osmux off%s", VTY_NEWLINE); + break; + } + if (g_cfg->osmux) { + vty_out(vty, " osmux bind-ip %s%s", + g_cfg->osmux_addr, VTY_NEWLINE); + vty_out(vty, " osmux batch-factor %d%s", + g_cfg->osmux_batch, VTY_NEWLINE); + vty_out(vty, " osmux batch-size %u%s", + g_cfg->osmux_batch_size, VTY_NEWLINE); + vty_out(vty, " osmux port %u%s", + g_cfg->osmux_port, VTY_NEWLINE); + vty_out(vty, " osmux dummy %s%s", + g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +static void dump_rtp_end(struct vty *vty, struct mgcp_rtp_state *state, + struct mgcp_rtp_end *end) +{ + struct mgcp_rtp_codec *codec = &end->codec; + + vty_out(vty, + " Timestamp Errs: %d->%d%s" + " Dropped Packets: %d%s" + " Payload Type: %d Rate: %u Channels: %d %s" + " Frame Duration: %u Frame Denominator: %u%s" + " FPP: %d Packet Duration: %u%s" + " FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s" + " Output-Enabled: %d Force-PTIME: %d%s", + state->in_stream.err_ts_counter, + state->out_stream.err_ts_counter, VTY_NEWLINE, + end->dropped_packets, VTY_NEWLINE, + codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE, + codec->frame_duration_num, codec->frame_duration_den, + VTY_NEWLINE, end->frames_per_packet, end->packet_duration_ms, + VTY_NEWLINE, end->fmtp_extra, codec->audio_name, + codec->subtype_name, VTY_NEWLINE, end->output_enabled, + end->force_output_ptime, VTY_NEWLINE); +} + +static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg, + int verbose) +{ + int i; + struct mgcp_conn *conn; + + vty_out(vty, "%s trunk nr %d with %d endpoints:%s", + cfg->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1", + cfg->trunk_nr, cfg->number_endpoints - 1, VTY_NEWLINE); + + if (!cfg->endpoints) { + vty_out(vty, "No endpoints allocated yet.%s", VTY_NEWLINE); + return; + } + + for (i = 1; i < cfg->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &cfg->endpoints[i]; + + vty_out(vty, "Endpoint 0x%.2x:%s", i, VTY_NEWLINE); + + llist_for_each_entry(conn, &endp->conns, entry) { + vty_out(vty, " CONN: %s%s", + mgcp_conn_dump(conn), VTY_NEWLINE); + + if (verbose) { + /* FIXME: Also add verbosity for other + * connection types (E1) as soon as + * the implementation is available */ + if (conn->type == MGCP_CONN_TYPE_RTP) { + dump_rtp_end(vty, &conn->u.rtp.state, + &conn->u.rtp.end); + } + } + } + } +} + +DEFUN(show_mcgp, show_mgcp_cmd, + "show mgcp [stats]", + SHOW_STR + "Display information about the MGCP Media Gateway\n" + "Include Statistics\n") +{ + struct mgcp_trunk_config *trunk; + int show_stats = argc >= 1; + + dump_trunk(vty, &g_cfg->trunk, show_stats); + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) + dump_trunk(vty, trunk, show_stats); + + if (g_cfg->osmux) + vty_out(vty, "Osmux used CID: %d%s", osmux_used_cid(), + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp, cfg_mgcp_cmd, "mgcp", "Configure the MGCP") +{ + vty->node = MGCP_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_local_ip, + cfg_mgcp_local_ip_cmd, + "local ip A.B.C.D", + "Local options for the SDP record\n" + IP_STR "IPv4 Address to use in SDP record\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->local_ip, argv[0]); + return CMD_SUCCESS; +} + +#define BIND_STR "Listen/Bind related socket option\n" +DEFUN(cfg_mgcp_bind_ip, + cfg_mgcp_bind_ip_cmd, + "bind ip A.B.C.D", BIND_STR IP_STR "IPv4 Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->source_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_port, + cfg_mgcp_bind_port_cmd, + "bind port <0-65534>", + BIND_STR "Port information\n" "UDP port to listen for MGCP messages\n") +{ + unsigned int port = atoi(argv[0]); + g_cfg->source_port = port; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_early, + cfg_mgcp_bind_early_cmd, + "bind early (0|1)", + BIND_STR + "Bind local ports on start up\n" "Bind on demand\n" "Bind on startup\n") +{ + vty_out(vty, "bind early is deprecated, remove it from the config.\n"); + return CMD_WARNING; +} + +static void parse_range(struct mgcp_port_range *range, const char **argv) +{ + range->range_start = atoi(argv[0]); + range->range_end = atoi(argv[1]); + range->last_port = g_cfg->net_ports.range_start; +} + +#define RTP_STR "RTP configuration\n" +#define UDP_PORT_STR "UDP Port number\n" +#define NET_START_STR "First UDP port allocated\n" +#define RANGE_START_STR "Start of the range of ports\n" +#define RANGE_END_STR "End of the range of ports\n" + +DEFUN(cfg_mgcp_rtp_net_range, + cfg_mgcp_rtp_net_range_cmd, + "rtp net-range <0-65534> <0-65534>", + RTP_STR "Range of ports to use for the NET side\n" + RANGE_START_STR RANGE_END_STR) +{ + parse_range(&g_cfg->net_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_bind_ip, + cfg_mgcp_rtp_net_bind_ip_cmd, + "rtp net-bind-ip A.B.C.D", + RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->net_ports.bind_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_no_net_bind_ip, + cfg_mgcp_rtp_no_net_bind_ip_cmd, + "no rtp net-bind-ip", + NO_STR RTP_STR "Bind endpoints facing the Network\n" + "Address to bind to\n") +{ + talloc_free(g_cfg->net_ports.bind_addr); + g_cfg->net_ports.bind_addr = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_bind_ip_probing, + cfg_mgcp_rtp_net_bind_ip_probing_cmd, + "rtp ip-probing", + RTP_STR "automatic rtp bind ip selection\n") +{ + g_cfg->net_ports.bind_addr_probe = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_no_net_bind_ip_probing, + cfg_mgcp_rtp_no_net_bind_ip_probing_cmd, + "no rtp ip-probing", + NO_STR RTP_STR "no automatic rtp bind ip selection\n") +{ + g_cfg->net_ports.bind_addr_probe = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_ip_dscp, + cfg_mgcp_rtp_ip_dscp_cmd, + "rtp ip-dscp <0-255>", + RTP_STR + "Apply IP_TOS to the audio stream (including Osmux)\n" "The DSCP value\n") +{ + int dscp = atoi(argv[0]); + g_cfg->endp_dscp = dscp; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_rtp_ip_dscp, cfg_mgcp_rtp_ip_tos_cmd, + "rtp ip-tos <0-255>", + RTP_STR + "Apply IP_TOS to the audio stream\n" "The DSCP value\n") +#define FORCE_PTIME_STR "Force a fixed ptime for packets sent" + DEFUN(cfg_mgcp_rtp_force_ptime, + cfg_mgcp_rtp_force_ptime_cmd, + "rtp force-ptime (10|20|40)", + RTP_STR FORCE_PTIME_STR + "The required ptime (packet duration) in ms\n" "10 ms\n20 ms\n40 ms\n") +{ + g_cfg->force_ptime = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_rtp_force_ptime, + cfg_mgcp_no_rtp_force_ptime_cmd, + "no rtp force-ptime", NO_STR RTP_STR FORCE_PTIME_STR) +{ + g_cfg->force_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_sdp_fmtp_extra, + cfg_mgcp_sdp_fmtp_extra_cmd, + "sdp audio fmtp-extra .NAME", + "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n" + "Extra Information\n") +{ + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_fmtp_extra, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_allow_transcoding, + cfg_mgcp_allow_transcoding_cmd, + "allow-transcoding", "Allow transcoding\n") +{ + g_cfg->trunk.no_audio_transcoding = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_allow_transcoding, + cfg_mgcp_no_allow_transcoding_cmd, + "no allow-transcoding", NO_STR "Allow transcoding\n") +{ + g_cfg->trunk.no_audio_transcoding = 1; + return CMD_SUCCESS; +} + +#define SDP_STR "SDP File related options\n" +#define AUDIO_STR "Audio payload options\n" +DEFUN(cfg_mgcp_sdp_payload_number, + cfg_mgcp_sdp_payload_number_cmd, + "sdp audio-payload number <0-255>", + SDP_STR AUDIO_STR "Number\n" "Payload number\n") +{ + unsigned int payload = atoi(argv[0]); + g_cfg->trunk.audio_payload = payload; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_number, + cfg_mgcp_sdp_payload_number_cmd_old, + "sdp audio payload number <0-255>", + SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload number\n") + + DEFUN(cfg_mgcp_sdp_payload_name, + cfg_mgcp_sdp_payload_name_cmd, + "sdp audio-payload name NAME", + SDP_STR AUDIO_STR "Name\n" "Payload name\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_name, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_name, cfg_mgcp_sdp_payload_name_cmd_old, + "sdp audio payload name NAME", + SDP_STR AUDIO_STR AUDIO_STR "Name\n" "Payload name\n") + + DEFUN(cfg_mgcp_sdp_payload_send_ptime, + cfg_mgcp_sdp_payload_send_ptime_cmd, + "sdp audio-payload send-ptime", + SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n") +{ + g_cfg->trunk.audio_send_ptime = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_sdp_payload_send_ptime, + cfg_mgcp_no_sdp_payload_send_ptime_cmd, + "no sdp audio-payload send-ptime", + NO_STR SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n") +{ + g_cfg->trunk.audio_send_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_sdp_payload_send_name, + cfg_mgcp_sdp_payload_send_name_cmd, + "sdp audio-payload send-name", + SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n") +{ + g_cfg->trunk.audio_send_name = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_sdp_payload_send_name, + cfg_mgcp_no_sdp_payload_send_name_cmd, + "no sdp audio-payload send-name", + NO_STR SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n") +{ + g_cfg->trunk.audio_send_name = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_loop, + cfg_mgcp_loop_cmd, + "loop (0|1)", + "Loop audio for all endpoints on main trunk\n" "Don't Loop\n" "Loop\n") +{ + if (g_cfg->osmux) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + g_cfg->trunk.audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_force_realloc, + cfg_mgcp_force_realloc_cmd, + "force-realloc (0|1)", + "Force endpoint reallocation when the endpoint is still seized\n" + "Don't force reallocation\n" "force reallocation\n") +{ + g_cfg->trunk.force_realloc = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_accept_all, + cfg_mgcp_rtp_accept_all_cmd, + "rtp-accept-all (0|1)", + "Accept all RTP packets, even when the originating IP/Port does not match\n" + "enable filter\n" "disable filter\n") +{ + g_cfg->trunk.rtp_accept_all = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_number_endp, + cfg_mgcp_number_endp_cmd, + "number endpoints <0-65534>", + "Number options\n" "Endpoints available\n" "Number endpoints\n") +{ + /* + 1 as we start counting at one */ + g_cfg->trunk.number_endpoints = atoi(argv[0]) + 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_omit_rtcp, cfg_mgcp_omit_rtcp_cmd, "rtcp-omit", RTCP_OMIT_STR) +{ + g_cfg->trunk.omit_rtcp = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_omit_rtcp, + cfg_mgcp_no_omit_rtcp_cmd, "no rtcp-omit", NO_STR RTCP_OMIT_STR) +{ + g_cfg->trunk.omit_rtcp = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_patch_rtp_ssrc, + cfg_mgcp_patch_rtp_ssrc_cmd, + "rtp-patch ssrc", RTP_PATCH_STR "Force a fixed SSRC\n") +{ + g_cfg->trunk.force_constant_ssrc = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp_ssrc, + cfg_mgcp_no_patch_rtp_ssrc_cmd, + "no rtp-patch ssrc", NO_STR RTP_PATCH_STR "Force a fixed SSRC\n") +{ + g_cfg->trunk.force_constant_ssrc = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_patch_rtp_ts, + cfg_mgcp_patch_rtp_ts_cmd, + "rtp-patch timestamp", RTP_PATCH_STR "Adjust RTP timestamp\n") +{ + g_cfg->trunk.force_aligned_timing = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp_ts, + cfg_mgcp_no_patch_rtp_ts_cmd, + "no rtp-patch timestamp", NO_STR RTP_PATCH_STR "Adjust RTP timestamp\n") +{ + g_cfg->trunk.force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp, + cfg_mgcp_no_patch_rtp_cmd, "no rtp-patch", NO_STR RTP_PATCH_STR) +{ + g_cfg->trunk.force_constant_ssrc = 0; + g_cfg->trunk.force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_keepalive, + cfg_mgcp_rtp_keepalive_cmd, + "rtp keep-alive <1-120>", + RTP_STR RTP_KEEPALIVE_STR "Keep alive interval in secs\n") +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_keepalive_once, + cfg_mgcp_rtp_keepalive_once_cmd, + "rtp keep-alive once", + RTP_STR RTP_KEEPALIVE_STR "Send dummy packet only once after CRCX/MDCX\n") +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, MGCP_KEEPALIVE_ONCE); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_rtp_keepalive, + cfg_mgcp_no_rtp_keepalive_cmd, + "no rtp keep-alive", NO_STR RTP_STR RTP_KEEPALIVE_STR) +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, MGCP_KEEPALIVE_NEVER); + return CMD_SUCCESS; +} + +#define CALL_AGENT_STR "Callagent information\n" +DEFUN(cfg_mgcp_agent_addr, + cfg_mgcp_agent_addr_cmd, + "call-agent ip A.B.C.D", + CALL_AGENT_STR IP_STR "IPv4 Address of the callagent\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->call_agent_addr, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_agent_addr, cfg_mgcp_agent_addr_cmd_old, + "call agent ip A.B.C.D", + CALL_AGENT_STR CALL_AGENT_STR IP_STR + "IPv4 Address of the callagent\n") + + DEFUN(cfg_mgcp_trunk, cfg_mgcp_trunk_cmd, + "trunk <1-64>", "Configure a SS7 trunk\n" "Trunk Nr\n") +{ + struct mgcp_trunk_config *trunk; + int index = atoi(argv[0]); + + trunk = mgcp_trunk_num(g_cfg, index); + if (!trunk) + trunk = mgcp_trunk_alloc(g_cfg, index); + + if (!trunk) { + vty_out(vty, "%%Unable to allocate trunk %u.%s", + index, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->node = TRUNK_NODE; + vty->index = trunk; + return CMD_SUCCESS; +} + +static int config_write_trunk(struct vty *vty) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + vty_out(vty, " trunk %d%s", trunk->trunk_nr, VTY_NEWLINE); + vty_out(vty, " sdp audio-payload number %d%s", + trunk->audio_payload, VTY_NEWLINE); + vty_out(vty, " sdp audio-payload name %s%s", + trunk->audio_name, VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-ptime%s", + trunk->audio_send_ptime ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-name%s", + trunk->audio_send_name ? "" : "no ", VTY_NEWLINE); + + if (trunk->keepalive_interval == MGCP_KEEPALIVE_ONCE) + vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE); + else if (trunk->keepalive_interval) + vty_out(vty, " rtp keep-alive %d%s", + trunk->keepalive_interval, VTY_NEWLINE); + else + vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE); + vty_out(vty, " loop %d%s", trunk->audio_loop, VTY_NEWLINE); + vty_out(vty, " force-realloc %d%s", + trunk->force_realloc, VTY_NEWLINE); + vty_out(vty, " rtp-accept-all %d%s", + trunk->rtp_accept_all, VTY_NEWLINE); + if (trunk->omit_rtcp) + vty_out(vty, " rtcp-omit%s", VTY_NEWLINE); + else + vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE); + if (trunk->force_constant_ssrc || trunk->force_aligned_timing) { + vty_out(vty, " %srtp-patch ssrc%s", + trunk->force_constant_ssrc ? "" : "no ", + VTY_NEWLINE); + vty_out(vty, " %srtp-patch timestamp%s", + trunk->force_aligned_timing ? "" : "no ", + VTY_NEWLINE); + } else + vty_out(vty, " no rtp-patch%s", VTY_NEWLINE); + if (trunk->audio_fmtp_extra) + vty_out(vty, " sdp audio fmtp-extra %s%s", + trunk->audio_fmtp_extra, VTY_NEWLINE); + vty_out(vty, " %sallow-transcoding%s", + trunk->no_audio_transcoding ? "no " : "", VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_fmtp_extra, + cfg_trunk_sdp_fmtp_extra_cmd, + "sdp audio fmtp-extra .NAME", + "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n" + "Extra Information\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(g_cfg, &trunk->audio_fmtp_extra, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_payload_number, + cfg_trunk_payload_number_cmd, + "sdp audio-payload number <0-255>", + SDP_STR AUDIO_STR "Number\n" "Payload Number\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + unsigned int payload = atoi(argv[0]); + + trunk->audio_payload = payload; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_trunk_payload_number, cfg_trunk_payload_number_cmd_old, + "sdp audio payload number <0-255>", + SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload Number\n") + + DEFUN(cfg_trunk_payload_name, + cfg_trunk_payload_name_cmd, + "sdp audio-payload name NAME", + SDP_STR AUDIO_STR "Payload\n" "Payload Name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + + osmo_talloc_replace_string(g_cfg, &trunk->audio_name, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_trunk_payload_name, cfg_trunk_payload_name_cmd_old, + "sdp audio payload name NAME", + SDP_STR AUDIO_STR AUDIO_STR "Payload\n" "Payload Name\n") + + DEFUN(cfg_trunk_loop, + cfg_trunk_loop_cmd, + "loop (0|1)", + "Loop audio for all endpoints on this trunk\n" "Don't Loop\n" "Loop\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + + if (g_cfg->osmux) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + trunk->audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_payload_send_ptime, + cfg_trunk_sdp_payload_send_ptime_cmd, + "sdp audio-payload send-ptime", + SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_ptime = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_sdp_payload_send_ptime, + cfg_trunk_no_sdp_payload_send_ptime_cmd, + "no sdp audio-payload send-ptime", + NO_STR SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_payload_send_name, + cfg_trunk_sdp_payload_send_name_cmd, + "sdp audio-payload send-name", + SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_name = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_sdp_payload_send_name, + cfg_trunk_no_sdp_payload_send_name_cmd, + "no sdp audio-payload send-name", + NO_STR SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_name = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_omit_rtcp, cfg_trunk_omit_rtcp_cmd, "rtcp-omit", RTCP_OMIT_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->omit_rtcp = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_omit_rtcp, + cfg_trunk_no_omit_rtcp_cmd, "no rtcp-omit", NO_STR RTCP_OMIT_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->omit_rtcp = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_patch_rtp_ssrc, + cfg_trunk_patch_rtp_ssrc_cmd, + "rtp-patch ssrc", RTP_PATCH_STR "Force a fixed SSRC\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp_ssrc, + cfg_trunk_no_patch_rtp_ssrc_cmd, + "no rtp-patch ssrc", NO_STR RTP_PATCH_STR "Force a fixed SSRC\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_patch_rtp_ts, + cfg_trunk_patch_rtp_ts_cmd, + "rtp-patch timestamp", RTP_PATCH_STR "Adjust RTP timestamp\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_aligned_timing = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp_ts, + cfg_trunk_no_patch_rtp_ts_cmd, + "no rtp-patch timestamp", NO_STR RTP_PATCH_STR "Adjust RTP timestamp\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp, + cfg_trunk_no_patch_rtp_cmd, "no rtp-patch", NO_STR RTP_PATCH_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 0; + trunk->force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_rtp_keepalive, + cfg_trunk_rtp_keepalive_cmd, + "rtp keep-alive <1-120>", + RTP_STR RTP_KEEPALIVE_STR "Keep-alive interval in secs\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_rtp_keepalive_once, + cfg_trunk_rtp_keepalive_once_cmd, + "rtp keep-alive once", + RTP_STR RTP_KEEPALIVE_STR "Send dummy packet only once after CRCX/MDCX\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_rtp_keepalive, + cfg_trunk_no_rtp_keepalive_cmd, + "no rtp keep-alive", NO_STR RTP_STR RTP_KEEPALIVE_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_allow_transcoding, + cfg_trunk_allow_transcoding_cmd, + "allow-transcoding", "Allow transcoding\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->no_audio_transcoding = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_allow_transcoding, + cfg_trunk_no_allow_transcoding_cmd, + "no allow-transcoding", NO_STR "Allow transcoding\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->no_audio_transcoding = 1; + return CMD_SUCCESS; +} + +DEFUN(loop_conn, + loop_conn_cmd, + "loop-endpoint <0-64> NAME (0|1)", + "Loop a given endpoint\n" "Trunk number\n" + "The name in hex of the endpoint\n" "Disable the loop\n" + "Enable the loop\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + struct mgcp_conn *conn; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Loopback number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + int loop = atoi(argv[2]); + llist_for_each_entry(conn, &endp->conns, entry) { + if (conn->type == MGCP_CONN_TYPE_RTP) + /* Handle it like a MDCX, switch on SSRC patching if enabled */ + mgcp_rtp_end_config(endp, 1, &conn->u.rtp.end); + else { + /* FIXME: Introduce support for other connection (E1) + * types when implementation is available */ + vty_out(vty, "%%Can't enable SSRC patching," + "connection %s is not an RTP connection.%s", + mgcp_conn_dump(conn), VTY_NEWLINE); + } + + if (loop) + conn->mode = MGCP_CONN_LOOPBACK; + else + conn->mode = conn->mode_orig; + } + + return CMD_SUCCESS; +} + +DEFUN(tap_rtp, + tap_rtp_cmd, + "tap-rtp <0-64> ENDPOINT CONN (in|out) A.B.C.D <0-65534>", + "Forward data on endpoint to a different system\n" "Trunk number\n" + "The endpoint in hex\n" + "The connection id in hex\n" + "Forward incoming data\n" + "Forward leaving data\n" + "destination IP of the data\n" "destination port\n") +{ + struct mgcp_rtp_tap *tap; + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + struct mgcp_conn_rtp *conn; + uint32_t conn_id; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + + conn_id = strtoul(argv[2], NULL, 10); + conn = mgcp_conn_get_rtp(endp, conn_id); + if (!conn) { + vty_out(vty, "Conn ID %s/%d is invalid.%s", + argv[2], conn_id, VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(argv[3], "in") == 0) + tap = &conn->tap_in; + else if (strcmp(argv[3], "out") == 0) + tap = &conn->tap_out; + else { + vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE); + return CMD_WARNING; + } + + memset(&tap->forward, 0, sizeof(tap->forward)); + inet_aton(argv[4], &tap->forward.sin_addr); + tap->forward.sin_port = htons(atoi(argv[5])); + tap->enabled = 1; + return CMD_SUCCESS; +} + +DEFUN(free_endp, free_endp_cmd, + "free-endpoint <0-64> NUMBER", + "Free the given endpoint\n" "Trunk number\n" "Endpoint number in hex.\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + mgcp_release_endp(endp); + return CMD_SUCCESS; +} + +DEFUN(reset_endp, reset_endp_cmd, + "reset-endpoint <0-64> NUMBER", + "Reset the given endpoint\n" "Trunk number\n" "Endpoint number in hex.\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + int endp_no, rc; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + rc = mgcp_send_reset_ep(endp, ENDPOINT_NUMBER(endp)); + if (rc < 0) { + vty_out(vty, "Error %d sending reset.%s", rc, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(reset_all_endp, reset_all_endp_cmd, + "reset-all-endpoints", "Reset all endpoints\n") +{ + int rc; + + rc = mgcp_send_reset_all(g_cfg); + if (rc < 0) { + vty_out(vty, "Error %d during endpoint reset.%s", + rc, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +#define OSMUX_STR "RTP multiplexing\n" +DEFUN(cfg_mgcp_osmux, + cfg_mgcp_osmux_cmd, + "osmux (on|off|only)", + OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n") +{ + if (strcmp(argv[0], "off") == 0) { + g_cfg->osmux = OSMUX_USAGE_OFF; + return CMD_SUCCESS; + } + + /* Since OSMUX support is not finished, we do not + * allow to turn it on yet. */ + vty_out(vty, "OSMUX currently unavailable in this software version.%s", VTY_NEWLINE); + return CMD_WARNING; + + if (strcmp(argv[0], "on") == 0) + g_cfg->osmux = OSMUX_USAGE_ON; + else if (strcmp(argv[0], "only") == 0) + g_cfg->osmux = OSMUX_USAGE_ONLY; + + if (g_cfg->trunk.audio_loop) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_ip, + cfg_mgcp_osmux_ip_cmd, + "osmux bind-ip A.B.C.D", OSMUX_STR IP_STR "IPv4 Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->osmux_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_batch_factor, + cfg_mgcp_osmux_batch_factor_cmd, + "osmux batch-factor <1-8>", + OSMUX_STR "Batching factor\n" "Number of messages in the batch\n") +{ + g_cfg->osmux_batch = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_batch_size, + cfg_mgcp_osmux_batch_size_cmd, + "osmux batch-size <1-65535>", + OSMUX_STR "batch size\n" "Batch size in bytes\n") +{ + g_cfg->osmux_batch_size = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_port, + cfg_mgcp_osmux_port_cmd, + "osmux port <1-65535>", OSMUX_STR "port\n" "UDP port\n") +{ + g_cfg->osmux_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_dummy, + cfg_mgcp_osmux_dummy_cmd, + "osmux dummy (on|off)", + OSMUX_STR "Dummy padding\n" "Enable dummy padding\n" + "Disable dummy padding\n") +{ + if (strcmp(argv[0], "on") == 0) + g_cfg->osmux_dummy = 1; + else if (strcmp(argv[0], "off") == 0) + g_cfg->osmux_dummy = 0; + + return CMD_SUCCESS; +} + +int mgcp_vty_init(void) +{ + install_element_ve(&show_mgcp_cmd); + install_element(ENABLE_NODE, &loop_conn_cmd); + install_element(ENABLE_NODE, &tap_rtp_cmd); + install_element(ENABLE_NODE, &free_endp_cmd); + install_element(ENABLE_NODE, &reset_endp_cmd); + install_element(ENABLE_NODE, &reset_all_endp_cmd); + + install_element(CONFIG_NODE, &cfg_mgcp_cmd); + install_node(&mgcp_node, config_write_mgcp); + + install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_no_net_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_bind_ip_probing_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_no_net_bind_ip_probing_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_force_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_rtp_force_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_once_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_rtp_keepalive_cmd); + install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd); + install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_loop_cmd); + install_element(MGCP_NODE, &cfg_mgcp_force_realloc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_accept_all_cmd); + install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_omit_rtcp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_omit_rtcp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ssrc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ssrc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ts_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ts_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_fmtp_extra_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_factor_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_size_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_dummy_cmd); + install_element(MGCP_NODE, &cfg_mgcp_allow_transcoding_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_allow_transcoding_cmd); + + install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd); + install_node(&trunk_node, config_write_trunk); + install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_cmd); + install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_once_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_rtp_keepalive_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd_old); + install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd_old); + install_element(TRUNK_NODE, &cfg_trunk_loop_cmd); + install_element(TRUNK_NODE, &cfg_trunk_omit_rtcp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_omit_rtcp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ssrc_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ssrc_cmd); + install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ts_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ts_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_fmtp_extra_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_ptime_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_ptime_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_allow_transcoding_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_allow_transcoding_cmd); + + return 0; +} + +static int allocate_trunk(struct mgcp_trunk_config *trunk) +{ + if (mgcp_endpoints_allocate(trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to allocate %d endpoints on trunk %d.\n", + trunk->number_endpoints, trunk->trunk_nr); + return -1; + } + + return 0; +} + +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg, + enum mgcp_role role) +{ + int rc; + struct mgcp_trunk_config *trunk; + + cfg->osmux_port = OSMUX_PORT; + cfg->osmux_batch = 4; + cfg->osmux_batch_size = OSMUX_BATCH_DEFAULT_MAX; + + g_cfg = cfg; + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + config_file); + return rc; + } + + if (!g_cfg->source_addr) { + fprintf(stderr, "You need to specify a bind address.\n"); + return -1; + } + + if (allocate_trunk(&g_cfg->trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize the virtual trunk.\n"); + return -1; + } + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + if (allocate_trunk(trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize E1 trunk %d.\n", + trunk->trunk_nr); + return -1; + } + } + cfg->role = role; + + return 0; +} diff --git a/src/osmo-bsc_mgcp/Makefile.am b/src/osmo-bsc_mgcp/Makefile.am new file mode 100644 index 0000000..4529a33 --- /dev/null +++ b/src/osmo-bsc_mgcp/Makefile.am @@ -0,0 +1,29 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBBCG729_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-bsc_mgcp \ + $(NULL) + +osmo_bsc_mgcp_SOURCES = \ + mgcp_main.c \ + $(NULL) + +osmo_bsc_mgcp_LDADD = \ + $(top_builddir)/src/libosmo-legacy-mgcp/libosmo-legacy-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBBCG729_LIBS) \ + $(LIBRARY_GSM) \ + $(NULL) diff --git a/src/osmo-bsc_mgcp/mgcp_main.c b/src/osmo-bsc_mgcp/mgcp_main.c new file mode 100644 index 0000000..48241a6 --- /dev/null +++ b/src/osmo-bsc_mgcp/mgcp_main.c @@ -0,0 +1,351 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The main method to drive it as a standalone process */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> + +#include <sys/socket.h> + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> +#include <osmocom/legacy_mgcp/vty.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/logging.h> + +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/stats.h> + +#include "../../bscconfig.h" + +#ifdef BUILD_MGCP_TRANSCODING +#include <osmocom/legacy_mgcp/mgcp_transcode.h> +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#warning "Make use of the rtp proxy code" + +static struct mgcp_config *cfg; +static struct mgcp_trunk_config *reset_trunk; +static int reset_endpoints = 0; +static int daemonize = 0; + +const char *openbsc_copyright = + "Copyright (C) 2009-2010 Holger Freyther and On-Waves\r\n" + "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n" + "Dieter Spaar, Andreas Eversberg, Harald Welte\r\n\r\n" + "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static char *config_file = "mgcp.cfg"; + +/* used by msgb and mgcp */ +void *tall_bsc_ctx = NULL; + +static void print_help() +{ + printf("Some useful help...\n"); + printf(" -h --help is printing this text.\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -s --disable-color\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -V --version Print the version number\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"config-file", 1, 0, 'c'}, + {"daemonize", 0, 0, 'D'}, + {"version", 0, 0, 'V'}, + {"disable-color", 0, 0, 's'}, + {0, 0, 0, 0}, + }; + + c = getopt_long(argc, argv, "hc:VD", long_options, &option_index); + + if (c == -1) + break; + + switch(c) { + case 'h': + print_help(); + exit(0); + break; + case 'c': + config_file = talloc_strdup(tall_bsc_ctx, optarg); + break; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'V': + print_version(1); + exit(0); + break; + case 'D': + daemonize = 1; + break; + default: + /* ignore */ + break; + }; + } +} + +/* simply remember this */ +static int mgcp_rsip_cb(struct mgcp_trunk_config *tcfg) +{ + reset_endpoints = 1; + reset_trunk = tcfg; + + return 0; +} + +static int read_call_agent(struct osmo_fd *fd, unsigned int what) +{ + struct sockaddr_in addr; + socklen_t slen = sizeof(addr); + struct msgb *msg; + struct msgb *resp; + int i; + + msg = (struct msgb *) fd->data; + + /* read one less so we can use it as a \0 */ + int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0, + (struct sockaddr *) &addr, &slen); + if (rc < 0) { + perror("Gateway failed to read"); + return -1; + } else if (slen > sizeof(addr)) { + fprintf(stderr, "Gateway received message from outerspace: %zu %zu\n", + (size_t) slen, sizeof(addr)); + return -1; + } + + /* handle message now */ + msg->l2h = msgb_put(msg, rc); + resp = mgcp_handle_message(cfg, msg); + msgb_reset(msg); + + if (resp) { + sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr)); + msgb_free(resp); + } + + if (reset_endpoints) { + LOGP(DLMGCP, LOGL_NOTICE, + "Asked to reset endpoints: %d/%d\n", + reset_trunk->trunk_nr, reset_trunk->trunk_type); + reset_endpoints = 0; + + /* is checking in_addr.s_addr == INADDR_LOOPBACK making it more secure? */ + for (i = 1; i < reset_trunk->number_endpoints; ++i) + mgcp_release_endp(&reset_trunk->endpoints[i]); + } + + return 0; +} + +int mgcp_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + case CONFIG_NODE: + return 0; + + default: + return 1; + } +} + +int mgcp_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case TRUNK_NODE: + vty->node = MGCP_NODE; + vty->index = NULL; + break; + case MGCP_NODE: + default: + if (mgcp_vty_is_config_node(vty, vty->node)) + vty->node = CONFIG_NODE; + else + vty->node = ENABLE_NODE; + + vty->index = NULL; + } + + return vty->node; +} + + +static struct vty_app_info vty_info = { + .name = "OpenBSC MGCP", + .version = PACKAGE_VERSION, + .go_parent_cb = mgcp_vty_go_parent, + .is_config_node = mgcp_vty_is_config_node, +}; + +static const struct log_info_cat log_categories[] = { + /* DLMGCP is provided by the MGCP library */ +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + struct sockaddr_in addr; + int on = 1, rc; + + tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent"); + msgb_talloc_ctx_init(tall_bsc_ctx, 0); + + osmo_init_ignore_signals(); + osmo_init_logging(&log_info); + + cfg = mgcp_config_alloc(); + if (!cfg) + return -1; + +#ifdef BUILD_MGCP_TRANSCODING + cfg->setup_rtp_processing_cb = &mgcp_transcoding_setup; + cfg->rtp_processing_cb = &mgcp_transcoding_process_rtp; + cfg->get_net_downlink_format_cb = &mgcp_transcoding_net_downlink_format; +#endif + + cfg->trunk.force_realloc = 1; + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(NULL); + osmo_stats_vty_add_cmds(&log_info); + mgcp_vty_init(); + + handle_options(argc, argv); + + rate_ctr_init(tall_bsc_ctx); + osmo_stats_init(tall_bsc_ctx); + + rc = mgcp_parse_config(config_file, cfg, MGCP_BSC); + if (rc < 0) + return rc; + + /* start telnet after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(tall_bsc_ctx, NULL, + vty_get_bind_addr(), OSMO_VTY_PORT_BSC_MGCP); + if (rc < 0) + return rc; + + /* set some callbacks */ + cfg->reset_cb = mgcp_rsip_cb; + + /* we need to bind a socket */ + if (rc == 0) { + cfg->gw_fd.bfd.when = BSC_FD_READ; + cfg->gw_fd.bfd.cb = read_call_agent; + cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (cfg->gw_fd.bfd.fd < 0) { + perror("Gateway failed to listen"); + return -1; + } + + setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(cfg->source_port); + inet_aton(cfg->source_addr, &addr.sin_addr); + + if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Gateway failed to bind"); + return -1; + } + + cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg"); + if (!cfg->gw_fd.bfd.data) { + fprintf(stderr, "Gateway memory error.\n"); + return -1; + } + + if (cfg->call_agent_addr) { + addr.sin_port = htons(2727); + inet_aton(cfg->call_agent_addr, &addr.sin_addr); + if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n", + cfg->call_agent_addr, errno); + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + } + + if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to register the fd\n"); + return -1; + } + + LOGP(DLMGCP, LOGL_NOTICE, "Configured for MGCP.\n"); + } + + /* initialisation */ + srand(time(NULL)); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + /* main loop */ + while (1) { + osmo_select_main(0); + } + + + return 0; +} diff --git a/src/osmo-mgw/Makefile.am b/src/osmo-mgw/Makefile.am new file mode 100644 index 0000000..0e12beb --- /dev/null +++ b/src/osmo-mgw/Makefile.am @@ -0,0 +1,26 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-mgw \ + $(NULL) + +osmo_mgw_SOURCES = \ + mgw_main.c \ + $(NULL) + +osmo_mgw_LDADD = \ + $(top_builddir)/src/libosmo-mgcp/libosmo-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(NULL) diff --git a/src/osmo-mgw/mgw_main.c b/src/osmo-mgw/mgw_main.c new file mode 100644 index 0000000..ab54e62 --- /dev/null +++ b/src/osmo-mgw/mgw_main.c @@ -0,0 +1,356 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The main method to drive it as a standalone process */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> + +#include <sys/socket.h> + +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/vty.h> +#include <osmocom/mgcp/debug.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/logging.h> + +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/stats.h> + +#include "../../bscconfig.h" + +#define _GNU_SOURCE +#include <getopt.h> + +/* FIXME: Make use of the rtp proxy code */ + +static struct mgcp_config *cfg; +static struct mgcp_trunk_config *reset_trunk; +static int reset_endpoints = 0; +static int daemonize = 0; + +const char *openbsc_copyright = + "Copyright (C) 2009-2010 Holger Freyther and On-Waves\r\n" + "Copyright (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>\r\n" + "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n" + "Dieter Spaar, Andreas Eversberg, Harald Welte\r\n\r\n" + "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static char *config_file = "mgcp.cfg"; + +/* used by msgb and mgcp */ +void *tall_bsc_ctx = NULL; + +static void print_help() +{ + printf("Some useful help...\n"); + printf(" -h --help is printing this text.\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -s --disable-color\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -V --version Print the version number\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"config-file", 1, 0, 'c'}, + {"daemonize", 0, 0, 'D'}, + {"version", 0, 0, 'V'}, + {"disable-color", 0, 0, 's'}, + {0, 0, 0, 0}, + }; + + c = getopt_long(argc, argv, "hc:VD", long_options, &option_index); + + if (c == -1) + break; + + switch(c) { + case 'h': + print_help(); + exit(0); + break; + case 'c': + config_file = talloc_strdup(tall_bsc_ctx, optarg); + break; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'V': + print_version(1); + exit(0); + break; + case 'D': + daemonize = 1; + break; + default: + /* ignore */ + break; + }; + } +} + +/* Callback function to be called when the RSIP ("Reset in Progress") mgcp + * command is received */ +static int mgcp_rsip_cb(struct mgcp_trunk_config *tcfg) +{ + /* Set flag so that, when read_call_agent() is called next time + * the reset can progress */ + reset_endpoints = 1; + + reset_trunk = tcfg; + + return 0; +} + +static int read_call_agent(struct osmo_fd *fd, unsigned int what) +{ + struct sockaddr_in addr; + socklen_t slen = sizeof(addr); + struct msgb *msg; + struct msgb *resp; + int i; + + msg = (struct msgb *) fd->data; + + /* read one less so we can use it as a \0 */ + int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0, + (struct sockaddr *) &addr, &slen); + if (rc < 0) { + perror("Gateway failed to read"); + return -1; + } else if (slen > sizeof(addr)) { + fprintf(stderr, "Gateway received message from outerspace: %zu %zu\n", + (size_t) slen, sizeof(addr)); + return -1; + } + + /* handle message now */ + msg->l2h = msgb_put(msg, rc); + resp = mgcp_handle_message(cfg, msg); + msgb_reset(msg); + + if (resp) { + sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr)); + msgb_free(resp); + } + + /* reset endpoints */ + if (reset_endpoints) { + LOGP(DLMGCP, LOGL_NOTICE, + "Asked to reset endpoints: %d/%d\n", + reset_trunk->trunk_nr, reset_trunk->trunk_type); + + /* reset flag */ + reset_endpoints = 0; + + /* Walk over all endpoints and trigger a release, this will release all + * endpoints, possible open connections are forcefully dropped */ + for (i = 1; i < reset_trunk->number_endpoints; ++i) + mgcp_release_endp(&reset_trunk->endpoints[i]); + } + + return 0; +} + +int mgcp_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + case CONFIG_NODE: + return 0; + + default: + return 1; + } +} + +int mgcp_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case TRUNK_NODE: + vty->node = MGCP_NODE; + vty->index = NULL; + break; + case MGCP_NODE: + default: + if (mgcp_vty_is_config_node(vty, vty->node)) + vty->node = CONFIG_NODE; + else + vty->node = ENABLE_NODE; + + vty->index = NULL; + } + + return vty->node; +} + + +static struct vty_app_info vty_info = { + .name = "OsmoMGW", + .version = PACKAGE_VERSION, + .go_parent_cb = mgcp_vty_go_parent, + .is_config_node = mgcp_vty_is_config_node, +}; + +static const struct log_info_cat log_categories[] = { + /* DLMGCP is provided by the MGCP library */ + [DRTP] = { + .name = "DRTP", + .description = "RTP stream handling", + .color = "\033[1;30m", + .enabled = 1,.loglevel = LOGL_NOTICE, + }, +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + struct sockaddr_in addr; + int on = 1, rc; + + tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent"); + msgb_talloc_ctx_init(tall_bsc_ctx, 0); + + osmo_init_ignore_signals(); + osmo_init_logging(&log_info); + + cfg = mgcp_config_alloc(); + if (!cfg) + return -1; + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(NULL); + osmo_stats_vty_add_cmds(&log_info); + mgcp_vty_init(); + + handle_options(argc, argv); + + rate_ctr_init(tall_bsc_ctx); + osmo_stats_init(tall_bsc_ctx); + + rc = mgcp_parse_config(config_file, cfg, MGCP_BSC); + if (rc < 0) + return rc; + + /* start telnet after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(tall_bsc_ctx, NULL, + vty_get_bind_addr(), OSMO_VTY_PORT_BSC_MGCP); + if (rc < 0) + return rc; + + /* Set the reset callback function. This functions is called when the + * mgcp-command "RSIP" (Reset in Progress) is received */ + cfg->reset_cb = mgcp_rsip_cb; + + /* we need to bind a socket */ + if (rc == 0) { + cfg->gw_fd.bfd.when = BSC_FD_READ; + cfg->gw_fd.bfd.cb = read_call_agent; + cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (cfg->gw_fd.bfd.fd < 0) { + perror("Gateway failed to listen"); + return -1; + } + + setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(cfg->source_port); + inet_aton(cfg->source_addr, &addr.sin_addr); + + if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Gateway failed to bind"); + return -1; + } + + cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg"); + if (!cfg->gw_fd.bfd.data) { + fprintf(stderr, "Gateway memory error.\n"); + return -1; + } + + if (cfg->call_agent_addr) { + addr.sin_port = htons(2727); + inet_aton(cfg->call_agent_addr, &addr.sin_addr); + if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n", + cfg->call_agent_addr, errno); + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + } + + if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to register the fd\n"); + return -1; + } + + LOGP(DLMGCP, LOGL_NOTICE, "Configured for MGCP.\n"); + } + + /* initialisation */ + srand(time(NULL)); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + /* main loop */ + while (1) { + osmo_select_main(0); + } + + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..2eed271 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,64 @@ +SUBDIRS = \ + legacy_mgcp \ + mgcp_client \ + mgcp \ + $(NULL) + +# The `:;' works around a Bash 3.2 bug when the output is not writeable. +$(srcdir)/package.m4: $(top_srcdir)/configure.ac + :;{ \ + echo '# Signature of the current package.' && \ + echo 'm4_define([AT_PACKAGE_NAME],' && \ + echo ' [$(PACKAGE_NAME)])' && \ + echo 'm4_define([AT_PACKAGE_TARNAME],' && \ + echo ' [$(PACKAGE_TARNAME)])' && \ + echo 'm4_define([AT_PACKAGE_VERSION],' && \ + echo ' [$(PACKAGE_VERSION)])' && \ + echo 'm4_define([AT_PACKAGE_STRING],' && \ + echo ' [$(PACKAGE_STRING)])' && \ + echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \ + echo ' [$(PACKAGE_BUGREPORT)])'; \ + echo 'm4_define([AT_PACKAGE_URL],' && \ + echo ' [$(PACKAGE_URL)])'; \ + } >'$(srcdir)/package.m4' + +EXTRA_DIST = \ + testsuite.at \ + $(srcdir)/package.m4 \ + $(TESTSUITE) \ + vty_test_runner.py \ + $(NULL) + +TESTSUITE = $(srcdir)/testsuite + +DISTCLEANFILES = \ + atconfig \ + $(NULL) + +if ENABLE_EXT_TESTS +python-tests: $(BUILT_SOURCES) + osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v + osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v + $(PYTHON) $(srcdir)/vty_test_runner.py -w $(abs_top_builddir) -v +else +python-tests: $(BUILT_SOURCES) + echo "Not running python-based tests (determined at configure-time)" +endif + +check-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) + $(MAKE) $(AM_MAKEFLAGS) python-tests + +installcheck-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \ + $(TESTSUITEFLAGS) + +clean-local: + test ! -f '$(TESTSUITE)' || \ + $(SHELL) '$(TESTSUITE)' --clean + +AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te +AUTOTEST = $(AUTOM4TE) --language=autotest +$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4 + $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at + mv $@.tmp $@ diff --git a/tests/atlocal.in b/tests/atlocal.in new file mode 100644 index 0000000..362bfa9 --- /dev/null +++ b/tests/atlocal.in @@ -0,0 +1,7 @@ +enable_nat_test='@osmo_ac_build_nat@' +enable_smpp_test='@osmo_ac_build_smpp@' +enable_bsc_test='@osmo_ac_build_bsc@' +enable_mgcp_transcoding_test='@osmo_ac_mgcp_transcoding@' +enable_sgsn_test='@found_libgtp_and_libcares@' +enable_oap_test='@found_libgtp_and_libcares@' +enable_gtphub_test='@found_libgtp_and_libcares@' diff --git a/tests/legacy_mgcp/Makefile.am b/tests/legacy_mgcp/Makefile.am new file mode 100644 index 0000000..f043124 --- /dev/null +++ b/tests/legacy_mgcp/Makefile.am @@ -0,0 +1,61 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBBCG729_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + mgcp_test.ok \ + mgcp_transcoding_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + mgcp_test \ + $(NULL) +if BUILD_MGCP_TRANSCODING +noinst_PROGRAMS += \ + mgcp_transcoding_test \ + $(NULL) +endif + +mgcp_test_SOURCES = \ + mgcp_test.c \ + $(NULL) + +mgcp_test_LDADD = \ + $(top_builddir)/src/libosmo-legacy-mgcp/libosmo-legacy-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBRARY_DL) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBRARY_GSM) \ + -lm \ + $(NULL) + +mgcp_transcoding_test_SOURCES = \ + mgcp_transcoding_test.c \ + $(NULL) + +mgcp_transcoding_test_LDADD = \ + $(top_builddir)/src/libosmo-legacy-mgcp/libosmo-legacy-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBBCG729_LIBS) \ + $(LIBRARY_DL) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBRARY_GSM) \ + -lm \ + $(NULL) diff --git a/tests/legacy_mgcp/mgcp_test.c b/tests/legacy_mgcp/mgcp_test.c new file mode 100644 index 0000000..1400893 --- /dev/null +++ b/tests/legacy_mgcp/mgcp_test.c @@ -0,0 +1,1236 @@ +/* + * (C) 2011-2012,2014 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2011-2012,2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#undef _GNU_SOURCE +#define _GNU_SOURCE + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/vty.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <string.h> +#include <limits.h> +#include <dlfcn.h> +#include <time.h> +#include <math.h> + +char *strline_r(char *str, char **saveptr); + +const char *strline_test_data = + "one CR\r" + "two CR\r" + "\r" + "one CRLF\r\n" + "two CRLF\r\n" + "\r\n" + "one LF\n" + "two LF\n" + "\n" + "mixed (4 lines)\r\r\n\n\r\n"; + +#define EXPECTED_NUMBER_OF_LINES 13 + +static void test_strline(void) +{ + char *save = NULL; + char *line; + char buf[2048]; + int counter = 0; + + osmo_strlcpy(buf, strline_test_data, sizeof(buf)); + + for (line = strline_r(buf, &save); line; + line = strline_r(NULL, &save)) { + printf("line: '%s'\n", line); + counter++; + } + + OSMO_ASSERT(counter == EXPECTED_NUMBER_OF_LINES); +} + +#define AUEP1 "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n" +#define AUEP1_RET "200 158663169 OK\r\n" +#define AUEP2 "AUEP 18983213 ds/e1-2/1@172.16.6.66 MGCP 1.0\r\n" +#define AUEP2_RET "500 18983213 FAIL\r\n" +#define EMPTY "\r\n" +#define EMPTY_RET NULL +#define SHORT "CRCX \r\n" +#define SHORT_RET "510 000000 FAIL\r\n" + +#define MDCX_WRONG_EP "MDCX 18983213 ds/e1-3/1@172.16.6.66 MGCP 1.0\r\n" +#define MDCX_ERR_RET "510 18983213 FAIL\r\n" +#define MDCX_UNALLOCATED "MDCX 18983214 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n" +#define MDCX_RET "400 18983214 FAIL\r\n" +#define MDCX3 "MDCX 18983215 1@mgw MGCP 1.0\r\n" +#define MDCX3_RET "200 18983215 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" +#define MDCX3_FMTP_RET "200 18983215 OK\r\n" \ + "I: 3\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 3 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=fmtp:126 0/1/2\r\n" \ + "a=ptime:20\r\n" +#define MDCX4 "MDCX 18983216 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" +#define MDCX4_RET(Ident) "200 " Ident " OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" + +#define MDCX4_PT1 "MDCX 18983217 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20-40, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_PT2 "MDCX 18983218 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20-20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_PT3 "MDCX 18983219 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_SO "MDCX 18983220 1@mgw MGCP 1.0\r\n" \ + "M: sendonly\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_RO "MDCX 18983221 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" + +#define SHORT2 "CRCX 1" +#define SHORT2_RET "510 000000 FAIL\r\n" +#define SHORT3 "CRCX 1 1@mgw" +#define SHORT4 "CRCX 1 1@mgw MGCP" +#define SHORT5 "CRCX 1 1@mgw MGCP 1.0" + +#define CRCX "CRCX 2 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 97\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_RET "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" + +#define CRCX_RET_NO_RTPMAP "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 0 RTP/AVP 126\r\n" \ + "a=ptime:20\r\n" + +#define CRCX_FMTP_RET "200 2 OK\r\n" \ + "I: 3\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 3 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=fmtp:126 0/1/2\r\n" \ + "a=ptime:20\r\n" + +#define CRCX_ZYN "CRCX 2 1@mgw MGCP 1.0\r" \ + "M: recvonly\r" \ + "C: 2\r\r" \ + "v=0\r" \ + "c=IN IP4 123.12.12.123\r" \ + "m=audio 5904 RTP/AVP 97\r" \ + "a=rtpmap:97 GSM-EFR/8000\r" + +#define CRCX_ZYN_RET "200 2 OK\r\n" \ + "I: 2\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 2 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" + +#define DLCX "DLCX 7 1@mgw MGCP 1.0\r\n" \ + "C: 2\r\n" + +#define DLCX_RET "250 7 OK\r\n" \ + "P: PS=0, OS=0, PR=0, OR=0, PL=0, JI=0\r\n" \ + "X-Osmo-CP: EC TIS=0, TOS=0, TIR=0, TOR=0\r\n" + +#define RQNT "RQNT 186908780 1@mgw MGCP 1.0\r\n" \ + "X: B244F267488\r\n" \ + "S: D/9\r\n" + +#define RQNT2 "RQNT 186908781 1@mgw MGCP 1.0\r\n" \ + "X: ADD4F26746F\r\n" \ + "R: D/[0-9#*](N), G/ft, fxr/t38\r\n" + +#define RQNT1_RET "200 186908780 OK\r\n" +#define RQNT2_RET "200 186908781 OK\r\n" + +#define PTYPE_IGNORE 0 /* == default initializer */ +#define PTYPE_NONE 128 +#define PTYPE_NYI PTYPE_NONE + +#define CRCX_MULT_1 "CRCX 2 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18 97\r\n"\ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_2 "CRCX 2 2@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18 97 101\r\n"\ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_3 "CRCX 2 3@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_4 "CRCX 2 4@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_GSM_EXACT \ + "CRCX 259260421 5@mgw MGCP 1.0\r\n" \ + "C: 1355c6041e\r\n" \ + "I: 3\r\n" \ + "L: p:20, a:GSM, nt:IN\r\n" \ + "M: recvonly\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "o=- 1439038275 1439038275 IN IP4 192.168.181.247\r\n" \ + "s=-\r\nc=IN IP4 192.168.181.247\r\n" \ + "t=0 0\r\nm=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101\r\n" \ + "a=rtpmap:0 PCMU/8000\r\n" \ + "a=rtpmap:8 PCMA/8000\r\n" \ + "a=rtpmap:3 gsm/8000\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=fmtp:18 annexb=no\r\n" \ + "a=rtpmap:4 G723/8000\r\n" \ + "a=rtpmap:96 iLBC/8000\r\n" \ + "a=fmtp:96 mode=20\r\n" \ + "a=rtpmap:97 iLBC/8000\r\n" \ + "a=fmtp:97 mode=30\r\n" \ + "a=rtpmap:101 telephone-event/8000\r\n" \ + "a=fmtp:101 0-15\r\n" \ + "a=recvonly\r\n" +#define MDCX_NAT_DUMMY \ + "MDCX 23 5@mgw MGCP 1.0\r\n" \ + "C: 1355c6041e\r\n" \ + "\r\n" \ + "c=IN IP4 8.8.8.8\r\n" \ + "m=audio 16434 RTP/AVP 255\r\n" + + +struct mgcp_test { + const char *name; + const char *req; + const char *exp_resp; + int exp_net_ptype; + int exp_bts_ptype; + + const char *extra_fmtp; +}; + +static const struct mgcp_test tests[] = { + { "AUEP1", AUEP1, AUEP1_RET }, + { "AUEP2", AUEP2, AUEP2_RET }, + { "MDCX1", MDCX_WRONG_EP, MDCX_ERR_RET }, + { "MDCX2", MDCX_UNALLOCATED, MDCX_RET }, + { "CRCX", CRCX, CRCX_RET, 97, 126 }, + { "MDCX3", MDCX3, MDCX3_RET, PTYPE_NONE, 126 }, + { "MDCX4", MDCX4, MDCX4_RET("18983216"), 99, 126 }, + { "MDCX4_PT1", MDCX4_PT1, MDCX4_RET("18983217"), 99, 126 }, + { "MDCX4_PT2", MDCX4_PT2, MDCX4_RET("18983218"), 99, 126 }, + { "MDCX4_PT3", MDCX4_PT3, MDCX4_RET("18983219"), 99, 126 }, + { "MDCX4_SO", MDCX4_SO, MDCX4_RET("18983220"), 99, 126 }, + { "MDCX4_RO", MDCX4_RO, MDCX4_RET("18983221"), PTYPE_IGNORE, 126 }, + { "DLCX", DLCX, DLCX_RET, -1, -1 }, + { "CRCX_ZYN", CRCX_ZYN, CRCX_ZYN_RET, 97, 126 }, + { "EMPTY", EMPTY, EMPTY_RET }, + { "SHORT1", SHORT, SHORT_RET }, + { "SHORT2", SHORT2, SHORT2_RET }, + { "SHORT3", SHORT3, SHORT2_RET }, + { "SHORT4", SHORT4, SHORT2_RET }, + { "RQNT1", RQNT, RQNT1_RET }, + { "RQNT2", RQNT2, RQNT2_RET }, + { "DLCX", DLCX, DLCX_RET, -1, -1 }, + { "CRCX", CRCX, CRCX_FMTP_RET, 97, 126, .extra_fmtp = "a=fmtp:126 0/1/2" }, + { "MDCX3", MDCX3, MDCX3_FMTP_RET, PTYPE_NONE, 126 , .extra_fmtp = "a=fmtp:126 0/1/2" }, + { "DLCX", DLCX, DLCX_RET, -1, -1 , .extra_fmtp = "a=fmtp:126 0/1/2" }, +}; + +static const struct mgcp_test retransmit[] = { + { "CRCX", CRCX, CRCX_RET }, + { "RQNT1", RQNT, RQNT1_RET }, + { "RQNT2", RQNT2, RQNT2_RET }, + { "MDCX3", MDCX3, MDCX3_RET }, + { "DLCX", DLCX, DLCX_RET }, +}; + +static struct msgb *create_msg(const char *str) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + int len = sprintf((char *)msg->data, "%s", str); + msg->l2h = msgb_put(msg, len); + return msg; +} + +static int last_endpoint = -1; + +static int mgcp_test_policy_cb(struct mgcp_trunk_config *cfg, int endpoint, + int state, const char *transactio_id) +{ + fprintf(stderr, "Policy CB got state %d on endpoint %d\n", + state, endpoint); + last_endpoint = endpoint; + return MGCP_POLICY_CONT; +} + +#define MGCP_DUMMY_LOAD 0x23 +static int dummy_packets = 0; +/* override and forward */ +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + typedef ssize_t (*sendto_t)(int, const void *, size_t, int, + const struct sockaddr *, socklen_t); + static sendto_t real_sendto = NULL; + uint32_t dest_host = htonl(((struct sockaddr_in *)dest_addr)->sin_addr.s_addr); + int dest_port = htons(((struct sockaddr_in *)dest_addr)->sin_port); + + if (!real_sendto) + real_sendto = dlsym(RTLD_NEXT, "sendto"); + + if (len == 1 && ((const char *)buf)[0] == MGCP_DUMMY_LOAD ) { + fprintf(stderr, "Dummy packet to 0x%08x:%d, msg length %zu\n%s\n\n", + dest_host, dest_port, + len, osmo_hexdump(buf, len)); + dummy_packets += 1; + } + + return real_sendto(sockfd, buf, len, flags, dest_addr, addrlen); +} + +static int64_t force_monotonic_time_us = -1; +/* override and forward */ +int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + typedef int (*clock_gettime_t)(clockid_t clk_id, struct timespec *tp); + static clock_gettime_t real_clock_gettime = NULL; + + if (!real_clock_gettime) + real_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime"); + + if (clk_id == CLOCK_MONOTONIC && force_monotonic_time_us >= 0) { + tp->tv_sec = force_monotonic_time_us / 1000000; + tp->tv_nsec = (force_monotonic_time_us % 1000000) * 1000; + return 0; + } + + return real_clock_gettime(clk_id, tp); +} + +#define CONN_UNMODIFIED (0x1000) + +static void test_values(void) +{ + /* Check that NONE disables all output */ + OSMO_ASSERT((MGCP_CONN_NONE & MGCP_CONN_RECV_SEND) == 0); + + /* Check that LOOPBACK enables all output */ + OSMO_ASSERT((MGCP_CONN_LOOPBACK & MGCP_CONN_RECV_SEND) == + MGCP_CONN_RECV_SEND); +} + + +static void test_messages(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + int i; + + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + cfg->policy_cb = mgcp_test_policy_cb; + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* reset endpoints */ + for (i = 0; i < cfg->trunk.number_endpoints; i++) { + endp = &cfg->trunk.endpoints[i]; + endp->net_end.codec.payload_type = PTYPE_NONE; + endp->net_end.packet_duration_ms = -1; + + OSMO_ASSERT(endp->conn_mode == MGCP_CONN_NONE); + endp->conn_mode |= CONN_UNMODIFIED; + } + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + const struct mgcp_test *t = &tests[i]; + struct msgb *inp; + struct msgb *msg; + + printf("Testing %s\n", t->name); + + last_endpoint = -1; + dummy_packets = 0; + + osmo_talloc_replace_string(cfg, &cfg->trunk.audio_fmtp_extra, t->extra_fmtp); + + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (!t->exp_resp) { + if (msg) + printf("%s failed '%s'\n", t->name, (char *) msg->data); + } else if (strcmp((char *) msg->data, t->exp_resp) != 0) + printf("%s failed.\nExpected:\n%s\nGot:\n%s\n", + t->name, t->exp_resp, (char *) msg->data); + msgb_free(msg); + + if (dummy_packets) + printf("Dummy packets: %d\n", dummy_packets); + + if (last_endpoint != -1) { + endp = &cfg->trunk.endpoints[last_endpoint]; + + if (endp->net_end.packet_duration_ms != -1) + printf("Detected packet duration: %d\n", + endp->net_end.packet_duration_ms); + else + printf("Packet duration not set\n"); + if (endp->local_options.pkt_period_min || + endp->local_options.pkt_period_max) + printf("Requested packetetization period: " + "%d-%d\n", + endp->local_options.pkt_period_min, + endp->local_options.pkt_period_max); + else + printf("Requested packetization period not set\n"); + + if ((endp->conn_mode & CONN_UNMODIFIED) == 0) { + printf("Connection mode: %d:%s%s%s%s\n", + endp->conn_mode, + !endp->conn_mode ? " NONE" : "", + endp->conn_mode & MGCP_CONN_SEND_ONLY ? + " SEND" : "", + endp->conn_mode & MGCP_CONN_RECV_ONLY ? + " RECV" : "", + endp->conn_mode & MGCP_CONN_LOOPBACK & + ~MGCP_CONN_RECV_SEND ? + " LOOP" : ""); + fprintf(stderr, + "BTS output %sabled, NET output %sabled\n", + endp->bts_end.output_enabled ? "en" : "dis", + endp->net_end.output_enabled ? "en" : "dis"); + } else + printf("Connection mode not set\n"); + + OSMO_ASSERT(endp->net_end.output_enabled == + (endp->conn_mode & MGCP_CONN_SEND_ONLY ? 1 : 0)); + OSMO_ASSERT(endp->bts_end.output_enabled == + (endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0)); + + endp->net_end.packet_duration_ms = -1; + endp->local_options.pkt_period_min = 0; + endp->local_options.pkt_period_max = 0; + endp->conn_mode |= CONN_UNMODIFIED; + } + + + /* Check detected payload type */ + if (t->exp_net_ptype != PTYPE_IGNORE || + t->exp_bts_ptype != PTYPE_IGNORE) { + OSMO_ASSERT(last_endpoint != -1); + endp = &cfg->trunk.endpoints[last_endpoint]; + + fprintf(stderr, "endpoint %d: " + "payload type BTS %d (exp %d), NET %d (exp %d)\n", + last_endpoint, + endp->bts_end.codec.payload_type, t->exp_bts_ptype, + endp->net_end.codec.payload_type, t->exp_net_ptype); + + if (t->exp_bts_ptype != PTYPE_IGNORE) + OSMO_ASSERT(endp->bts_end.codec.payload_type == + t->exp_bts_ptype); + if (t->exp_net_ptype != PTYPE_IGNORE) + OSMO_ASSERT(endp->net_end.codec.payload_type == + t->exp_net_ptype); + + /* Reset them again for next test */ + endp->net_end.codec.payload_type = PTYPE_NONE; + } + } + + talloc_free(cfg); +} + +static void test_retransmission(void) +{ + struct mgcp_config *cfg; + int i; + + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* reset endpoints */ + for (i = 0; i < cfg->trunk.number_endpoints; i++) { + struct mgcp_endpoint *endp; + endp = &cfg->trunk.endpoints[i]; + endp->bts_end.packet_duration_ms = 20; + } + + for (i = 0; i < ARRAY_SIZE(retransmit); i++) { + const struct mgcp_test *t = &retransmit[i]; + struct msgb *inp; + struct msgb *msg; + + printf("Testing %s\n", t->name); + + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (strcmp((char *) msg->data, t->exp_resp) != 0) + printf("%s failed '%s'\n", t->name, (char *) msg->data); + msgb_free(msg); + + /* Retransmit... */ + printf("Re-transmitting %s\n", t->name); + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (strcmp((char *) msg->data, t->exp_resp) != 0) + printf("%s failed '%s'\n", t->name, (char *) msg->data); + msgb_free(msg); + } + + talloc_free(cfg); +} + +static int rqnt_cb(struct mgcp_endpoint *endp, char _tone) +{ + ptrdiff_t tone = _tone; + endp->cfg->data = (void *) tone; + return 0; +} + +static void test_rqnt_cb(void) +{ + struct mgcp_config *cfg; + struct msgb *inp, *msg; + + cfg = mgcp_config_alloc(); + cfg->rqnt_cb = rqnt_cb; + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + inp = create_msg(CRCX); + msgb_free(mgcp_handle_message(cfg, inp)); + msgb_free(inp); + + /* send the RQNT and check for the CB */ + inp = create_msg(RQNT); + msg = mgcp_handle_message(cfg, inp); + if (strncmp((const char *) msg->l2h, "200", 3) != 0) { + printf("FAILED: message is not 200. '%s'\n", msg->l2h); + abort(); + } + + if (cfg->data != (void *) '9') { + printf("FAILED: callback not called: %p\n", cfg->data); + abort(); + } + + msgb_free(msg); + msgb_free(inp); + + inp = create_msg(DLCX); + msgb_free(mgcp_handle_message(cfg, inp)); + msgb_free(inp); + talloc_free(cfg); +} + +struct pl_test { + int cycles; + uint16_t base_seq; + uint16_t max_seq; + uint32_t packets; + + uint32_t expected; + int loss; +}; + +static const struct pl_test pl_test_dat[] = { + /* basic.. just one package */ + { .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = 1, .expected = 1, .loss = 0}, + /* some packages and a bit of loss */ + { .cycles = 0, .base_seq = 0, .max_seq = 100, .packets = 100, .expected = 101, .loss = 1}, + /* wrap around */ + { .cycles = 1<<16, .base_seq = 0xffff, .max_seq = 2, .packets = 4, .expected = 4, .loss = 0}, + /* min loss */ + { .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = UINT_MAX, .expected = 1, .loss = INT_MIN }, + /* max loss, with wrap around on expected max */ + { .cycles = INT_MAX, .base_seq = 0, .max_seq = UINT16_MAX, .packets = 0, .expected = ((uint32_t)(INT_MAX) + UINT16_MAX + 1), .loss = INT_MAX }, +}; + +static void test_packet_loss_calc(void) +{ + int i; + printf("Testing packet loss calculation.\n"); + + for (i = 0; i < ARRAY_SIZE(pl_test_dat); ++i) { + uint32_t expected; + int loss; + struct mgcp_rtp_state state; + struct mgcp_rtp_end rtp; + memset(&state, 0, sizeof(state)); + memset(&rtp, 0, sizeof(rtp)); + + state.stats_initialized = 1; + state.stats_base_seq = pl_test_dat[i].base_seq; + state.stats_max_seq = pl_test_dat[i].max_seq; + state.stats_cycles = pl_test_dat[i].cycles; + + rtp.packets = pl_test_dat[i].packets; + mgcp_state_calc_loss(&state, &rtp, &expected, &loss); + + if (loss != pl_test_dat[i].loss || expected != pl_test_dat[i].expected) { + printf("FAIL: Wrong exp/loss at idx(%d) Loss(%d vs. %d) Exp(%u vs. %u)\n", + i, loss, pl_test_dat[i].loss, + expected, pl_test_dat[i].expected); + } + } +} + +static void test_mgcp_stats(void) +{ + printf("Testing stat parsing\n"); + + uint32_t bps, bos, pr, _or, jitter; + struct msgb *msg; + int loss; + int rc; + + msg = create_msg(DLCX_RET); + rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter); + printf("Parsing result: %d\n", rc); + if (bps != 0 || bos != 0 || pr != 0 || _or != 0 || loss != 0 || jitter != 0) + printf("FAIL: Parsing failed1.\n"); + msgb_free(msg); + + msg = create_msg("250 7 OK\r\nP: PS=10, OS=20, PR=30, OR=40, PL=-3, JI=40\r\n"); + rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter); + printf("Parsing result: %d\n", rc); + if (bps != 10 || bos != 20 || pr != 30 || _or != 40 || loss != -3 || jitter != 40) + printf("FAIL: Parsing failed2.\n"); + msgb_free(msg); +} + +struct rtp_packet_info { + float txtime; + int len; + char *data; +}; + +struct rtp_packet_info test_rtp_packets1[] = { + /* RTP: SeqNo=0, TS=0 */ + {0.000000, 20, "\x80\x62\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 20, "\x80\x62\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=2, TS=320 */ + {0.040000, 20, "\x80\x62\x00\x02\x00\x00\x01\x40\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Repeat RTP timestamp: */ + /* RTP: SeqNo=3, TS=320 */ + {0.060000, 20, "\x80\x62\x00\x03\x00\x00\x01\x40\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=4, TS=480 */ + {0.080000, 20, "\x80\x62\x00\x04\x00\x00\x01\xE0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=5, TS=640 */ + {0.100000, 20, "\x80\x62\x00\x05\x00\x00\x02\x80\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Double skip RTP timestamp (delta = 2*160): */ + /* RTP: SeqNo=6, TS=960 */ + {0.120000, 20, "\x80\x62\x00\x06\x00\x00\x03\xC0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=7, TS=1120 */ + {0.140000, 20, "\x80\x62\x00\x07\x00\x00\x04\x60\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=8, TS=1280 */ + {0.160000, 20, "\x80\x62\x00\x08\x00\x00\x05\x00\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Non 20ms RTP timestamp (delta = 120): */ + /* RTP: SeqNo=9, TS=1400 */ + {0.180000, 20, "\x80\x62\x00\x09\x00\x00\x05\x78\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=10, TS=1560 */ + {0.200000, 20, "\x80\x62\x00\x0A\x00\x00\x06\x18\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=11, TS=1720 */ + {0.220000, 20, "\x80\x62\x00\x0B\x00\x00\x06\xB8\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* SSRC changed to 0x10203040, RTP timestamp jump */ + /* RTP: SeqNo=12, TS=34688 */ + {0.240000, 20, "\x80\x62\x00\x0C\x00\x00\x87\x80\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=13, TS=34848 */ + {0.260000, 20, "\x80\x62\x00\x0D\x00\x00\x88\x20\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=14, TS=35008 */ + {0.280000, 20, "\x80\x62\x00\x0E\x00\x00\x88\xC0\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Non 20ms RTP timestamp (delta = 120): */ + /* RTP: SeqNo=15, TS=35128 */ + {0.300000, 20, "\x80\x62\x00\x0F\x00\x00\x89\x38\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=16, TS=35288 */ + {0.320000, 20, "\x80\x62\x00\x10\x00\x00\x89\xD8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=17, TS=35448 */ + {0.340000, 20, "\x80\x62\x00\x11\x00\x00\x8A\x78\x10\x20\x30\x40" + "\x01\x23\x45\x67\x8A\xAB\xCD\xEF"}, + /* SeqNo increment by 2, RTP timestamp delta = 320: */ + /* RTP: SeqNo=19, TS=35768 */ + {0.360000, 20, "\x80\x62\x00\x13\x00\x00\x8B\xB8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=20, TS=35928 */ + {0.380000, 20, "\x80\x62\x00\x14\x00\x00\x8C\x58\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=21, TS=36088 */ + {0.380000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Repeat last packet */ + /* RTP: SeqNo=21, TS=36088 */ + {0.400000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=22, TS=36248 */ + {0.420000, 20, "\x80\x62\x00\x16\x00\x00\x8D\x98\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=23, TS=36408 */ + {0.440000, 20, "\x80\x62\x00\x17\x00\x00\x8E\x38\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Don't increment SeqNo but increment timestamp by 160 */ + /* RTP: SeqNo=23, TS=36568 */ + {0.460000, 20, "\x80\x62\x00\x17\x00\x00\x8E\xD8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=24, TS=36728 */ + {0.480000, 20, "\x80\x62\x00\x18\x00\x00\x8F\x78\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=25, TS=36888 */ + {0.500000, 20, "\x80\x62\x00\x19\x00\x00\x90\x18\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* SSRC changed to 0x50607080, RTP timestamp jump, Delay of 1.5s, + * SeqNo jump */ + /* RTP: SeqNo=1000, TS=160000 */ + {2.000000, 20, "\x80\x62\x03\xE8\x00\x02\x71\x00\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1001, TS=160160 */ + {2.020000, 20, "\x80\x62\x03\xE9\x00\x02\x71\xA0\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1002, TS=160320 */ + {2.040000, 20, "\x80\x62\x03\xEA\x00\x02\x72\x40\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, +}; + +void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, + char *data, int len); + +static void test_packet_error_detection(int patch_ssrc, int patch_ts) +{ + int i; + + struct mgcp_trunk_config trunk; + struct mgcp_endpoint endp; + struct mgcp_rtp_state state; + struct mgcp_rtp_end *rtp = &endp.net_end; + struct sockaddr_in addr = {0}; + char buffer[4096]; + uint32_t last_ssrc = 0; + uint32_t last_timestamp = 0; + uint32_t last_seqno = 0; + int last_in_ts_err_cnt = 0; + int last_out_ts_err_cnt = 0; + + printf("Testing packet error detection%s%s.\n", + patch_ssrc ? ", patch SSRC" : "", + patch_ts ? ", patch timestamps" : ""); + + memset(&trunk, 0, sizeof(trunk)); + memset(&endp, 0, sizeof(endp)); + memset(&state, 0, sizeof(state)); + + trunk.number_endpoints = 1; + trunk.endpoints = &endp; + trunk.force_constant_ssrc = patch_ssrc; + trunk.force_aligned_timing = patch_ts; + + endp.tcfg = &trunk; + + mgcp_initialize_endp(&endp); + + rtp->codec.payload_type = 98; + + for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) { + struct rtp_packet_info *info = test_rtp_packets1 + i; + + force_monotonic_time_us = round(1000000.0 * info->txtime); + + OSMO_ASSERT(info->len <= sizeof(buffer)); + OSMO_ASSERT(info->len >= 0); + memmove(buffer, info->data, info->len); + + mgcp_rtp_end_config(&endp, 1, rtp); + + mgcp_patch_and_count(&endp, &state, rtp, &addr, + buffer, info->len); + + if (state.out_stream.ssrc != last_ssrc) { + printf("Output SSRC changed to %08x\n", + state.out_stream.ssrc); + last_ssrc = state.out_stream.ssrc; + } + + printf("In TS: %d, dTS: %d, Seq: %d\n", + state.in_stream.last_timestamp, + state.in_stream.last_tsdelta, + state.in_stream.last_seq); + + printf("Out TS change: %d, dTS: %d, Seq change: %d, " + "TS Err change: in %+d, out %+d\n", + state.out_stream.last_timestamp - last_timestamp, + state.out_stream.last_tsdelta, + state.out_stream.last_seq - last_seqno, + state.in_stream.err_ts_counter - last_in_ts_err_cnt, + state.out_stream.err_ts_counter - last_out_ts_err_cnt); + + printf("Stats: Jitter = %u, Transit = %d\n", + mgcp_state_calc_jitter(&state), state.stats_transit); + + last_in_ts_err_cnt = state.in_stream.err_ts_counter; + last_out_ts_err_cnt = state.out_stream.err_ts_counter; + last_timestamp = state.out_stream.last_timestamp; + last_seqno = state.out_stream.last_seq; + } + + force_monotonic_time_us = -1; +} + +static void test_multilple_codec(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + struct msgb *inp, *resp; + struct in_addr addr; + + printf("Testing multiple payload types\n"); + + cfg = mgcp_config_alloc(); + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + cfg->policy_cb = mgcp_test_policy_cb; + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* Allocate endpoint 1@mgw with two codecs */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_1); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 1); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 18); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 97); + + /* Allocate 2@mgw with three codecs, last one ignored */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_2); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 2); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 18); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 97); + + /* Allocate 3@mgw with no codecs, check for PT == -1 */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_3); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 3); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == -1); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + + /* Allocate 4@mgw with a single codec */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_4); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 4); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 18); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + + /* Allocate 5@mgw at select GSM.. */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_GSM_EXACT); + talloc_free(cfg->trunk.audio_name); + cfg->trunk.audio_name = "GSM/8000"; + cfg->trunk.no_audio_transcoding = 1; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 3); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + + inp = create_msg(MDCX_NAT_DUMMY); + last_endpoint = -1; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 3); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + OSMO_ASSERT(endp->net_end.rtp_port == htons(16434)); + memset(&addr, 0, sizeof(addr)); + inet_aton("8.8.8.8", &addr); + OSMO_ASSERT(endp->net_end.addr.s_addr == addr.s_addr); + + /* Check what happens without that flag */ + + /* Free the previous endpoint and the data ... */ + mgcp_release_endp(endp); + talloc_free(endp->last_response); + talloc_free(endp->last_trans); + endp->last_response = endp->last_trans = NULL; + + last_endpoint = -1; + inp = create_msg(CRCX_MULT_GSM_EXACT); + cfg->trunk.no_audio_transcoding = 0; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 255); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 0); + + talloc_free(cfg); +} + +static void test_no_cycle(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + + printf("Testing no sequence flow on initial packet\n"); + + cfg = mgcp_config_alloc(); + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + endp = &cfg->trunk.endpoints[1]; + OSMO_ASSERT(endp->net_state.stats_initialized == 0); + + mgcp_rtp_annex_count(endp, &endp->net_state, 0, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == 0); + OSMO_ASSERT(endp->net_state.stats_max_seq == 0); + + mgcp_rtp_annex_count(endp, &endp->net_state, 1, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == 0); + OSMO_ASSERT(endp->net_state.stats_max_seq == 1); + + /* now jump.. */ + mgcp_rtp_annex_count(endp, &endp->net_state, UINT16_MAX, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == 0); + OSMO_ASSERT(endp->net_state.stats_max_seq == UINT16_MAX); + + /* and wrap */ + mgcp_rtp_annex_count(endp, &endp->net_state, 0, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == UINT16_MAX + 1); + OSMO_ASSERT(endp->net_state.stats_max_seq == 0); + + talloc_free(cfg); +} + +static void test_no_name(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + struct msgb *inp, *msg; + int i; + + printf("Testing no rtpmap name\n"); + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + cfg->trunk.audio_send_name = 0; + mgcp_endpoints_allocate(&cfg->trunk); + + cfg->policy_cb = mgcp_test_policy_cb; + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* reset endpoints */ + for (i = 0; i < cfg->trunk.number_endpoints; i++) { + endp = &cfg->trunk.endpoints[i]; + endp->net_end.codec.payload_type = PTYPE_NONE; + endp->net_end.packet_duration_ms = -1; + + OSMO_ASSERT(endp->conn_mode == MGCP_CONN_NONE); + endp->conn_mode |= CONN_UNMODIFIED; + } + + inp = create_msg(CRCX); + msg = mgcp_handle_message(cfg, inp); + if (strcmp((char *) msg->data, CRCX_RET_NO_RTPMAP) != 0) + printf("FAILED: there should not be a RTPMAP: %s\n", + (char *) msg->data); + msgb_free(inp); + msgb_free(msg); + + mgcp_release_endp(&cfg->trunk.endpoints[1]); + talloc_free(cfg); +} + +static void test_osmux_cid(void) +{ + int id, i; + + OSMO_ASSERT(osmux_used_cid() == 0); + id = osmux_get_cid(); + OSMO_ASSERT(id == 0); + OSMO_ASSERT(osmux_used_cid() == 1); + osmux_put_cid(id); + OSMO_ASSERT(osmux_used_cid() == 0); + + for (i = 0; i < 256; ++i) { + id = osmux_get_cid(); + OSMO_ASSERT(id == i); + OSMO_ASSERT(osmux_used_cid() == i + 1); + } + + id = osmux_get_cid(); + OSMO_ASSERT(id == -1); + + for (i = 0; i < 256; ++i) + osmux_put_cid(i); + OSMO_ASSERT(osmux_used_cid() == 0); +} + +static const struct log_info_cat log_categories[] = { +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + msgb_talloc_ctx_init(NULL, 0); + osmo_init_logging(&log_info); + + test_strline(); + test_values(); + test_messages(); + test_retransmission(); + test_packet_loss_calc(); + test_rqnt_cb(); + test_mgcp_stats(); + test_packet_error_detection(1, 0); + test_packet_error_detection(0, 0); + test_packet_error_detection(0, 1); + test_packet_error_detection(1, 1); + test_multilple_codec(); + test_no_cycle(); + test_no_name(); + test_osmux_cid(); + + printf("Done\n"); + return EXIT_SUCCESS; +} diff --git a/tests/legacy_mgcp/mgcp_test.ok b/tests/legacy_mgcp/mgcp_test.ok new file mode 100644 index 0000000..4e27282 --- /dev/null +++ b/tests/legacy_mgcp/mgcp_test.ok @@ -0,0 +1,480 @@ +line: 'one CR' +line: 'two CR' +line: '' +line: 'one CRLF' +line: 'two CRLF' +line: '' +line: 'one LF' +line: 'two LF' +line: '' +line: 'mixed (4 lines)' +line: '' +line: '' +line: '' +Testing AUEP1 +Testing AUEP2 +Testing MDCX1 +Testing MDCX2 +Testing CRCX +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing MDCX3 +Dummy packets: 1 +Packet duration not set +Requested packetization period not set +Connection mode not set +Testing MDCX4 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 3: SEND RECV +Testing MDCX4_PT1 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-40 +Connection mode: 3: SEND RECV +Testing MDCX4_PT2 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 3: SEND RECV +Testing MDCX4_PT3 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetization period not set +Connection mode: 3: SEND RECV +Testing MDCX4_SO +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 2: SEND +Testing MDCX4_RO +Dummy packets: 1 +Packet duration not set +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing DLCX +Detected packet duration: 20 +Requested packetization period not set +Connection mode: 0: NONE +Testing CRCX_ZYN +Dummy packets: 1 +Packet duration not set +Requested packetization period not set +Connection mode: 1: RECV +Testing EMPTY +Testing SHORT1 +Testing SHORT2 +Testing SHORT3 +Testing SHORT4 +Testing RQNT1 +Testing RQNT2 +Testing DLCX +Detected packet duration: 20 +Requested packetization period not set +Connection mode: 0: NONE +Testing CRCX +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing MDCX3 +Dummy packets: 1 +Packet duration not set +Requested packetization period not set +Connection mode not set +Testing DLCX +Detected packet duration: 20 +Requested packetization period not set +Connection mode: 0: NONE +Testing CRCX +Re-transmitting CRCX +Testing RQNT1 +Re-transmitting RQNT1 +Testing RQNT2 +Re-transmitting RQNT2 +Testing MDCX3 +Re-transmitting MDCX3 +Testing DLCX +Re-transmitting DLCX +Testing packet loss calculation. +Testing stat parsing +Parsing result: 0 +Parsing result: 0 +Testing packet error detection, patch SSRC. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +Output SSRC changed to 10203040 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +Output SSRC changed to 50607080 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection, patch timestamps. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +Output SSRC changed to 10203040 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +Output SSRC changed to 50607080 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection, patch SSRC, patch timestamps. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing multiple payload types +Testing no sequence flow on initial packet +Testing no rtpmap name +Done diff --git a/tests/legacy_mgcp/mgcp_transcoding_test.c b/tests/legacy_mgcp/mgcp_transcoding_test.c new file mode 100644 index 0000000..61de25f --- /dev/null +++ b/tests/legacy_mgcp/mgcp_transcoding_test.c @@ -0,0 +1,661 @@ +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <err.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> + +#include <osmocom/netif/rtp.h> + +#include <osmocom/legacy_mgcp/mgcp.h> +#include <osmocom/legacy_mgcp/mgcp_internal.h> + +#include "bscconfig.h" +#ifndef BUILD_MGCP_TRANSCODING +#error "Requires MGCP transcoding enabled (see --enable-mgcp-transcoding)" +#endif + +#include <osmocom/legacy_mgcp/mgcp_transcode.h> + +uint8_t *audio_frame_l16[] = { +}; + +struct rtp_packets { + float t; + int len; + char *data; +}; + +struct rtp_packets audio_packets_l16[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 332, + "\x80\x0B\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + }, +}; + +struct rtp_packets audio_packets_gsm[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 45, + "\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B" + "\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A" + "\xDE" + }, +}; + +struct rtp_packets audio_packets_gsm_invalid_size[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 41, + "\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B" + "\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A" + "\xDE" + }, +}; + +struct rtp_packets audio_packets_gsm_invalid_data[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 45, + "\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE" + "\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE" + "\xEE" + }, +}; + +struct rtp_packets audio_packets_gsm_invalid_ptype[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 45, + "\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B" + "\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A" + "\xDE" + }, +}; + +struct rtp_packets audio_packets_g729[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 32, + "\x80\x12\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xAF\xC2\x81\x40\x00\xFA\xCE\xA4\x21\x7C\xC5\xC3\x4F\xA5\x98\xF5" + "\xB2\x95\xC4\xAD" + }, +}; + +struct rtp_packets audio_packets_pcma[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 172, + "\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + }, + /* RTP: SeqNo=26527, TS=232640 */ + {0.020000, 92, + "\x80\x08\x67\x9f\x00\x03\x8c\xc0\x04\xaa\x67\x9f\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\x55\x55\xd5\xd5\x55\x55\xd5\xd5\x55\x55" + "\xd5\xd5\xd5\x55\x55\xd5\xd5\xd5\x55\x55\xd5\xd5" + }, + /* RTP: SeqNo=26528, TS=232720 */ + {0.020000, 92, + "\x80\x08\x67\xa0\x00\x03\x8d\x10\x04\xaa\x67\x9f\x55\xd5\xd5\x55" + "\xd5\x55\xd5\xd5\xd5\x55\xd5\x55\xd5\xd5\x55\xd5\x55\xd5\x55\xd5" + "\x55\x55\xd5\x55\xd5\xd5\x55\x55\x55\x55\x55\xd5\xd5\x55\xd5\xd5" + "\xd5\x55\xd5\xd5\xd5\x55\x54\x55\xd5\xd5\x55\xd5\xd5\xd5\xd5\x55" + "\x54\x55\xd5\x55\xd5\x55\x55\x55\x55\x55\xd5\xd5\xd5\xd5\xd5\xd4" + "\xd5\x54\x55\xd5\xd4\xd5\x54\xd5\x55\xd5\xd5\xd5" + }, +}; + + + +static int audio_name_to_type(const char *name) +{ + if (!strcasecmp(name, "gsm")) + return 3; +#ifdef HAVE_BCG729 + else if (!strcasecmp(name, "g729")) + return 18; +#endif + else if (!strcasecmp(name, "pcma")) + return 8; + else if (!strcasecmp(name, "l16")) + return 11; + return -1; +} + +int mgcp_get_trans_frame_size(void *state_, int nsamples, int dst); + +static int given_configured_endpoint(int in_samples, int out_samples, + const char *srcfmt, const char *dstfmt, + void **out_ctx, struct mgcp_endpoint **out_endp) +{ + int rc; + struct mgcp_rtp_end *dst_end; + struct mgcp_rtp_end *src_end; + struct mgcp_config *cfg; + struct mgcp_trunk_config *tcfg; + struct mgcp_endpoint *endp; + + cfg = mgcp_config_alloc(); + tcfg = talloc_zero(cfg, struct mgcp_trunk_config); + endp = talloc_zero(tcfg, struct mgcp_endpoint); + + cfg->setup_rtp_processing_cb = mgcp_transcoding_setup; + cfg->rtp_processing_cb = mgcp_transcoding_process_rtp; + cfg->get_net_downlink_format_cb = mgcp_transcoding_net_downlink_format; + + tcfg->endpoints = endp; + tcfg->number_endpoints = 1; + tcfg->cfg = cfg; + endp->tcfg = tcfg; + endp->cfg = cfg; + mgcp_initialize_endp(endp); + + dst_end = &endp->bts_end; + dst_end->codec.payload_type = audio_name_to_type(dstfmt); + + src_end = &endp->net_end; + src_end->codec.payload_type = audio_name_to_type(srcfmt); + + if (out_samples) { + dst_end->codec.frame_duration_den = dst_end->codec.rate; + dst_end->codec.frame_duration_num = out_samples; + dst_end->frames_per_packet = 1; + dst_end->force_output_ptime = 1; + } + + rc = mgcp_transcoding_setup(endp, dst_end, src_end); + if (rc < 0) { + printf("setup failed: %s", strerror(-rc)); + abort(); + } + + *out_ctx = cfg; + *out_endp = endp; + return 0; +} + + +static int transcode_test(const char *srcfmt, const char *dstfmt, + uint8_t *src_pkts, size_t src_pkt_size) +{ + char buf[4096] = {0x80, 0}; + void *ctx; + + struct mgcp_rtp_end *dst_end; + struct mgcp_process_rtp_state *state; + struct mgcp_endpoint *endp; + int in_size; + const int in_samples = 160; + int len, cont; + + printf("== Transcoding test ==\n"); + printf("converting %s -> %s\n", srcfmt, dstfmt); + + given_configured_endpoint(in_samples, 0, srcfmt, dstfmt, &ctx, &endp); + + dst_end = &endp->bts_end; + state = dst_end->rtp_process_data; + OSMO_ASSERT(state != NULL); + + in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0); + OSMO_ASSERT(sizeof(buf) >= in_size + 12); + + memcpy(buf, src_pkts, src_pkt_size); + + len = src_pkt_size; + + cont = mgcp_transcoding_process_rtp(endp, dst_end, + buf, &len, sizeof(buf)); + if (cont < 0) { + printf("Nothing encoded due: %s\n", strerror(-cont)); + talloc_free(ctx); + return -1; + } + + if (len < 24) { + printf("encoded: %s\n", osmo_hexdump((unsigned char *)buf, len)); + } else { + const char *str = osmo_hexdump((unsigned char *)buf, len); + int i = 0; + const int prefix = 4; + const int cutlen = 48; + int nchars = 0; + + printf("encoded:\n"); + do { + nchars = printf("%*s%-.*s", prefix, "", cutlen, str + i); + i += nchars - prefix; + printf("\n"); + } while (nchars - prefix >= cutlen); + } + printf("counted: %d\n", cont); + talloc_free(ctx); + return 0; +} + +static void test_rtp_seq_state(void) +{ + char buf[4096]; + int len; + int cont; + void *ctx; + struct mgcp_endpoint *endp; + struct mgcp_process_rtp_state *state; + struct rtp_hdr *hdr; + uint32_t ts_no; + uint16_t seq_no; + + given_configured_endpoint(160, 0, "pcma", "l16", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + OSMO_ASSERT(!state->is_running); + OSMO_ASSERT(state->next_seq == 0); + OSMO_ASSERT(state->next_time == 0); + + /* initialize packet */ + len = audio_packets_pcma[0].len; + memcpy(buf, audio_packets_pcma[0].data, len); + cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, len); + OSMO_ASSERT(cont >= 0); + OSMO_ASSERT(state->is_running); + OSMO_ASSERT(state->next_seq == 2); + OSMO_ASSERT(state->next_time == 240); + + /* verify that the right timestamp was written */ + OSMO_ASSERT(len == audio_packets_pcma[0].len); + hdr = (struct rtp_hdr *) &buf[0]; + + memcpy(&ts_no, &hdr->timestamp, sizeof(ts_no)); + OSMO_ASSERT(htonl(ts_no) == 160); + memcpy(&seq_no, &hdr->sequence, sizeof(seq_no)); + OSMO_ASSERT(htons(seq_no) == 1); + /* Check the right sequence number is written */ + state->next_seq = 1234; + len = audio_packets_pcma[0].len; + memcpy(buf, audio_packets_pcma[0].data, len); + cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, len); + OSMO_ASSERT(cont >= 0); + OSMO_ASSERT(len == audio_packets_pcma[0].len); + hdr = (struct rtp_hdr *) &buf[0]; + + memcpy(&seq_no, &hdr->sequence, sizeof(seq_no)); + OSMO_ASSERT(htons(seq_no) == 1234); + + talloc_free(ctx); +} + +static void test_transcode_result(void) +{ + char buf[4096]; + int len, res; + void *ctx; + struct mgcp_endpoint *endp; + struct mgcp_process_rtp_state *state; + + { + /* from GSM to PCMA and same ptime */ + given_configured_endpoint(160, 0, "gsm", "pcma", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* result */ + len = audio_packets_gsm[0].len; + memcpy(buf, audio_packets_gsm[0].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + OSMO_ASSERT(state->sample_cnt == 0); + + len = res; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == -ENOMSG); + + talloc_free(ctx); + } + + { + /* from GSM to PCMA and same ptime */ + given_configured_endpoint(160, 160, "gsm", "pcma", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* result */ + len = audio_packets_gsm[0].len; + memcpy(buf, audio_packets_gsm[0].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + OSMO_ASSERT(state->sample_cnt == 0); + + len = res; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == -EAGAIN); + + talloc_free(ctx); + } + + { + /* from PCMA to GSM and wrong different ptime */ + given_configured_endpoint(80, 160, "pcma", "gsm", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* Add the first sample */ + len = audio_packets_pcma[1].len; + memcpy(buf, audio_packets_pcma[1].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->next_time == 232640); + OSMO_ASSERT(res < 0); + + /* Add the second sample and it should be consumable */ + len = audio_packets_pcma[2].len; + memcpy(buf, audio_packets_pcma[2].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state->sample_cnt == 0); + OSMO_ASSERT(state->next_time == 232640 + 80 + 160); + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + + talloc_free(ctx); + } + + { + /* from PCMA to GSM with a big time jump */ + struct rtp_hdr *hdr; + uint32_t ts; + + given_configured_endpoint(80, 160, "pcma", "gsm", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* Add the first sample */ + len = audio_packets_pcma[1].len; + memcpy(buf, audio_packets_pcma[1].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->next_time == 232640); + OSMO_ASSERT(state->next_seq == 26527); + OSMO_ASSERT(res < 0); + + /* Add a skip to the packet to force a 'resync' */ + len = audio_packets_pcma[2].len; + memcpy(buf, audio_packets_pcma[2].data, len); + hdr = (struct rtp_hdr *) &buf[0]; + /* jump the time and add alignment error */ + ts = ntohl(hdr->timestamp) + 123 * 80 + 2; + hdr->timestamp = htonl(ts); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res < 0); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->next_time == ts); + OSMO_ASSERT(state->next_seq == 26527); + /* TODO: this can create alignment errors */ + + + /* Now attempt to consume 160 samples */ + len = audio_packets_pcma[2].len; + memcpy(buf, audio_packets_pcma[2].data, len); + hdr = (struct rtp_hdr *) &buf[0]; + ts += 80; + hdr->timestamp = htonl(ts); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == 12); + OSMO_ASSERT(state->sample_cnt == 0); + OSMO_ASSERT(state->next_time == ts + 160); + OSMO_ASSERT(state->next_seq == 26528); + + talloc_free(ctx); + } +} + +static void test_transcode_change(void) +{ + char buf[4096] = {0x80, 0}; + void *ctx; + + struct mgcp_endpoint *endp; + struct mgcp_process_rtp_state *state; + struct rtp_hdr *hdr; + + int len, res; + + { + /* from GSM to PCMA and same ptime */ + printf("Testing Initial L16->GSM, PCMA->GSM\n"); + given_configured_endpoint(160, 0, "l16", "gsm", &ctx, &endp); + endp->net_end.alt_codec = endp->net_end.codec; + endp->net_end.alt_codec.payload_type = audio_name_to_type("pcma"); + state = endp->bts_end.rtp_process_data; + + /* initial transcoding work */ + OSMO_ASSERT(state->src_fmt == AF_L16); + OSMO_ASSERT(state->dst_fmt == AF_GSM); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 8); + OSMO_ASSERT(endp->net_end.codec.payload_type == 11); + + /* result */ + len = audio_packets_pcma[0].len; + memcpy(buf, audio_packets_pcma[0].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + state = endp->bts_end.rtp_process_data; + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + OSMO_ASSERT(state->sample_cnt == 0); + OSMO_ASSERT(state->src_fmt == AF_PCMA); + OSMO_ASSERT(state->dst_fmt == AF_GSM); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 11); + OSMO_ASSERT(endp->net_end.codec.payload_type == 8); + + len = res; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == -ENOMSG); + OSMO_ASSERT(state == endp->bts_end.rtp_process_data); + + + /* now check that comfort noise doesn't change anything */ + len = audio_packets_pcma[1].len; + memcpy(buf, audio_packets_pcma[1].data, len); + hdr = (struct rtp_hdr *) buf; + hdr->payload_type = 12; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state == endp->bts_end.rtp_process_data); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->src_fmt == AF_PCMA); + OSMO_ASSERT(state->dst_fmt == AF_GSM); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 11); + OSMO_ASSERT(endp->net_end.codec.payload_type == 8); + + talloc_free(ctx); + } +} + +static int test_repacking(int in_samples, int out_samples, int no_transcode) +{ + char buf[4096] = {0x80, 0}; + int cc; + struct mgcp_endpoint *endp; + void *ctx; + + struct mgcp_process_rtp_state *state; + int in_cnt; + int out_size; + int in_size; + uint32_t ts = 0; + uint16_t seq = 0; + const char *srcfmt = "pcma"; + const char *dstfmt = no_transcode ? "pcma" : "l16"; + + printf("== Transcoding test ==\n"); + printf("converting %s -> %s\n", srcfmt, dstfmt); + + given_configured_endpoint(in_samples, out_samples, srcfmt, dstfmt, &ctx, &endp); + + state = endp->bts_end.rtp_process_data; + OSMO_ASSERT(state != NULL); + + in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0); + OSMO_ASSERT(sizeof(buf) >= in_size + 12); + + out_size = mgcp_transcoding_get_frame_size(state, -1, 1); + OSMO_ASSERT(sizeof(buf) >= out_size + 12); + + buf[1] = endp->net_end.codec.payload_type; + *(uint16_t*)(buf+2) = htons(1); + *(uint32_t*)(buf+4) = htonl(0); + *(uint32_t*)(buf+8) = htonl(0xaabbccdd); + + for (in_cnt = 0; in_cnt < 16; in_cnt++) { + int cont; + int len; + + /* fake PCMA data */ + printf("generating %d %s input samples\n", in_samples, srcfmt); + for (cc = 0; cc < in_samples; cc++) + buf[12+cc] = cc; + + *(uint16_t*)(buf+2) = htonl(seq); + *(uint32_t*)(buf+4) = htonl(ts); + + seq += 1; + ts += in_samples; + + cc += 12; /* include RTP header */ + + len = cc; + + do { + cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, + buf, &len, sizeof(buf)); + if (cont == -EAGAIN) { + fprintf(stderr, "Got EAGAIN\n"); + break; + } + + if (cont < 0) { + printf("processing failed: %s", strerror(-cont)); + abort(); + } + + len -= 12; /* ignore RTP header */ + + printf("got %d %s output frames (%d octets) count=%d\n", + len / out_size, dstfmt, len, cont); + + len = cont; + } while (len > 0); + } + + talloc_free(ctx); + return 0; +} + +static const struct log_info_cat log_categories[] = { +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + int rc; + osmo_init_logging(&log_info); + + printf("=== Transcoding Good Cases ===\n"); + + transcode_test("l16", "l16", + (uint8_t *)audio_packets_l16[0].data, + audio_packets_l16[0].len); + transcode_test("l16", "gsm", + (uint8_t *)audio_packets_l16[0].data, + audio_packets_l16[0].len); + transcode_test("l16", "pcma", + (uint8_t *)audio_packets_l16[0].data, + audio_packets_l16[0].len); + transcode_test("gsm", "l16", + (uint8_t *)audio_packets_gsm[0].data, + audio_packets_gsm[0].len); + transcode_test("gsm", "gsm", + (uint8_t *)audio_packets_gsm[0].data, + audio_packets_gsm[0].len); + transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm[0].data, + audio_packets_gsm[0].len); + transcode_test("pcma", "l16", + (uint8_t *)audio_packets_pcma[0].data, + audio_packets_pcma[0].len); + transcode_test("pcma", "gsm", + (uint8_t *)audio_packets_pcma[0].data, + audio_packets_pcma[0].len); + transcode_test("pcma", "pcma", + (uint8_t *)audio_packets_pcma[0].data, + audio_packets_pcma[0].len); + + printf("=== Transcoding Bad Cases ===\n"); + + printf("Invalid size:\n"); + rc = transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm_invalid_size[0].data, + audio_packets_gsm_invalid_size[0].len); + OSMO_ASSERT(rc < 0); + + printf("Invalid data:\n"); + rc = transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm_invalid_data[0].data, + audio_packets_gsm_invalid_data[0].len); + OSMO_ASSERT(rc < 0); + + printf("Invalid payload type:\n"); + rc = transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm_invalid_ptype[0].data, + audio_packets_gsm_invalid_ptype[0].len); + OSMO_ASSERT(rc == 0); + + printf("=== Repacking ===\n"); + + test_repacking(160, 160, 0); + test_repacking(160, 160, 1); + test_repacking(160, 80, 0); + test_repacking(160, 80, 1); + test_repacking(160, 320, 0); + test_repacking(160, 320, 1); + test_repacking(160, 240, 0); + test_repacking(160, 240, 1); + test_repacking(160, 100, 0); + test_repacking(160, 100, 1); + test_rtp_seq_state(); + test_transcode_result(); + test_transcode_change(); + + return 0; +} + diff --git a/tests/legacy_mgcp/mgcp_transcoding_test.ok b/tests/legacy_mgcp/mgcp_transcoding_test.ok new file mode 100644 index 0000000..387cfd2 --- /dev/null +++ b/tests/legacy_mgcp/mgcp_transcoding_test.ok @@ -0,0 +1,539 @@ +=== Transcoding Good Cases === +== Transcoding test == +converting l16 -> l16 +encoded: + 80 0b 00 01 00 00 00 a0 11 22 33 44 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed +counted: 0 +== Transcoding test == +converting l16 -> gsm +encoded: + 80 0b 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 + 62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc + 69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de +counted: 12 +== Transcoding test == +converting l16 -> pcma +encoded: + 80 0b 00 01 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 +counted: 12 +== Transcoding test == +converting gsm -> l16 +encoded: + 80 03 00 01 00 00 00 a0 11 22 33 44 00 00 54 00 + 59 f0 34 20 c4 c8 b9 f8 e2 18 f1 e8 f2 28 f0 e0 + 46 08 4f 80 2c a0 a9 c8 80 00 c0 58 3f 80 63 c0 + 24 b8 fa b8 f6 88 0b a0 c8 70 a8 b0 c8 c0 3b a8 + 66 a0 2e 38 d1 f8 98 98 aa 18 e8 30 26 a0 37 40 + 37 e8 17 00 ee 50 b7 80 b1 88 de 28 18 40 45 b0 + 4f 48 21 d8 df 78 ae 68 ab 98 d6 b8 18 18 48 90 + 4e 70 27 40 e8 10 b5 b0 ac 80 d4 60 14 50 48 48 + 50 10 2a 00 ec 08 ba 00 af 58 d1 c0 10 60 45 c8 + 54 10 30 78 f1 a8 bb 18 ad 48 ce 30 0a e8 3f 30 + 4f 10 32 18 f6 18 bf 20 ac 30 cd 80 0b d0 43 d8 + 55 e0 34 a0 f5 78 bc 98 ad 98 cd c8 0a 80 40 58 + 51 c0 35 40 f9 60 c1 68 ac c8 cb 38 08 00 40 98 + 51 e0 34 d8 fa 28 c2 f0 ae 40 c7 70 02 d0 3c a8 + 54 78 38 a0 fc 68 c2 08 ad 50 c7 78 01 60 39 c0 + 51 38 3a e8 00 e8 c6 38 ab d8 c4 00 fe 08 39 18 + 50 30 39 50 01 d8 ca 70 b1 80 c4 c8 fc 58 36 40 + 51 d8 3b 08 02 80 c8 58 b0 60 c5 a8 fb d0 33 e8 + 4e 80 3c e0 06 10 cb 90 ae 48 c2 60 f9 58 34 08 + 4d a0 3a a8 06 48 cf 80 b4 60 c3 e8 f7 90 30 18 + 4d a0 3b 98 07 90 cf 18 b4 68 c4 88 +counted: 12 +== Transcoding test == +converting gsm -> gsm +encoded: + 80 03 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 + 62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc + 69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de +counted: 0 +== Transcoding test == +converting gsm -> pcma +encoded: + 80 03 00 01 00 00 00 a0 11 22 33 44 d5 a0 a3 bf + 38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 + 17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 + 04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 + 02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd + 19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf + 10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf + 62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 + db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 + f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 + ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 +counted: 12 +== Transcoding test == +converting pcma -> l16 +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 +counted: 12 +== Transcoding test == +converting pcma -> gsm +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 d4 b9 f4 5d + d9 50 5a e1 a0 cd 76 ea 52 0e 87 53 ad d4 ea a2 + 0a 63 ca e9 60 79 e2 2a 25 d2 c0 f3 39 +counted: 12 +== Transcoding test == +converting pcma -> pcma +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 +counted: 0 +=== Transcoding Bad Cases === +Invalid size: +== Transcoding test == +converting gsm -> pcma +Nothing encoded due: No message of desired type +Invalid data: +== Transcoding test == +converting gsm -> pcma +Nothing encoded due: No message of desired type +Invalid payload type: +== Transcoding test == +converting gsm -> pcma +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 d5 a0 a3 bf + 38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 + 17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 + 04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 + 02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd + 19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf + 10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf + 62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 + db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 + f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 + ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 +counted: 12 +=== Repacking === +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +Testing Initial L16->GSM, PCMA->GSM diff --git a/tests/mgcp/Makefile.am b/tests/mgcp/Makefile.am new file mode 100644 index 0000000..caccb9f --- /dev/null +++ b/tests/mgcp/Makefile.am @@ -0,0 +1,38 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + mgcp_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + mgcp_test \ + $(NULL) + +mgcp_test_SOURCES = \ + mgcp_test.c \ + $(NULL) + +mgcp_test_LDADD = \ + $(top_builddir)/src/libosmo-mgcp/libosmo-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBRARY_DL) \ + $(LIBOSMONETIF_LIBS) \ + -lm \ + $(NULL) diff --git a/tests/mgcp/mgcp_test.c b/tests/mgcp/mgcp_test.c new file mode 100644 index 0000000..e14b7ee --- /dev/null +++ b/tests/mgcp/mgcp_test.c @@ -0,0 +1,1353 @@ +/* + * (C) 2011-2012,2014 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2011-2012,2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#undef _GNU_SOURCE +#define _GNU_SOURCE + +#include <osmocom/mgcp/mgcp.h> +#include <osmocom/mgcp/vty.h> +#include <osmocom/mgcp/mgcp_common.h> +#include <osmocom/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/mgcp_stat.h> +#include <osmocom/mgcp/mgcp_msg.h> +#include <osmocom/mgcp/mgcp_ep.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <string.h> +#include <limits.h> +#include <dlfcn.h> +#include <time.h> +#include <math.h> + +char *strline_r(char *str, char **saveptr); + +const char *strline_test_data = + "one CR\r" + "two CR\r" + "\r" + "one CRLF\r\n" + "two CRLF\r\n" + "\r\n" "one LF\n" "two LF\n" "\n" "mixed (4 lines)\r\r\n\n\r\n"; + +#define EXPECTED_NUMBER_OF_LINES 13 + +static void test_strline(void) +{ + char *save = NULL; + char *line; + char buf[2048]; + int counter = 0; + + osmo_strlcpy(buf, strline_test_data, sizeof(buf)); + + for (line = mgcp_strline(buf, &save); line; + line = mgcp_strline(NULL, &save)) { + printf("line: '%s'\n", line); + counter++; + } + + OSMO_ASSERT(counter == EXPECTED_NUMBER_OF_LINES); +} + +#define AUEP1 "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n" +#define AUEP1_RET "200 158663169 OK\r\n" +#define AUEP2 "AUEP 18983213 ds/e1-2/1@172.16.6.66 MGCP 1.0\r\n" +#define AUEP2_RET "500 18983213 FAIL\r\n" +#define EMPTY "\r\n" +#define EMPTY_RET NULL +#define SHORT "CRCX \r\n" +#define SHORT_RET "510 000000 FAIL\r\n" + +#define MDCX_WRONG_EP "MDCX 18983213 ds/e1-3/1@172.16.6.66 MGCP 1.0\r\n" +#define MDCX_ERR_RET "510 18983213 FAIL\r\n" +#define MDCX_UNALLOCATED "MDCX 18983214 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n" +#define MDCX_RET "400 18983214 FAIL\r\n" + +#define MDCX3 \ + "MDCX 18983215 1@mgw MGCP 1.0\r\n" \ + "I: 1\n" + +#define MDCX3_RET \ + "200 18983215 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16002 RTP/AVP 128\r\n" \ + "a=rtpmap:128 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX3A_RET \ + "200 18983215 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16002 RTP/AVP 97\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX3_FMTP_RET \ + "200 18983215 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16006 RTP/AVP 128\r\n" \ + "a=rtpmap:128 GSM-EFR/8000\r\n" \ + "a=fmtp:126 0/1/2\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4 \ + "MDCX 18983216 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_RET(Ident) \ + "200 " Ident " OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16002 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_RO_RET(Ident) \ + "200 " Ident " OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16002 RTP/AVP 96\r\n" \ + "a=rtpmap:96 AMR\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_PT1 \ + "MDCX 18983217 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20-40, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_PT2 \ + "MDCX 18983218 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20-20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_PT3 \ + "MDCX 18983219 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_SO \ + "MDCX 18983220 1@mgw MGCP 1.0\r\n" \ + "M: sendonly\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_RO \ + "MDCX 18983221 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" + +#define SHORT2 "CRCX 1" +#define SHORT2_RET "510 000000 FAIL\r\n" +#define SHORT3 "CRCX 1 1@mgw" +#define SHORT4 "CRCX 1 1@mgw MGCP" +#define SHORT5 "CRCX 1 1@mgw MGCP 1.0" + +#define CRCX \ + "CRCX 2 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 97\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_RET \ + "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16002 RTP/AVP 97\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_RET_NO_RTPMAP \ + "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16002 RTP/AVP 97\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_FMTP_RET \ + "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16006 RTP/AVP 97\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=fmtp:126 0/1/2\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_ZYN \ + "CRCX 2 1@mgw MGCP 1.0\r" \ + "M: recvonly\r" \ + "C: 2\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r" \ + "c=IN IP4 123.12.12.123\r" \ + "m=audio 5904 RTP/AVP 97\r" \ + "a=rtpmap:97 GSM-EFR/8000\r" + +#define CRCX_ZYN_RET \ + "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16004 RTP/AVP 97\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:20\r\n" + +#define DLCX \ + "DLCX 7 1@mgw MGCP 1.0\r\n" \ + "I: 1\r\n" \ + "C: 2\r\n" + +#define DLCX_RET \ + "250 7 OK\r\n" \ + "P: PS=0, OS=0, PR=0, OR=0, PL=0, JI=0\r\n" \ + "X-Osmo-CP: EC TI=0, TO=0\r\n" + +#define RQNT \ + "RQNT 186908780 1@mgw MGCP 1.0\r\n" \ + "X: B244F267488\r\n" \ + "S: D/9\r\n" + +#define RQNT2 \ + "RQNT 186908781 1@mgw MGCP 1.0\r\n" \ + "X: ADD4F26746F\r\n" \ + "R: D/[0-9#*](N), G/ft, fxr/t38\r\n" + +#define RQNT1_RET "200 186908780 OK\r\n" +#define RQNT2_RET "200 186908781 OK\r\n" + +#define PTYPE_IGNORE 0 /* == default initializer */ +#define PTYPE_NONE 128 +#define PTYPE_NYI PTYPE_NONE + +#define CRCX_MULT_1 \ + "CRCX 2 1@mgw MGCP 1.0\r\n" \ + "I: 4711\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18 97\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_2 \ + "CRCX 2 2@mgw MGCP 1.0\r\n" \ + "I: 90210\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18 97 101\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_3 \ + "CRCX 2 3@mgw MGCP 1.0\r\n" \ + "I: 0815\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_4 \ + "CRCX 2 4@mgw MGCP 1.0\r\n" \ + "I: 32168\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_GSM_EXACT \ + "CRCX 259260421 5@mgw MGCP 1.0\r\n" \ + "C: 1355c6041e\r\n" \ + "I: 3\r\n" \ + "L: p:20, a:GSM, nt:IN\r\n" \ + "M: recvonly\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "o=- 1439038275 1439038275 IN IP4 192.168.181.247\r\n" \ + "s=-\r\nc=IN IP4 192.168.181.247\r\n" \ + "t=0 0\r\nm=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101\r\n" \ + "a=rtpmap:0 PCMU/8000\r\n" \ + "a=rtpmap:8 PCMA/8000\r\n" \ + "a=rtpmap:3 gsm/8000\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=fmtp:18 annexb=no\r\n" \ + "a=rtpmap:4 G723/8000\r\n" \ + "a=rtpmap:96 iLBC/8000\r\n" \ + "a=fmtp:96 mode=20\r\n" \ + "a=rtpmap:97 iLBC/8000\r\n" \ + "a=fmtp:97 mode=30\r\n" \ + "a=rtpmap:101 telephone-event/8000\r\n" \ + "a=fmtp:101 0-15\r\n" \ + "a=recvonly\r\n" + +#define MDCX_NAT_DUMMY \ + "MDCX 23 5@mgw MGCP 1.0\r\n" \ + "C: 1355c6041e\r\n" \ + "I: 3\r\n" \ + "\r\n" \ + "c=IN IP4 8.8.8.8\r\n" \ + "m=audio 16434 RTP/AVP 255\r\n" + +struct mgcp_test { + const char *name; + const char *req; + const char *exp_resp; + int ptype; + const char *extra_fmtp; +}; + +static const struct mgcp_test tests[] = { + {"AUEP1", AUEP1, AUEP1_RET}, + {"AUEP2", AUEP2, AUEP2_RET}, + {"MDCX1", MDCX_WRONG_EP, MDCX_ERR_RET}, + {"MDCX2", MDCX_UNALLOCATED, MDCX_RET}, + {"CRCX", CRCX, CRCX_RET, 97}, + {"MDCX3", MDCX3, MDCX3_RET, PTYPE_IGNORE}, + {"MDCX4", MDCX4, MDCX4_RET("18983216"), 99}, + {"MDCX4_PT1", MDCX4_PT1, MDCX4_RET("18983217"), 99}, + {"MDCX4_PT2", MDCX4_PT2, MDCX4_RET("18983218"), 99}, + {"MDCX4_PT3", MDCX4_PT3, MDCX4_RET("18983219"), 99}, + {"MDCX4_SO", MDCX4_SO, MDCX4_RET("18983220"), 99}, + {"MDCX4_RO", MDCX4_RO, MDCX4_RO_RET("18983221"), PTYPE_IGNORE}, + {"DLCX", DLCX, DLCX_RET, PTYPE_IGNORE}, + {"CRCX_ZYN", CRCX_ZYN, CRCX_ZYN_RET, 97}, + {"EMPTY", EMPTY, EMPTY_RET}, + {"SHORT1", SHORT, SHORT_RET}, + {"SHORT2", SHORT2, SHORT2_RET}, + {"SHORT3", SHORT3, SHORT2_RET}, + {"SHORT4", SHORT4, SHORT2_RET}, + {"RQNT1", RQNT, RQNT1_RET}, + {"RQNT2", RQNT2, RQNT2_RET}, + {"DLCX", DLCX, DLCX_RET, PTYPE_IGNORE}, + {"CRCX", CRCX, CRCX_FMTP_RET, 97,.extra_fmtp = "a=fmtp:126 0/1/2"}, + {"MDCX3", MDCX3, MDCX3_FMTP_RET, PTYPE_NONE,.extra_fmtp = + "a=fmtp:126 0/1/2"}, + {"DLCX", DLCX, DLCX_RET, PTYPE_IGNORE,.extra_fmtp = "a=fmtp:126 0/1/2"}, +}; + +static const struct mgcp_test retransmit[] = { + {"CRCX", CRCX, CRCX_RET}, + {"RQNT1", RQNT, RQNT1_RET}, + {"RQNT2", RQNT2, RQNT2_RET}, + {"MDCX3", MDCX3, MDCX3A_RET}, + {"DLCX", DLCX, DLCX_RET}, +}; + +static struct msgb *create_msg(const char *str) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + int len = sprintf((char *)msg->data, "%s", str); + msg->l2h = msgb_put(msg, len); + return msg; +} + +static int last_endpoint = -1; + +static int mgcp_test_policy_cb(struct mgcp_trunk_config *cfg, int endpoint, + int state, const char *transactio_id) +{ + fprintf(stderr, "Policy CB got state %d on endpoint %d\n", + state, endpoint); + last_endpoint = endpoint; + return MGCP_POLICY_CONT; +} + +#define MGCP_DUMMY_LOAD 0x23 +static int dummy_packets = 0; +/* override and forward */ +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + uint32_t dest_host = + htonl(((struct sockaddr_in *)dest_addr)->sin_addr.s_addr); + int dest_port = htons(((struct sockaddr_in *)dest_addr)->sin_port); + + if (len == 1 && ((const char *)buf)[0] == MGCP_DUMMY_LOAD) { + fprintf(stderr, + "Dummy packet to 0x%08x:%d, msg length %zu\n%s\n\n", + dest_host, dest_port, len, osmo_hexdump(buf, len)); + dummy_packets += 1; + } + + return len; +} + +static int64_t force_monotonic_time_us = -1; +/* override and forward */ +int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + typedef int (*clock_gettime_t)(clockid_t clk_id, struct timespec *tp); + static clock_gettime_t real_clock_gettime = NULL; + + if (!real_clock_gettime) + real_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime"); + + if (clk_id == CLOCK_MONOTONIC && force_monotonic_time_us >= 0) { + tp->tv_sec = force_monotonic_time_us / 1000000; + tp->tv_nsec = (force_monotonic_time_us % 1000000) * 1000; + return 0; + } + + return real_clock_gettime(clk_id, tp); +} + +#define CONN_UNMODIFIED (0x1000) + +static void test_values(void) +{ + /* Check that NONE disables all output */ + OSMO_ASSERT((MGCP_CONN_NONE & MGCP_CONN_RECV_SEND) == 0); + + /* Check that LOOPBACK enables all output */ + OSMO_ASSERT((MGCP_CONN_LOOPBACK & MGCP_CONN_RECV_SEND) == + MGCP_CONN_RECV_SEND); +} + +static void test_messages(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + int i; + struct mgcp_conn_rtp *conn = NULL; + + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + cfg->policy_cb = mgcp_test_policy_cb; + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + const struct mgcp_test *t = &tests[i]; + struct msgb *inp; + struct msgb *msg; + + printf("Testing %s\n", t->name); + + last_endpoint = -1; + dummy_packets = 0; + + osmo_talloc_replace_string(cfg, &cfg->trunk.audio_fmtp_extra, + t->extra_fmtp); + + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (!t->exp_resp) { + if (msg) { + printf("%s failed '%s'\n", t->name, + (char *)msg->data); + OSMO_ASSERT(false); + } + } else if (strcmp((char *)msg->data, t->exp_resp) != 0) { + printf("%s failed.\nExpected:\n%s\nGot:\n%s\n", + t->name, t->exp_resp, (char *) msg->data); + OSMO_ASSERT(false); + } + msgb_free(msg); + + if (dummy_packets) + printf("Dummy packets: %d\n", dummy_packets); + + if (last_endpoint != -1) { + endp = &cfg->trunk.endpoints[last_endpoint]; + + conn = mgcp_conn_get_rtp(endp, 1); + if (conn) { + OSMO_ASSERT(conn); + + if (conn->end.packet_duration_ms != -1) + printf("Detected packet duration: %d\n", + conn->end.packet_duration_ms); + else + printf("Packet duration not set\n"); + if (endp->local_options.pkt_period_min || + endp->local_options.pkt_period_max) + printf + ("Requested packetetization period: " + "%d-%d\n", + endp->local_options.pkt_period_min, + endp-> + local_options.pkt_period_max); + else + printf + ("Requested packetization period not set\n"); + + if ((conn->conn->mode & CONN_UNMODIFIED) == 0) { + printf("Connection mode: %d:%s%s%s%s\n", + conn->conn->mode, + !conn->conn->mode ? " NONE" : "", + conn->conn->mode & MGCP_CONN_SEND_ONLY + ? " SEND" : "", + conn->conn->mode & MGCP_CONN_RECV_ONLY + ? " RECV" : "", + conn->conn->mode & MGCP_CONN_LOOPBACK + & ~MGCP_CONN_RECV_SEND + ? " LOOP" : ""); + fprintf(stderr, + "RTP output %sabled, NET output %sabled\n", + conn->end.output_enabled + ? "en" : "dis", + conn->end.output_enabled + ? "en" : "dis"); + } else + printf("Connection mode not set\n"); + + OSMO_ASSERT(conn->end.output_enabled + == (conn->conn->mode & MGCP_CONN_SEND_ONLY ? 1 : 0)); + + conn->conn->mode |= CONN_UNMODIFIED; + + } + endp->local_options.pkt_period_min = 0; + endp->local_options.pkt_period_max = 0; + } + + /* Check detected payload type */ + if (t->ptype != PTYPE_IGNORE) { + OSMO_ASSERT(last_endpoint != -1); + endp = &cfg->trunk.endpoints[last_endpoint]; + + fprintf(stderr, "endpoint %d: " + "payload type %d (expected %d)\n", + last_endpoint, + conn->end.codec.payload_type, t->ptype); + + if (t->ptype != PTYPE_IGNORE) + OSMO_ASSERT(conn->end.codec.payload_type == + t->ptype); + + /* Reset them again for next test */ + conn->end.codec.payload_type = PTYPE_NONE; + } + } + + talloc_free(cfg); +} + +static void test_retransmission(void) +{ + struct mgcp_config *cfg; + int i; + + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + for (i = 0; i < ARRAY_SIZE(retransmit); i++) { + const struct mgcp_test *t = &retransmit[i]; + struct msgb *inp; + struct msgb *msg; + + printf("Testing %s\n", t->name); + + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + + msgb_free(inp); + if (strcmp((char *)msg->data, t->exp_resp) != 0) { + printf("%s failed '%s'\n", t->name, (char *)msg->data); + OSMO_ASSERT(false); + } + msgb_free(msg); + + /* Retransmit... */ + printf("Re-transmitting %s\n", t->name); + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (strcmp((char *)msg->data, t->exp_resp) != 0) { + printf("%s failed '%s'\n", t->name, (char *)msg->data); + OSMO_ASSERT(false); + } + msgb_free(msg); + } + + talloc_free(cfg); +} + +static int rqnt_cb(struct mgcp_endpoint *endp, char _tone) +{ + ptrdiff_t tone = _tone; + endp->cfg->data = (void *)tone; + return 0; +} + +static void test_rqnt_cb(void) +{ + struct mgcp_config *cfg; + struct msgb *inp, *msg; + + cfg = mgcp_config_alloc(); + cfg->rqnt_cb = rqnt_cb; + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + inp = create_msg(CRCX); + msgb_free(mgcp_handle_message(cfg, inp)); + msgb_free(inp); + + /* send the RQNT and check for the CB */ + inp = create_msg(RQNT); + msg = mgcp_handle_message(cfg, inp); + if (strncmp((const char *)msg->l2h, "200", 3) != 0) { + printf("FAILED: message is not 200. '%s'\n", msg->l2h); + abort(); + } + + if (cfg->data != (void *)'9') { + printf("FAILED: callback not called: %p\n", cfg->data); + abort(); + } + + msgb_free(msg); + msgb_free(inp); + + inp = create_msg(DLCX); + msgb_free(mgcp_handle_message(cfg, inp)); + msgb_free(inp); + talloc_free(cfg); +} + +struct pl_test { + int cycles; + uint16_t base_seq; + uint16_t max_seq; + uint32_t packets; + + uint32_t expected; + int loss; +}; + +static const struct pl_test pl_test_dat[] = { + /* basic.. just one package */ + {.cycles = 0,.base_seq = 0,.max_seq = 0,.packets = 1,.expected = + 1,.loss = 0}, + /* some packages and a bit of loss */ + {.cycles = 0,.base_seq = 0,.max_seq = 100,.packets = 100,.expected = + 101,.loss = 1}, + /* wrap around */ + {.cycles = 1 << 16,.base_seq = 0xffff,.max_seq = 2,.packets = + 4,.expected = 4,.loss = 0}, + /* min loss */ + {.cycles = 0,.base_seq = 0,.max_seq = 0,.packets = UINT_MAX,.expected = + 1,.loss = INT_MIN}, + /* max loss, with wrap around on expected max */ + {.cycles = INT_MAX,.base_seq = 0,.max_seq = UINT16_MAX,.packets = + 0,.expected = ((uint32_t) (INT_MAX) + UINT16_MAX + 1),.loss = INT_MAX}, +}; + +static void test_packet_loss_calc(void) +{ + int i; + printf("Testing packet loss calculation.\n"); + + for (i = 0; i < ARRAY_SIZE(pl_test_dat); ++i) { + uint32_t expected; + int loss; + struct mgcp_rtp_state state; + struct mgcp_rtp_end rtp; + memset(&state, 0, sizeof(state)); + memset(&rtp, 0, sizeof(rtp)); + + state.stats_initialized = 1; + state.stats_base_seq = pl_test_dat[i].base_seq; + state.stats_max_seq = pl_test_dat[i].max_seq; + state.stats_cycles = pl_test_dat[i].cycles; + + rtp.packets_rx = pl_test_dat[i].packets; + calc_loss(&state, &rtp, &expected, &loss); + + if (loss != pl_test_dat[i].loss + || expected != pl_test_dat[i].expected) { + printf + ("FAIL: Wrong exp/loss at idx(%d) Loss(%d vs. %d) Exp(%u vs. %u)\n", + i, loss, pl_test_dat[i].loss, expected, + pl_test_dat[i].expected); + } + } +} + +int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, + uint32_t *pr, uint32_t *_or, int *loss, + uint32_t *jitter) +{ + char *line, *save; + int rc; + + /* initialize with bad values */ + *ps = *os = *pr = *_or = *jitter = UINT_MAX; + *loss = INT_MAX; + + line = strtok_r((char *)msg->l2h, "\r\n", &save); + if (!line) + return -1; + + /* this can only parse the message that is created above... */ + for_each_non_empty_line(line, save) { + switch (line[0]) { + case 'P': + rc = sscanf(line, + "P: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", + ps, os, pr, _or, loss, jitter); + return rc == 6 ? 0 : -1; + } + } + + return -1; +} + +static void test_mgcp_stats(void) +{ + printf("Testing stat parsing\n"); + + uint32_t bps, bos, pr, _or, jitter; + struct msgb *msg; + int loss; + int rc; + + msg = create_msg(DLCX_RET); + rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter); + printf("Parsing result: %d\n", rc); + if (bps != 0 || bos != 0 || pr != 0 || _or != 0 || loss != 0 + || jitter != 0) + printf("FAIL: Parsing failed1.\n"); + msgb_free(msg); + + msg = + create_msg + ("250 7 OK\r\nP: PS=10, OS=20, PR=30, OR=40, PL=-3, JI=40\r\n"); + rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter); + printf("Parsing result: %d\n", rc); + if (bps != 10 || bos != 20 || pr != 30 || _or != 40 || loss != -3 + || jitter != 40) + printf("FAIL: Parsing failed2.\n"); + msgb_free(msg); +} + +struct rtp_packet_info { + float txtime; + int len; + char *data; +}; + +struct rtp_packet_info test_rtp_packets1[] = { + /* RTP: SeqNo=0, TS=0 */ + {0.000000, 20, "\x80\x62\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 20, "\x80\x62\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=2, TS=320 */ + {0.040000, 20, "\x80\x62\x00\x02\x00\x00\x01\x40\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Repeat RTP timestamp: */ + /* RTP: SeqNo=3, TS=320 */ + {0.060000, 20, "\x80\x62\x00\x03\x00\x00\x01\x40\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=4, TS=480 */ + {0.080000, 20, "\x80\x62\x00\x04\x00\x00\x01\xE0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=5, TS=640 */ + {0.100000, 20, "\x80\x62\x00\x05\x00\x00\x02\x80\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Double skip RTP timestamp (delta = 2*160): */ + /* RTP: SeqNo=6, TS=960 */ + {0.120000, 20, "\x80\x62\x00\x06\x00\x00\x03\xC0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=7, TS=1120 */ + {0.140000, 20, "\x80\x62\x00\x07\x00\x00\x04\x60\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=8, TS=1280 */ + {0.160000, 20, "\x80\x62\x00\x08\x00\x00\x05\x00\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Non 20ms RTP timestamp (delta = 120): */ + /* RTP: SeqNo=9, TS=1400 */ + {0.180000, 20, "\x80\x62\x00\x09\x00\x00\x05\x78\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=10, TS=1560 */ + {0.200000, 20, "\x80\x62\x00\x0A\x00\x00\x06\x18\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=11, TS=1720 */ + {0.220000, 20, "\x80\x62\x00\x0B\x00\x00\x06\xB8\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* SSRC changed to 0x10203040, RTP timestamp jump */ + /* RTP: SeqNo=12, TS=34688 */ + {0.240000, 20, "\x80\x62\x00\x0C\x00\x00\x87\x80\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=13, TS=34848 */ + {0.260000, 20, "\x80\x62\x00\x0D\x00\x00\x88\x20\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=14, TS=35008 */ + {0.280000, 20, "\x80\x62\x00\x0E\x00\x00\x88\xC0\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Non 20ms RTP timestamp (delta = 120): */ + /* RTP: SeqNo=15, TS=35128 */ + {0.300000, 20, "\x80\x62\x00\x0F\x00\x00\x89\x38\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=16, TS=35288 */ + {0.320000, 20, "\x80\x62\x00\x10\x00\x00\x89\xD8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=17, TS=35448 */ + {0.340000, 20, "\x80\x62\x00\x11\x00\x00\x8A\x78\x10\x20\x30\x40" + "\x01\x23\x45\x67\x8A\xAB\xCD\xEF"}, + /* SeqNo increment by 2, RTP timestamp delta = 320: */ + /* RTP: SeqNo=19, TS=35768 */ + {0.360000, 20, "\x80\x62\x00\x13\x00\x00\x8B\xB8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=20, TS=35928 */ + {0.380000, 20, "\x80\x62\x00\x14\x00\x00\x8C\x58\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=21, TS=36088 */ + {0.380000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Repeat last packet */ + /* RTP: SeqNo=21, TS=36088 */ + {0.400000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=22, TS=36248 */ + {0.420000, 20, "\x80\x62\x00\x16\x00\x00\x8D\x98\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=23, TS=36408 */ + {0.440000, 20, "\x80\x62\x00\x17\x00\x00\x8E\x38\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Don't increment SeqNo but increment timestamp by 160 */ + /* RTP: SeqNo=23, TS=36568 */ + {0.460000, 20, "\x80\x62\x00\x17\x00\x00\x8E\xD8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=24, TS=36728 */ + {0.480000, 20, "\x80\x62\x00\x18\x00\x00\x8F\x78\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=25, TS=36888 */ + {0.500000, 20, "\x80\x62\x00\x19\x00\x00\x90\x18\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* SSRC changed to 0x50607080, RTP timestamp jump, Delay of 1.5s, + * SeqNo jump */ + /* RTP: SeqNo=1000, TS=160000 */ + {2.000000, 20, "\x80\x62\x03\xE8\x00\x02\x71\x00\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1001, TS=160160 */ + {2.020000, 20, "\x80\x62\x03\xE9\x00\x02\x71\xA0\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1002, TS=160320 */ + {2.040000, 20, "\x80\x62\x03\xEA\x00\x02\x72\x40\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, +}; + +void mgcp_patch_and_count(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, char *data, int len); + +static void test_packet_error_detection(int patch_ssrc, int patch_ts) +{ + int i; + + struct mgcp_trunk_config trunk; + struct mgcp_endpoint endp; + struct mgcp_rtp_state state; + struct mgcp_rtp_end *rtp; + struct sockaddr_in addr = { 0 }; + char buffer[4096]; + uint32_t last_ssrc = 0; + uint32_t last_timestamp = 0; + uint32_t last_seqno = 0; + int last_in_ts_err_cnt = 0; + int last_out_ts_err_cnt = 0; + struct mgcp_conn_rtp *conn = NULL; + + printf("Testing packet error detection%s%s.\n", + patch_ssrc ? ", patch SSRC" : "", + patch_ts ? ", patch timestamps" : ""); + + memset(&trunk, 0, sizeof(trunk)); + memset(&endp, 0, sizeof(endp)); + memset(&state, 0, sizeof(state)); + + endp.type = &ep_typeset.rtp; + + trunk.number_endpoints = 1; + trunk.endpoints = &endp; + trunk.force_constant_ssrc = patch_ssrc; + trunk.force_aligned_timing = patch_ts; + + endp.tcfg = &trunk; + + INIT_LLIST_HEAD(&endp.conns); + mgcp_conn_alloc(NULL, &endp, 4711, MGCP_CONN_TYPE_RTP, + "test-connection"); + conn = mgcp_conn_get_rtp(&endp, 4711); + OSMO_ASSERT(conn); + + rtp = &conn->end; + + rtp->codec.payload_type = 98; + + for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) { + struct rtp_packet_info *info = test_rtp_packets1 + i; + + force_monotonic_time_us = round(1000000.0 * info->txtime); + + OSMO_ASSERT(info->len <= sizeof(buffer)); + OSMO_ASSERT(info->len >= 0); + memmove(buffer, info->data, info->len); + mgcp_rtp_end_config(&endp, 1, rtp); + + mgcp_patch_and_count(&endp, &state, rtp, &addr, + buffer, info->len); + + if (state.out_stream.ssrc != last_ssrc) { + printf("Output SSRC changed to %08x\n", + state.out_stream.ssrc); + last_ssrc = state.out_stream.ssrc; + } + + printf("In TS: %d, dTS: %d, Seq: %d\n", + state.in_stream.last_timestamp, + state.in_stream.last_tsdelta, state.in_stream.last_seq); + + printf("Out TS change: %d, dTS: %d, Seq change: %d, " + "TS Err change: in %+d, out %+d\n", + state.out_stream.last_timestamp - last_timestamp, + state.out_stream.last_tsdelta, + state.out_stream.last_seq - last_seqno, + state.in_stream.err_ts_counter - last_in_ts_err_cnt, + state.out_stream.err_ts_counter - last_out_ts_err_cnt); + + printf("Stats: Jitter = %u, Transit = %d\n", + calc_jitter(&state), state.stats_transit); + + last_in_ts_err_cnt = state.in_stream.err_ts_counter; + last_out_ts_err_cnt = state.out_stream.err_ts_counter; + last_timestamp = state.out_stream.last_timestamp; + last_seqno = state.out_stream.last_seq; + } + + force_monotonic_time_us = -1; +} + +static void test_multilple_codec(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + struct msgb *inp, *resp; + struct in_addr addr; + struct mgcp_conn_rtp *conn = NULL; + + printf("Testing multiple payload types\n"); + + cfg = mgcp_config_alloc(); + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + cfg->policy_cb = mgcp_test_policy_cb; + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* Allocate endpoint 1@mgw with two codecs */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_1); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 1); + endp = &cfg->trunk.endpoints[last_endpoint]; + conn = mgcp_conn_get_rtp(endp, 4711); + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->end.codec.payload_type == 18); + OSMO_ASSERT(conn->end.alt_codec.payload_type == 97); + + /* Allocate 2@mgw with three codecs, last one ignored */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_2); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 2); + endp = &cfg->trunk.endpoints[last_endpoint]; + conn = mgcp_conn_get_rtp(endp, 90210); + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->end.codec.payload_type == 18); + OSMO_ASSERT(conn->end.alt_codec.payload_type == 97); + + /* Allocate 3@mgw with no codecs, check for PT == -1 */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_3); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 3); + endp = &cfg->trunk.endpoints[last_endpoint]; + conn = mgcp_conn_get_rtp(endp, 815); + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->end.codec.payload_type == -1); + OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + + /* Allocate 4@mgw with a single codec */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_4); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 4); + endp = &cfg->trunk.endpoints[last_endpoint]; + conn = mgcp_conn_get_rtp(endp, 32168); + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->end.codec.payload_type == 18); + OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + + /* Allocate 5@mgw at select GSM.. */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_GSM_EXACT); + talloc_free(cfg->trunk.audio_name); + cfg->trunk.audio_name = "GSM/8000"; + cfg->trunk.no_audio_transcoding = 1; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + conn = mgcp_conn_get_rtp(endp, 3); + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->end.codec.payload_type == 3); + OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + + inp = create_msg(MDCX_NAT_DUMMY); + last_endpoint = -1; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + conn = mgcp_conn_get_rtp(endp, 3); + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->end.codec.payload_type == 3); + OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + OSMO_ASSERT(conn->end.rtp_port == htons(16434)); + memset(&addr, 0, sizeof(addr)); + inet_aton("8.8.8.8", &addr); + OSMO_ASSERT(conn->end.addr.s_addr == addr.s_addr); + + /* Check what happens without that flag */ + + /* Free the previous endpoint and the data and + * check if the connection really vanished... */ + mgcp_release_endp(endp); + talloc_free(endp->last_response); + talloc_free(endp->last_trans); + endp->last_response = endp->last_trans = NULL; + conn = mgcp_conn_get_rtp(endp, 3); + OSMO_ASSERT(!conn); + + last_endpoint = -1; + inp = create_msg(CRCX_MULT_GSM_EXACT); + cfg->trunk.no_audio_transcoding = 0; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + conn = mgcp_conn_get_rtp(endp, 3); + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->end.codec.payload_type == 255); + OSMO_ASSERT(conn->end.alt_codec.payload_type == 0); + + talloc_free(cfg); +} + +static void test_no_cycle(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + struct mgcp_conn_rtp *conn = NULL; + + printf("Testing no sequence flow on initial packet\n"); + + cfg = mgcp_config_alloc(); + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + endp = &cfg->trunk.endpoints[1]; + + mgcp_conn_alloc(NULL, endp, 4711, MGCP_CONN_TYPE_RTP, + "test-connection"); + conn = mgcp_conn_get_rtp(endp, 4711); + OSMO_ASSERT(conn); + + OSMO_ASSERT(conn->state.stats_initialized == 0); + + mgcp_rtp_annex_count(endp, &conn->state, 0, 0, 2342); + OSMO_ASSERT(conn->state.stats_initialized == 1); + OSMO_ASSERT(conn->state.stats_cycles == 0); + OSMO_ASSERT(conn->state.stats_max_seq == 0); + + mgcp_rtp_annex_count(endp, &conn->state, 1, 0, 2342); + OSMO_ASSERT(conn->state.stats_initialized == 1); + OSMO_ASSERT(conn->state.stats_cycles == 0); + OSMO_ASSERT(conn->state.stats_max_seq == 1); + + /* now jump.. */ + mgcp_rtp_annex_count(endp, &conn->state, UINT16_MAX, 0, 2342); + OSMO_ASSERT(conn->state.stats_initialized == 1); + OSMO_ASSERT(conn->state.stats_cycles == 0); + OSMO_ASSERT(conn->state.stats_max_seq == UINT16_MAX); + + /* and wrap */ + mgcp_rtp_annex_count(endp, &conn->state, 0, 0, 2342); + OSMO_ASSERT(conn->state.stats_initialized == 1); + OSMO_ASSERT(conn->state.stats_cycles == UINT16_MAX + 1); + OSMO_ASSERT(conn->state.stats_max_seq == 0); + + talloc_free(cfg); +} + +static void test_no_name(void) +{ + struct mgcp_config *cfg; + struct msgb *inp, *msg; + + printf("Testing no rtpmap name\n"); + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + cfg->trunk.audio_send_name = 0; + mgcp_endpoints_allocate(&cfg->trunk); + + cfg->policy_cb = mgcp_test_policy_cb; + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + inp = create_msg(CRCX); + msg = mgcp_handle_message(cfg, inp); + if (strcmp((char *)msg->data, CRCX_RET_NO_RTPMAP) != 0) { + printf("FAILED: there should not be a RTPMAP: %s\n", + (char *)msg->data); + OSMO_ASSERT(false); + } + msgb_free(inp); + msgb_free(msg); + + mgcp_release_endp(&cfg->trunk.endpoints[1]); + talloc_free(cfg); +} + +static void test_osmux_cid(void) +{ + int id, i; + + OSMO_ASSERT(osmux_used_cid() == 0); + id = osmux_get_cid(); + OSMO_ASSERT(id == 0); + OSMO_ASSERT(osmux_used_cid() == 1); + osmux_put_cid(id); + OSMO_ASSERT(osmux_used_cid() == 0); + + for (i = 0; i < 256; ++i) { + id = osmux_get_cid(); + OSMO_ASSERT(id == i); + OSMO_ASSERT(osmux_used_cid() == i + 1); + } + + id = osmux_get_cid(); + OSMO_ASSERT(id == -1); + + for (i = 0; i < 256; ++i) + osmux_put_cid(i); + OSMO_ASSERT(osmux_used_cid() == 0); +} + +static const struct log_info_cat log_categories[] = { +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + msgb_talloc_ctx_init(NULL, 0); + osmo_init_logging(&log_info); + + test_strline(); + test_values(); + test_messages(); + test_retransmission(); + test_packet_loss_calc(); + test_rqnt_cb(); + test_mgcp_stats(); + test_packet_error_detection(1, 0); + test_packet_error_detection(0, 0); + test_packet_error_detection(0, 1); + test_packet_error_detection(1, 1); + test_multilple_codec(); + test_no_cycle(); + test_no_name(); + test_osmux_cid(); + + printf("Done\n"); + return EXIT_SUCCESS; +} diff --git a/tests/mgcp/mgcp_test.ok b/tests/mgcp/mgcp_test.ok new file mode 100644 index 0000000..7376930 --- /dev/null +++ b/tests/mgcp/mgcp_test.ok @@ -0,0 +1,471 @@ +line: 'one CR' +line: 'two CR' +line: '' +line: 'one CRLF' +line: 'two CRLF' +line: '' +line: 'one LF' +line: 'two LF' +line: '' +line: 'mixed (4 lines)' +line: '' +line: '' +line: '' +Testing AUEP1 +Testing AUEP2 +Testing MDCX1 +Testing MDCX2 +Testing CRCX +Dummy packets: 2 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing MDCX3 +Dummy packets: 2 +Detected packet duration: 40 +Requested packetization period not set +Connection mode: 1: RECV +Testing MDCX4 +Dummy packets: 2 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 3: SEND RECV +Testing MDCX4_PT1 +Dummy packets: 2 +Detected packet duration: 40 +Requested packetetization period: 20-40 +Connection mode: 3: SEND RECV +Testing MDCX4_PT2 +Dummy packets: 2 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 3: SEND RECV +Testing MDCX4_PT3 +Dummy packets: 2 +Detected packet duration: 40 +Requested packetization period not set +Connection mode: 3: SEND RECV +Testing MDCX4_SO +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 2: SEND +Testing MDCX4_RO +Dummy packets: 2 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing DLCX +Testing CRCX_ZYN +Dummy packets: 2 +Detected packet duration: 20 +Requested packetization period not set +Connection mode: 1: RECV +Testing EMPTY +Testing SHORT1 +Testing SHORT2 +Testing SHORT3 +Testing SHORT4 +Testing RQNT1 +Testing RQNT2 +Testing DLCX +Testing CRCX +Dummy packets: 2 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing MDCX3 +Dummy packets: 2 +Detected packet duration: 40 +Requested packetization period not set +Connection mode: 1: RECV +Testing DLCX +Testing CRCX +Re-transmitting CRCX +Testing RQNT1 +Re-transmitting RQNT1 +Testing RQNT2 +Re-transmitting RQNT2 +Testing MDCX3 +Re-transmitting MDCX3 +Testing DLCX +Re-transmitting DLCX +Testing packet loss calculation. +Testing stat parsing +Parsing result: 0 +Parsing result: 0 +Testing packet error detection, patch SSRC. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +Output SSRC changed to 10203040 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +Output SSRC changed to 50607080 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection, patch timestamps. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +Output SSRC changed to 10203040 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +Output SSRC changed to 50607080 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection, patch SSRC, patch timestamps. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing multiple payload types +Testing no sequence flow on initial packet +Testing no rtpmap name +Done diff --git a/tests/mgcp_client/Makefile.am b/tests/mgcp_client/Makefile.am new file mode 100644 index 0000000..5cd0d8a --- /dev/null +++ b/tests/mgcp_client/Makefile.am @@ -0,0 +1,39 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_srcdir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + mgcp_client_test.ok \ + mgcp_client_test.err \ + $(NULL) + +noinst_PROGRAMS = \ + mgcp_client_test \ + $(NULL) + +mgcp_client_test_SOURCES = \ + mgcp_client_test.c \ + $(NULL) + +mgcp_client_test_LDADD = \ + $(top_builddir)/src/libosmo-mgcp-client/libosmo-mgcp-client.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBRARY_DL) \ + $(LIBOSMONETIF_LIBS) \ + $(NULL) diff --git a/tests/mgcp_client/mgcp_client_test.c b/tests/mgcp_client/mgcp_client_test.c new file mode 100644 index 0000000..513f345 --- /dev/null +++ b/tests/mgcp_client/mgcp_client_test.c @@ -0,0 +1,255 @@ +/* + * (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/application.h> +#include <osmocom/mgcp_client/mgcp_client.h> +#include <osmocom/mgcp_client/mgcp_client_internal.h> + +void *ctx; + +#define buf_len 4096 + +#if 0 +static struct msgb *from_hex(const char *hex) +{ + struct msgb *msg = msgb_alloc(buf_len, "mgcpgw_test_from_hex"); + unsigned int l = osmo_hexparse(hex, msg->data, buf_len); + msg->l2h = msgb_put(msg, l); + return msg; +} + +static struct msgb *mgcp_from_str(const char *head, const char *params) +{ + struct msgb *msg = msgb_alloc(buf_len, "mgcp_from_str"); + unsigned int l; + char *data; + l = strlen(head); + msg->l2h = msgb_put(msg, l); + data = (char*)msgb_l2(msg); + strncpy(data, head, l); + + data = (char*)msgb_put(msg, 1); + *data = '\n'; + + l = strlen(params); + data = (char*)msgb_put(msg, l); + strncpy(data, params, l); + + return msg; +} +#endif + +static struct msgb *from_str(const char *str) +{ + struct msgb *msg = msgb_alloc(buf_len, "from_str"); + unsigned int l = strlen(str); + char *data; + msg->l2h = msgb_put(msg, l); + data = (char*)msgb_l2(msg); + strncpy(data, str, l); + return msg; +} + +static struct mgcp_client_conf conf; +struct mgcp_client *mgcp = NULL; + +static void reply_to(mgcp_trans_id_t trans_id, int code, const char *comment, + int conn_id, const char *params) +{ + static char compose[4096 - 128]; + int len; + + len = snprintf(compose, sizeof(compose), + "%d %u %s\r\nI: %d\n\n%s", + code, trans_id, comment, conn_id, params); + OSMO_ASSERT(len < sizeof(compose)); + OSMO_ASSERT(len > 0); + + printf("composed response:\n-----\n%s\n-----\n", + compose); + mgcp_client_rx(mgcp, from_str(compose)); +} + +void test_response_cb(struct mgcp_response *response, void *priv) +{ + OSMO_ASSERT(priv == mgcp); + mgcp_response_parse_params(response); + + printf("response cb received:\n" + " head.response_code = %d\n" + " head.trans_id = %u\n" + " head.comment = %s\n" + " audio_port = %u\n" + " audio_ip = %s\n", + response->head.response_code, + response->head.trans_id, + response->head.comment, + response->audio_port, + response->audio_ip + ); +} + +mgcp_trans_id_t dummy_mgcp_send(struct msgb *msg) +{ + mgcp_trans_id_t trans_id; + trans_id = msg->cb[MSGB_CB_MGCP_TRANS_ID]; + char *end; + + OSMO_ASSERT(mgcp_client_pending_add(mgcp, trans_id, test_response_cb, mgcp)); + + end = (char*)msgb_put(msg, 1); + *end = '\0'; + printf("composed:\n-----\n%s\n-----\n", + (char*)msgb_l2(msg)); + + talloc_free(msg); + return trans_id; +} + +void test_crcx(void) +{ + struct msgb *msg; + mgcp_trans_id_t trans_id; + + printf("\n===== %s =====\n", __func__); + + if (mgcp) + talloc_free(mgcp); + mgcp = mgcp_client_init(ctx, &conf); + + msg = mgcp_msg_crcx(mgcp, 23, 42, MGCP_CONN_LOOPBACK); + trans_id = dummy_mgcp_send(msg); + + reply_to(trans_id, 200, "OK", 1, + "v=0\r\n" + "o=- 1 23 IN IP4 10.9.1.120\r\n" + "s=-\r\n" + "c=IN IP4 10.9.1.120\r\n" + "t=0 0\r\n" + "m=audio 16002 RTP/AVP 98\r\n" + "a=rtpmap:98 AMR/8000\r\n" + "a=ptime:20\r\n"); +} + +void test_mgcp_msg(void) +{ + struct msgb *msg; + char audio_ip_overflow[5000]; + + /* A message struct prefilled with some arbitary values */ + struct mgcp_msg mgcp_msg = { + .audio_ip = "192.168.100.23", + .endpoint = "23@mgw", + .audio_port = 1234, + .call_id = 47, + .conn_id = 11, + .conn_mode = MGCP_CONN_RECV_SEND + }; + + if (mgcp) + talloc_free(mgcp); + mgcp = mgcp_client_init(ctx, &conf); + + printf("\n"); + + printf("Generated CRCX message:\n"); + mgcp_msg.verb = MGCP_VERB_CRCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE); + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + printf("%s\n", (char *)msg->data); + + printf("Generated MDCX message:\n"); + mgcp_msg.verb = MGCP_VERB_MDCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE | + MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT); + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + printf("%s\n", (char *)msg->data); + + printf("Generated DLCX message:\n"); + mgcp_msg.verb = MGCP_VERB_DLCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID); + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + printf("%s\n", (char *)msg->data); + + printf("Generated AUEP message:\n"); + mgcp_msg.verb = MGCP_VERB_AUEP; + mgcp_msg.presence = (MGCP_MSG_PRESENCE_ENDPOINT); + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + printf("%s\n", msg->data); + + printf("Generated RSIP message:\n"); + mgcp_msg.verb = MGCP_VERB_RSIP; + mgcp_msg.presence = (MGCP_MSG_PRESENCE_ENDPOINT); + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + printf("%s\n", (char *)msg->data); + + printf("Overfolow test:\n"); + mgcp_msg.verb = MGCP_VERB_MDCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE | + MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT); + memset(audio_ip_overflow, 'X', sizeof(audio_ip_overflow)); + audio_ip_overflow[sizeof(audio_ip_overflow) - 1] = '\0'; + mgcp_msg.audio_ip = audio_ip_overflow; + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + OSMO_ASSERT(msg == NULL); + + printf("\n"); + msgb_free(msg); +} + +static const struct log_info_cat log_categories[] = { +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + + +int main(int argc, char **argv) +{ + ctx = talloc_named_const(NULL, 1, "mgcp_client_test"); + msgb_talloc_ctx_init(ctx, 0); + osmo_init_logging(&log_info); + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_timestamp(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + + mgcp_client_conf_init(&conf); + + test_crcx(); + test_mgcp_msg(); + + printf("Done\n"); + fprintf(stderr, "Done\n"); + return EXIT_SUCCESS; +} diff --git a/tests/mgcp_client/mgcp_client_test.err b/tests/mgcp_client/mgcp_client_test.err new file mode 100644 index 0000000..24151ee --- /dev/null +++ b/tests/mgcp_client/mgcp_client_test.err @@ -0,0 +1,2 @@ +DLMGCP message buffer to small, can not generate MGCP message +Done diff --git a/tests/mgcp_client/mgcp_client_test.ok b/tests/mgcp_client/mgcp_client_test.ok new file mode 100644 index 0000000..d4efee4 --- /dev/null +++ b/tests/mgcp_client/mgcp_client_test.ok @@ -0,0 +1,62 @@ + +===== test_crcx ===== +composed: +----- +CRCX 1 17@mgw MGCP 1.0
+C: 2a
+L: p:20, a:AMR, nt:IN
+M: loopback
+ +----- +composed response: +----- +200 1 OK
+I: 1 + +v=0
+o=- 1 23 IN IP4 10.9.1.120
+s=-
+c=IN IP4 10.9.1.120
+t=0 0
+m=audio 16002 RTP/AVP 98
+a=rtpmap:98 AMR/8000
+a=ptime:20
+ +----- +response cb received: + head.response_code = 200 + head.trans_id = 1 + head.comment = OK + audio_port = 16002 + audio_ip = 10.9.1.120 + +Generated CRCX message: +CRCX 1 23@mgw MGCP 1.0
+C: 2f
+I: 11
+L: p:20, a:AMR, nt:IN
+M: sendrecv
+ +Generated MDCX message: +MDCX 2 23@mgw MGCP 1.0
+C: 2f
+I: 11
+M: sendrecv
+
+c=IN IP4 192.168.100.23
+m=audio 1234 RTP/AVP 255
+ +Generated DLCX message: +DLCX 3 23@mgw MGCP 1.0
+C: 2f
+I: 11
+ +Generated AUEP message: +AUEP 4 23@mgw MGCP 1.0
+ +Generated RSIP message: +RSIP 5 23@mgw MGCP 1.0
+ +Overfolow test: + +Done diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..03ec4b9 --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,28 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +AT_SETUP([legacy_mgcp]) +AT_KEYWORDS([legacy_mgcp]) +cat $abs_srcdir/legacy_mgcp/mgcp_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/legacy_mgcp/mgcp_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([legacy_mgcp-trans]) +AT_KEYWORDS([legacy_mgcp-trans]) +AT_CHECK([test "$enable_mgcp_transcoding_test" == yes || exit 77]) +cat $abs_srcdir/legacy_mgcp/mgcp_transcoding_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/legacy_mgcp/mgcp_transcoding_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([mgcp_client]) +AT_KEYWORDS([mgcp_client]) +cat $abs_srcdir/mgcp_client/mgcp_client_test.ok > expout +cat $abs_srcdir/mgcp_client/mgcp_client_test.err > experr +AT_CHECK([$abs_top_builddir/tests/mgcp_client/mgcp_client_test], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([mgcp]) +AT_KEYWORDS([mgcp]) +cat $abs_srcdir/mgcp/mgcp_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/mgcp/mgcp_test], [], [expout], [ignore]) +AT_CLEANUP diff --git a/tests/vty_test_runner.py b/tests/vty_test_runner.py new file mode 100644 index 0000000..05bc3bf --- /dev/null +++ b/tests/vty_test_runner.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python + +# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com> +# (C) 2013 by Holger Hans Peter Freyther +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os, sys +import time +import unittest +import socket +import subprocess + +import osmopy.obscvty as obscvty +import osmopy.osmoutil as osmoutil + +# add $top_srcdir/contrib to find ipa.py +sys.path.append(os.path.join(sys.path[0], '..', 'contrib')) + +from ipa import IPA + +# to be able to find $top_srcdir/doc/... +confpath = os.path.join(sys.path[0], '..') + +class TestVTYBase(unittest.TestCase): + + def checkForEndAndExit(self): + res = self.vty.command("list") + #print ('looking for "exit"\n') + self.assert_(res.find(' exit\r') > 0) + #print 'found "exit"\nlooking for "end"\n' + self.assert_(res.find(' end\r') > 0) + #print 'found "end"\n' + + def vty_command(self): + raise Exception("Needs to be implemented by a subclass") + + def vty_app(self): + raise Exception("Needs to be implemented by a subclass") + + def setUp(self): + osmo_vty_cmd = self.vty_command()[:] + config_index = osmo_vty_cmd.index('-c') + if config_index: + cfi = config_index + 1 + osmo_vty_cmd[cfi] = os.path.join(confpath, osmo_vty_cmd[cfi]) + + try: + self.proc = osmoutil.popen_devnull(osmo_vty_cmd) + except OSError: + print >> sys.stderr, "Current directory: %s" % os.getcwd() + print >> sys.stderr, "Consider setting -b" + + appstring = self.vty_app()[2] + appport = self.vty_app()[0] + self.vty = obscvty.VTYInteract(appstring, "127.0.0.1", appport) + + def tearDown(self): + if self.vty: + self.vty._close_socket() + self.vty = None + osmoutil.end_proc(self.proc) + +class TestVTYMGCP(TestVTYBase): + def vty_command(self): + return ["./src/osmo-bsc_mgcp/osmo-bsc_mgcp", "-c", + "doc/examples/osmo-bsc_mgcp/mgcp.cfg"] + + def vty_app(self): + return (4243, "./src/osmo-bsc_mgcp/osmo-bsc_mgcp", "OpenBSC MGCP", "mgcp") + + def testForcePtime(self): + self.vty.enable() + res = self.vty.command("show running-config") + self.assert_(res.find(' rtp force-ptime 20\r') > 0) + self.assertEquals(res.find(' no rtp force-ptime\r'), -1) + + self.vty.command("configure terminal") + self.vty.command("mgcp") + self.vty.command("no rtp force-ptime") + res = self.vty.command("show running-config") + self.assertEquals(res.find(' rtp force-ptime 20\r'), -1) + self.assertEquals(res.find(' no rtp force-ptime\r'), -1) + + def testOmitAudio(self): + self.vty.enable() + res = self.vty.command("show running-config") + self.assert_(res.find(' sdp audio-payload send-name\r') > 0) + self.assertEquals(res.find(' no sdp audio-payload send-name\r'), -1) + + self.vty.command("configure terminal") + self.vty.command("mgcp") + self.vty.command("no sdp audio-payload send-name") + res = self.vty.command("show running-config") + self.assertEquals(res.find(' rtp sdp audio-payload send-name\r'), -1) + self.assert_(res.find(' no sdp audio-payload send-name\r') > 0) + + # TODO: test it for the trunk! + + def testBindAddr(self): + self.vty.enable() + + self.vty.command("configure terminal") + self.vty.command("mgcp") + + # enable.. disable bts-bind-ip + self.vty.command("rtp bts-bind-ip 254.253.252.250") + res = self.vty.command("show running-config") + self.assert_(res.find('rtp bts-bind-ip 254.253.252.250') > 0) + self.vty.command("no rtp bts-bind-ip") + res = self.vty.command("show running-config") + self.assertEquals(res.find(' rtp bts-bind-ip'), -1) + + # enable.. disable net-bind-ip + self.vty.command("rtp net-bind-ip 254.253.252.250") + res = self.vty.command("show running-config") + self.assert_(res.find('rtp net-bind-ip 254.253.252.250') > 0) + self.vty.command("no rtp net-bind-ip") + res = self.vty.command("show running-config") + self.assertEquals(res.find(' rtp net-bind-ip'), -1) + + +if __name__ == '__main__': + import argparse + import sys + + workdir = '.' + + parser = argparse.ArgumentParser() + parser.add_argument("-v", "--verbose", dest="verbose", + action="store_true", help="verbose mode") + parser.add_argument("-p", "--pythonconfpath", dest="p", + help="searchpath for config") + parser.add_argument("-w", "--workdir", dest="w", + help="Working directory") + parser.add_argument("test_name", nargs="*", help="(parts of) test names to run, case-insensitive") + args = parser.parse_args() + + verbose_level = 1 + if args.verbose: + verbose_level = 2 + + if args.w: + workdir = args.w + + if args.p: + confpath = args.p + + print "confpath %s, workdir %s" % (confpath, workdir) + os.chdir(workdir) + print "Running tests for specific VTY commands" + suite = unittest.TestSuite() + suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestVTYMGCP)) + + if args.test_name: + osmoutil.pick_tests(suite, *args.test_name) + + res = unittest.TextTestRunner(verbosity=verbose_level, stream=sys.stdout).run(suite) + sys.exit(len(res.errors) + len(res.failures)) + +# vim: shiftwidth=4 expandtab nocin ai |