From 8e11aec9a72dca1837401be67747a6d62a47a116 Mon Sep 17 00:00:00 2001 From: Kira Obrezkova Date: Mon, 22 Jan 2018 11:52:29 +0100 Subject: Import libosmo-sccp_0.8.1.orig.tar.gz [dgit import orig libosmo-sccp_0.8.1.orig.tar.gz] --- .gitignore | 71 ++ .gitreview | 3 + COPYING | 339 ++++++ Doxyfile.in | 1716 ++++++++++++++++++++++++++++ Makefile.am | 44 + TODO-RELEASE | 9 + configure.ac | 92 ++ contrib/jenkins.sh | 41 + contrib/systemd/osmo-stp.service | 11 + contrib/test/osmo-stp.cfg | 48 + contrib/test/run-in-ns.sh | 13 + contrib/test/test-m3ua.sh | 31 + contrib/test/test-sua.sh | 31 + doc/examples/osmo-stp.cfg | 20 + examples/Makefile.am | 11 + examples/internal.h | 12 + examples/m3ua_example.c | 121 ++ examples/sccp_test_server.c | 118 ++ examples/sccp_test_vty.c | 153 +++ git-version-gen | 151 +++ include/Makefile.am | 1 + include/mtp/Makefile.am | 2 + include/mtp/mtp_level3.h | 182 +++ include/mtp/mtp_pcap.h | 29 + include/osmocom/Makefile.am | 1 + include/osmocom/sigtran/Makefile.am | 7 + include/osmocom/sigtran/m2ua_types.h | 255 +++++ include/osmocom/sigtran/mtp_sap.h | 68 ++ include/osmocom/sigtran/osmo_ss7.h | 488 ++++++++ include/osmocom/sigtran/protocol/m3ua.h | 179 +++ include/osmocom/sigtran/protocol/mtp.h | 24 + include/osmocom/sigtran/protocol/sua.h | 160 +++ include/osmocom/sigtran/sccp_helpers.h | 57 + include/osmocom/sigtran/sccp_sap.h | 277 +++++ include/osmocom/sigtran/sigtran_sap.h | 81 ++ include/osmocom/sigtran/xua_msg.h | 103 ++ include/osmocom/sigtran/xua_types.h | 45 + include/sccp/Makefile.am | 2 + include/sccp/sccp.h | 204 ++++ include/sccp/sccp_types.h | 441 ++++++++ libosmo-mtp.pc.in | 10 + libosmo-sccp.pc.in | 10 + libosmo-sigtran.pc.in | 10 + libosmo-xua.pc.in | 10 + src/Makefile.am | 37 + src/ipa.c | 215 ++++ src/m3ua.c | 771 +++++++++++++ src/mtp_pcap.c | 86 ++ src/osmo_ss7.c | 1874 +++++++++++++++++++++++++++++++ src/osmo_ss7_hmrt.c | 229 ++++ src/osmo_ss7_vty.c | 1793 +++++++++++++++++++++++++++++ src/sccp.c | 1483 ++++++++++++++++++++++++ src/sccp2sua.c | 1421 +++++++++++++++++++++++ src/sccp_helpers.c | 326 ++++++ src/sccp_internal.h | 90 ++ src/sccp_sap.c | 135 +++ src/sccp_sclc.c | 350 ++++++ src/sccp_scoc.c | 1704 ++++++++++++++++++++++++++++ src/sccp_scrc.c | 487 ++++++++ src/sccp_user.c | 632 +++++++++++ src/sccp_vty.c | 149 +++ src/sua.c | 735 ++++++++++++ src/xua_as_fsm.c | 395 +++++++ src/xua_as_fsm.h | 15 + src/xua_asp_fsm.c | 1058 +++++++++++++++++ src/xua_asp_fsm.h | 49 + src/xua_default_lm_fsm.c | 387 +++++++ src/xua_internal.h | 80 ++ src/xua_msg.c | 506 +++++++++ src/xua_rkm.c | 564 ++++++++++ stp/Makefile.am | 9 + stp/stp_main.c | 212 ++++ tests/Makefile.am | 40 + tests/m2ua/Makefile.am | 8 + tests/m2ua/m2ua_test.c | 116 ++ tests/m2ua/m2ua_test.ok | 3 + tests/mtp/Makefile.am | 6 + tests/mtp/mtp_parse_test.c | 643 +++++++++++ tests/mtp/mtp_parse_test.ok | 2 + tests/sccp/Makefile.am | 10 + tests/sccp/sccp_test.c | 1027 +++++++++++++++++ tests/sccp/sccp_test.ok | 86 ++ tests/ss7/Makefile.am | 12 + tests/ss7/ss7_test.c | 322 ++++++ tests/ss7/ss7_test.err | 49 + tests/ss7/ss7_test.ok | 27 + tests/testsuite.at | 33 + tests/xua/Makefile.am | 13 + tests/xua/sccp_test_data.c | 102 ++ tests/xua/sccp_test_data.h | 14 + tests/xua/xua_test.c | 601 ++++++++++ tests/xua/xua_test.err | 2 + tests/xua/xua_test.ok | 208 ++++ 93 files changed, 24797 insertions(+) create mode 100644 .gitignore create mode 100644 .gitreview create mode 100644 COPYING create mode 100644 Doxyfile.in create mode 100644 Makefile.am create mode 100644 TODO-RELEASE create mode 100644 configure.ac create mode 100755 contrib/jenkins.sh create mode 100644 contrib/systemd/osmo-stp.service create mode 100644 contrib/test/osmo-stp.cfg create mode 100755 contrib/test/run-in-ns.sh create mode 100755 contrib/test/test-m3ua.sh create mode 100755 contrib/test/test-sua.sh create mode 100644 doc/examples/osmo-stp.cfg create mode 100644 examples/Makefile.am create mode 100644 examples/internal.h create mode 100644 examples/m3ua_example.c create mode 100644 examples/sccp_test_server.c create mode 100644 examples/sccp_test_vty.c create mode 100755 git-version-gen create mode 100644 include/Makefile.am create mode 100644 include/mtp/Makefile.am create mode 100644 include/mtp/mtp_level3.h create mode 100644 include/mtp/mtp_pcap.h create mode 100644 include/osmocom/Makefile.am create mode 100644 include/osmocom/sigtran/Makefile.am create mode 100644 include/osmocom/sigtran/m2ua_types.h create mode 100644 include/osmocom/sigtran/mtp_sap.h create mode 100644 include/osmocom/sigtran/osmo_ss7.h create mode 100644 include/osmocom/sigtran/protocol/m3ua.h create mode 100644 include/osmocom/sigtran/protocol/mtp.h create mode 100644 include/osmocom/sigtran/protocol/sua.h create mode 100644 include/osmocom/sigtran/sccp_helpers.h create mode 100644 include/osmocom/sigtran/sccp_sap.h create mode 100644 include/osmocom/sigtran/sigtran_sap.h create mode 100644 include/osmocom/sigtran/xua_msg.h create mode 100644 include/osmocom/sigtran/xua_types.h create mode 100644 include/sccp/Makefile.am create mode 100644 include/sccp/sccp.h create mode 100644 include/sccp/sccp_types.h create mode 100644 libosmo-mtp.pc.in create mode 100644 libosmo-sccp.pc.in create mode 100644 libosmo-sigtran.pc.in create mode 100644 libosmo-xua.pc.in create mode 100644 src/Makefile.am create mode 100644 src/ipa.c create mode 100644 src/m3ua.c create mode 100644 src/mtp_pcap.c create mode 100644 src/osmo_ss7.c create mode 100644 src/osmo_ss7_hmrt.c create mode 100644 src/osmo_ss7_vty.c create mode 100644 src/sccp.c create mode 100644 src/sccp2sua.c create mode 100644 src/sccp_helpers.c create mode 100644 src/sccp_internal.h create mode 100644 src/sccp_sap.c create mode 100644 src/sccp_sclc.c create mode 100644 src/sccp_scoc.c create mode 100644 src/sccp_scrc.c create mode 100644 src/sccp_user.c create mode 100644 src/sccp_vty.c create mode 100644 src/sua.c create mode 100644 src/xua_as_fsm.c create mode 100644 src/xua_as_fsm.h create mode 100644 src/xua_asp_fsm.c create mode 100644 src/xua_asp_fsm.h create mode 100644 src/xua_default_lm_fsm.c create mode 100644 src/xua_internal.h create mode 100644 src/xua_msg.c create mode 100644 src/xua_rkm.c create mode 100644 stp/Makefile.am create mode 100644 stp/stp_main.c create mode 100644 tests/Makefile.am create mode 100644 tests/m2ua/Makefile.am create mode 100644 tests/m2ua/m2ua_test.c create mode 100644 tests/m2ua/m2ua_test.ok create mode 100644 tests/mtp/Makefile.am create mode 100644 tests/mtp/mtp_parse_test.c create mode 100644 tests/mtp/mtp_parse_test.ok create mode 100644 tests/sccp/Makefile.am create mode 100644 tests/sccp/sccp_test.c create mode 100644 tests/sccp/sccp_test.ok create mode 100644 tests/ss7/Makefile.am create mode 100644 tests/ss7/ss7_test.c create mode 100644 tests/ss7/ss7_test.err create mode 100644 tests/ss7/ss7_test.ok create mode 100644 tests/testsuite.at create mode 100644 tests/xua/Makefile.am create mode 100644 tests/xua/sccp_test_data.c create mode 100644 tests/xua/sccp_test_data.h create mode 100644 tests/xua/xua_test.c create mode 100644 tests/xua/xua_test.err create mode 100644 tests/xua/xua_test.ok diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e63e4ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +*.o +*.a +*.lo +*.la +.deps +Makefile +Makefile.in +bscconfig.h +bscconfig.h.in +openbsc.pc +bsc_hack +bsc_msc_ip +bsc_mgcp +*.*~ +*.sw? + +#configure +aclocal.m4 +autom4te.cache/ +compile +config.log +config.status +configure +configure.lineno +depcomp +install-sh +missing +stamp-h1 + +# libtool +ltmain.sh +libtool +.libs + +# git-version-gen magic +.tarball-version +.version + + +# apps and app data +hlr.sqlite3 +bs11_config +ipaccess-config +ipaccess-find +ipaccess-firmware +ipaccess-proxy +isdnsync +bsc_nat +osmo-sgsn +osmo-gbproxy + +#tests +tests/channel/channel_test +tests/db/db_test +tests/debug/debug_test +tests/*/*_test + +tests/atconfig +tests/package.m4 +tests/testsuite +tests/testsuite.log + +examples/m3ua_example + +stp/osmo-stp + +*.pc +config.* + +tags +/Doxyfile diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..d9747f4 --- /dev/null +++ b/.gitreview @@ -0,0 +1,3 @@ +[gerrit] +host=gerrit.osmocom.org +project=libosmo-sccp diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + 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 +this service 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. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 0000000..2676a6f --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,1716 @@ +# Doxyfile 1.7.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = libosmo-sigtran + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Osmocom SIGTRAN library" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/sigtran + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @srcdir@/include/osmocom/sigtran @srcdir@/src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +# IMAGE_PATH = images/ + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is adviced to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = doc/libosmo-sigtran.tag + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called Helvetica to the output +# directory and reference it in all dot files that doxygen generates. +# When you want a differently looking font you can specify the font name +# using DOT_FONTNAME. You need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = /usr/bin/dot + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..d1c3c21 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,44 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +SUBDIRS = include src tests examples stp + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libosmo-sccp.pc libosmo-mtp.pc libosmo-sigtran.pc libosmo-xua.pc + +EXTRA_DIST = .version doc/examples/osmo-stp.cfg + +@RELMAKE@ + +BUILT_SOURCES = $(top_srcdir)/.version +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version + + +if HAVE_DOXYGEN + +html_DATA = $(top_builddir)/doc/html.tar + +doc: $(html_DATA) + +$(html_DATA): $(top_builddir)/doc/sigtran/html/index.html + cd $(top_builddir)/doc && tar cf html.tar */html + +$(top_builddir)/doc/sigtran/html/index.html: $(SOURCES) Doxyfile + @rm -rf doc/sigtran + mkdir -p doc/sigtran + $(DOXYGEN) Doxyfile + +install-data-hook: + cd $(DESTDIR)$(htmldir) && tar xf html.tar && rm -f html.tar + +uninstall-hook: + cd $(DESTDIR)$(htmldir) && rm -rf sigtran + +DX_CLEAN = doc/sigtran/html/search/* doc/sigtran/{html,latex}/* doc/html.tar doc/doxygen_sqlite3.db doc/sigtran/doxygen_sqlite3.db doc/*.tag + +endif + +MOSTLYCLEANFILES = $(DX_CLEAN) diff --git a/TODO-RELEASE b/TODO-RELEASE new file mode 100644 index 0000000..d0852fc --- /dev/null +++ b/TODO-RELEASE @@ -0,0 +1,9 @@ +# When cleaning up this file: bump API version in corresponding Makefile.am and rename corresponding debian/lib*.install +# according to https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info +# In short: +# LIBVERSION=c:r:a +# If the library source code has changed at all since the last update, then increment revision: c:r + 1:a. +# If any interfaces have been added, removed, or changed since the last update: c + 1:0:0. +# If any interfaces have been added since the last public release: c:r:a + 1. +# If any interfaces have been removed or changed since the last public release: c:r:0. +#library what description / commit summary line diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..5165cdb --- /dev/null +++ b/configure.ac @@ -0,0 +1,92 @@ +dnl Process this file with autoconf to produce a configure script +AC_INIT([libosmo-sccp], + 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 +AC_PROG_LIBTOOL + +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]) + +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) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.10.0) + +old_LIBS=$LIBS +AC_SEARCH_LIBS([sctp_send], [sctp], [ + AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support]) + AC_SUBST(HAVE_LIBSCTP, [1]) + if test -n "$ac_lib"; then + AC_SUBST(LIBSCTP_LIBS, [-l$ac_lib]) + fi + ], [ + AC_MSG_ERROR([sctp_send not found in searched libs])]) +LIBS=$old_LIBS + +# 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) + +CFLAGS="$CFLAGS -Wall" +CPPFLAGS="$CPPFLAGS -Wall" + +AC_ARG_ENABLE(doxygen, + [AS_HELP_STRING( + [--disable-doxygen], + [Disable generation of documentation using doxygen], + )], + [doxygen=$enableval], [doxygen="yes"]) +AC_PATH_PROG(DOXYGEN,doxygen,false) +AM_CONDITIONAL(HAVE_DOXYGEN, test $DOXYGEN != false && test "x$doxygen" = "xyes") + +AC_OUTPUT( + libosmo-sigtran.pc + libosmo-sccp.pc + libosmo-mtp.pc + libosmo-xua.pc + include/sccp/Makefile + include/mtp/Makefile + include/osmocom/Makefile + include/osmocom/sigtran/Makefile + include/Makefile + src/Makefile + tests/Makefile + tests/sccp/Makefile + tests/mtp/Makefile + tests/m2ua/Makefile + tests/xua/Makefile + tests/ss7/Makefile + examples/Makefile + stp/Makefile + Doxyfile + Makefile) diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh new file mode 100755 index 0000000..ae850ed --- /dev/null +++ b/contrib/jenkins.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# jenkins build helper script for libosmo-sccp. 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 + +mkdir "$deps" || true +rm -rf "$inst" + +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 libosmocore +osmo-build-dep.sh libosmo-abis +osmo-build-dep.sh libosmo-netif + +set +x +echo +echo +echo +echo " =============================== libosmo-sccp ===============================" +echo +set -x + +autoreconf --install --force +./configure CFLAGS="-Werror" CPPFLAGS="-Werror" +$MAKE $PARALLEL_MAKE +$MAKE distcheck \ + || cat-testlogs.sh diff --git a/contrib/systemd/osmo-stp.service b/contrib/systemd/osmo-stp.service new file mode 100644 index 0000000..5d06870 --- /dev/null +++ b/contrib/systemd/osmo-stp.service @@ -0,0 +1,11 @@ +[Unit] +Description=Osmocom STP (Signal Transfer Point) + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-stp -c /etc/osmocom/osmo-stp.cfg +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/contrib/test/osmo-stp.cfg b/contrib/test/osmo-stp.cfg new file mode 100644 index 0000000..bf059a9 --- /dev/null +++ b/contrib/test/osmo-stp.cfg @@ -0,0 +1,48 @@ +! +! osmo-stp (0.0.6.3.179-b248) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 0 + logging level all everything + logging level lglobal notice + logging level llapd notice + logging level linp info + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice + logging level lctrl notice + logging level lgtp notice + logging level lstats notice + logging level lgsup notice + logging level loap notice + logging level lss7 debug + logging level lsccp debug + logging level lsua debug + logging level lm3ua debug +! +line vty + no login +! +cs7 instance 0 + asp asp0 0 2905 m3ua + remote-ip 172.18.0.2 + asp asp-s-0 0 14001 sua + remote-ip 172.18.0.3 + as as0 m3ua + asp asp0 + routing-key 23 0.2.7 + as as-s-0 sua + asp asp-s-0 + routing-key 24 0.3.0 + route-table system + update route 0.2.7 0.2.7 linkset as0 + update route 0.3.0 0.3.0 linkset as-s-0 + listen m3ua 2905 + local-ip 172.18.0.200 + listen sua 14001 + local-ip 172.18.0.200 diff --git a/contrib/test/run-in-ns.sh b/contrib/test/run-in-ns.sh new file mode 100755 index 0000000..a961f78 --- /dev/null +++ b/contrib/test/run-in-ns.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# small helper script to run the specified command inside a network +# namespace while remapping the real PID to root inside the namespace, +# so the program inside the namespace can do things like setting +# interface addresses + +if [ $# -eq 0 ]; then + echo "You have to specify the command you want to execute in the new namespace" + exit 1 +fi + +unshare --map-root-user --user -i -m -p -f -u -U -n $1 diff --git a/contrib/test/test-m3ua.sh b/contrib/test/test-m3ua.sh new file mode 100755 index 0000000..0f81fe8 --- /dev/null +++ b/contrib/test/test-m3ua.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# this script executes m3ua-testtool against osmo-stp. It assumes that +# it is called from within libosmo-sccp/contrib/test and also assumes +# that adjacent to the libosmo-sccp, there's a check-out of +# git://git.osmocom.org/nplab/m3ua-testtool + +# the top of the libosmo-sccp git repository +TOPDIR=../../ + +# the directory in which we can find the osmo-stp binary +STP_DIR=$TOPDIR/stp + +# the directory in which we can find the m3ua-testtool.git +M3UA_DIR=$TOPDIR/../m3ua-testtool + +# osmo-stp config file, used from CWD +STP_CONFIG=./osmo-stp.cfg + +# we're pesudo-root but inherit the path from a non-root user +PATH=/sbin:/usr/sbin:$PATH + +# set up the ip addresses +ip link set lo up +ip addr add 172.18.0.2/32 dev lo +ip addr add 172.18.0.200/32 dev lo + +$STP_DIR/osmo-stp -c $STP_CONFIG & +STP_PID=$! +(cd $M3UA_DIR && ./run-all-sgp-tests) +kill $! diff --git a/contrib/test/test-sua.sh b/contrib/test/test-sua.sh new file mode 100755 index 0000000..0cb4e35 --- /dev/null +++ b/contrib/test/test-sua.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# this script executes m3ua-testtool against osmo-stp. It assumes that +# it is called from within libosmo-sccp/contrib/test and also assumes +# that adjacent to the libosmo-sccp, there's a check-out of +# git://git.osmocom.org/nplab/m3ua-testtool + +# the top of the libosmo-sccp git repository +TOPDIR=../../ + +# the directory in which we can find the osmo-stp binary +STP_DIR=$TOPDIR/stp + +# the directory in which we can find the sua-testtool.git +SUA_DIR=$TOPDIR/../sua-testtool + +# osmo-stp config file, used from CWD +STP_CONFIG=./osmo-stp.cfg + +# we're pesudo-root but inherit the path from a non-root user +PATH=/sbin:/usr/sbin:$PATH + +# set up the ip addresses +ip link set lo up +ip addr add 172.18.0.3/32 dev lo +ip addr add 172.18.0.200/32 dev lo + +$STP_DIR/osmo-stp -c $STP_CONFIG & +STP_PID=$! +(cd $SUA_DIR && ./run-some-sua-sgp-tests) +kill $! diff --git a/doc/examples/osmo-stp.cfg b/doc/examples/osmo-stp.cfg new file mode 100644 index 0000000..960bf33 --- /dev/null +++ b/doc/examples/osmo-stp.cfg @@ -0,0 +1,20 @@ +! +! osmo-stp (0.0.6.3.179-b248) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 0 + logging level lss7 debug + logging level lsccp debug + logging level lsua debug + logging level lm3ua debug +line vty + no login +! +cs7 instance 0 + xua rkm routing-key-allocation dynamic-permitted + listen m3ua 2905 + accept-asp-connections dynamic-permitted diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..6418aca --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMONETIF_CFLAGS) $(COVERAGE_FLAGS) +AM_LDFLAGS=$(COVERAGE_LDFLAGS) + +noinst_HEADERS = internal.h + +noinst_PROGRAMS = m3ua_example + +m3ua_example_SOURCES = m3ua_example.c sccp_test_server.c sccp_test_vty.c +m3ua_example_LDADD = $(top_builddir)/src/libosmo-sigtran.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) diff --git a/examples/internal.h b/examples/internal.h new file mode 100644 index 0000000..70b9058 --- /dev/null +++ b/examples/internal.h @@ -0,0 +1,12 @@ +#pragma once + +#define SSN_TEST_UNUSED 200 +#define SSN_TEST_REFUSE 201 +#define SSN_TEST_ECHO 202 +#define SSN_TEST_CALLBACK 203 + +struct osmo_sccp_user; + +int sccp_test_user_vty_install(struct osmo_sccp_instance *inst, int ssn); + +int sccp_test_server_init(struct osmo_sccp_instance *sccp); diff --git a/examples/m3ua_example.c b/examples/m3ua_example.c new file mode 100644 index 0000000..b3a1505 --- /dev/null +++ b/examples/m3ua_example.c @@ -0,0 +1,121 @@ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "internal.h" + +static struct osmo_sccp_instance *g_sccp; + +static struct osmo_sccp_instance *sua_server_helper(void) +{ + struct osmo_sccp_instance *sccp; + + sccp = osmo_sccp_simple_server(NULL, 1, OSMO_SS7_ASP_PROT_M3UA, + -1, "127.0.0.2"); + + osmo_sccp_simple_server_add_clnt(sccp, OSMO_SS7_ASP_PROT_M3UA, + "23", 23, -1, 0, NULL); + + return sccp; +} + +/*********************************************************************** + * Initialization + ***********************************************************************/ + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %d received\n", signal); + + switch (signal) { + case SIGUSR1: + talloc_report_full(osmo_sccp_get_ss7(g_sccp), stderr); + break; + case SIGUSR2: + talloc_report_full(NULL, stderr); + break; + } +} + +static const struct log_info_cat log_info_cat[] = { +}; + +static const struct log_info log_info = { + .cat = log_info_cat, + .num_cat = ARRAY_SIZE(log_info_cat), +}; + +static void init_logging(void) +{ + const int log_cats[] = { DLSS7, DLSUA, DLM3UA, DLSCCP, DLINP }; + unsigned int i; + + osmo_init_logging(&log_info); + + for (i = 0; i < ARRAY_SIZE(log_cats); i++) + log_set_category_filter(osmo_stderr_target, log_cats[i], 1, LOGL_DEBUG); +} + +static struct vty_app_info vty_info = { + .name = "sccp-test", + .version = 0, +}; + +int main(int argc, char **argv) +{ + bool client; + int rc; + + talloc_enable_leak_report_full(); + + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + + init_logging(); + osmo_ss7_init(); + osmo_fsm_log_addr(false); + vty_init(&vty_info); + osmo_ss7_vty_init_asp(NULL); + osmo_sccp_vty_init(); + + if (argc <= 1) + client = true; + else + client = false; + + rc = telnet_init_dynif(NULL, NULL, vty_get_bind_addr(), 2324+client); + if (rc < 0) { + perror("Erro binding VTY port\n"); + exit(1); + } + + + if (client) { + g_sccp = osmo_sccp_simple_client(NULL, "client", 23, OSMO_SS7_ASP_PROT_M3UA, 0, NULL, M3UA_PORT, "127.0.0.2"); + sccp_test_user_vty_install(g_sccp, OSMO_SCCP_SSN_BSSAP); + } else { + g_sccp = sua_server_helper(); + sccp_test_server_init(g_sccp); + } + + while (1) { + osmo_select_main(0); + } +} diff --git a/examples/sccp_test_server.c b/examples/sccp_test_server.c new file mode 100644 index 0000000..6249e45 --- /dev/null +++ b/examples/sccp_test_server.c @@ -0,0 +1,118 @@ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "internal.h" + +unsigned int conn_id =1; + +/* a simple SCCP User which refuses all connections and discards all + * unitdata */ +static int refuser_prim_cb(struct osmo_prim_hdr *oph, void *_scu) +{ + struct osmo_sccp_user *scu = _scu; + struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *) oph; + + switch (OSMO_PRIM_HDR(&scu_prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): + printf("%s: refusing N-CONNECT.ind (local_ref=%u)\n", + __func__, scu_prim->u.connect.conn_id); + osmo_sccp_tx_disconn(scu, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, + 23); + break; + default: + printf("%s: Unknown primitive %u:%u\n", __func__, + oph->primitive, oph->operation); + break; + } + msgb_free(oph->msg); + return 0; +} + +/* a simple SCCP User which accepts all connections and echos back all + * DATA + UNITDATA */ +static int echo_prim_cb(struct osmo_prim_hdr *oph, void *_scu) +{ + struct osmo_sccp_user *scu = _scu; + struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *) oph; + const uint8_t *data = msgb_l2(oph->msg); + unsigned int data_len = msgb_l2len(oph->msg); + + switch (OSMO_PRIM_HDR(&scu_prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): + printf("%s: Accepting N-CONNECT.ind (local_ref=%u)\n", + __func__, scu_prim->u.connect.conn_id); + osmo_sccp_tx_conn_resp(scu, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, + data, data_len); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): + printf("%s: Echoing N-DATA.ind (local_ref=%u)\n", + __func__, scu_prim->u.data.conn_id); + osmo_sccp_tx_data(scu, scu_prim->u.data.conn_id, + data, data_len); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION): + printf("%s: Echoing N-UNITDATA.ind\n", __func__); + osmo_sccp_tx_unitdata(scu, &scu_prim->u.unitdata.called_addr, + &scu_prim->u.unitdata.calling_addr, + data, data_len); + break; + default: + printf("%s: Unknown primitive %u:%u\n", __func__, + oph->primitive, oph->operation); + break; + } + msgb_free(oph->msg); + return 0; +} + +/* a simple SCCP User which receives UNITDATA messages and connects back + * to whoever sents UNITDATA and then echo's back all DATA */ +static int callback_prim_cb(struct osmo_prim_hdr *oph, void *_scu) +{ + struct osmo_sccp_user *scu = _scu; + struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *) oph; + const uint8_t *data = msgb_l2(oph->msg); + unsigned int data_len = msgb_l2len(oph->msg); + + switch (OSMO_PRIM_HDR(&scu_prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION): + printf("%s: N-UNITDATA.ind: Connectiong back to sender\n", __func__); + osmo_sccp_tx_conn_req(scu, conn_id++, + &scu_prim->u.unitdata.called_addr, + &scu_prim->u.unitdata.calling_addr, + data, data_len); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): + printf("%s: Echoing N-DATA.ind (local_ref=%u)\n", + __func__, scu_prim->u.data.conn_id); + osmo_sccp_tx_data(scu, scu_prim->u.data.conn_id, + data, data_len); + break; + default: + printf("%s: Unknown primitive %u:%u\n", __func__, + oph->primitive, oph->operation); + break; + } + msgb_free(oph->msg); + return 0; +} + +int sccp_test_server_init(struct osmo_sccp_instance *sccp) +{ + osmo_sccp_user_bind(sccp, "refuser", &refuser_prim_cb, SSN_TEST_REFUSE); + osmo_sccp_user_bind(sccp, "echo", &echo_prim_cb, SSN_TEST_ECHO); + osmo_sccp_user_bind(sccp, "callback", &callback_prim_cb, SSN_TEST_CALLBACK); + + return 0; +} diff --git a/examples/sccp_test_vty.c b/examples/sccp_test_vty.c new file mode 100644 index 0000000..d809fbc --- /dev/null +++ b/examples/sccp_test_vty.c @@ -0,0 +1,153 @@ + +#include + +#include +#include + +#include +#include + +#include "internal.h" + +#define SCU_NODE 23 + +static struct osmo_sccp_user *g_scu; + +static struct osmo_sccp_addr g_calling_addr = { + .presence = OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC, + .ri = OSMO_SCCP_RI_SSN_PC, + .pc = 23, +}; + +static struct osmo_sccp_addr g_called_addr = { + .presence = OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC, + .ssn = 1, + .ri = OSMO_SCCP_RI_SSN_PC, + .pc = 1, +}; + +DEFUN(scu_called_ssn, scu_called_ssn_cmd, + "called-addr-ssn <0-255>", + "Set SSN of SCCP CalledAddress\n" + "SSN of SCCP CalledAddress\n") +{ + g_called_addr.ssn = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(scu_conn_req, scu_conn_req_cmd, + "connect-req <0-16777216> [DATA]", + "N-CONNECT.req\n" + "Connection ID\n") +{ + struct osmo_sccp_user *scu = vty->index; + int conn_id = atoi(argv[0]); + const char *data = argv[1]; + + osmo_sccp_tx_conn_req(scu, conn_id, &g_calling_addr, &g_called_addr, + (const uint8_t *)data, data ? strlen(data)+1 : 0); + return CMD_SUCCESS; +} + +DEFUN(scu_conn_resp, scu_conn_resp_cmd, + "connect-resp <0-16777216> [DATA]", + "N-CONNET.resp\n" + "Connection ID\n") +{ + struct osmo_sccp_user *scu = vty->index; + int conn_id = atoi(argv[0]); + const char *data = argv[1]; + + osmo_sccp_tx_conn_resp(scu, conn_id, NULL, + (const uint8_t *)data, data ? strlen(data)+1 : 0); + return CMD_SUCCESS; +} + +DEFUN(scu_data_req, scu_data_req_cmd, + "data-req <0-16777216> DATA", + "N-DATA.req\n" + "Connection ID\n") +{ + struct osmo_sccp_user *scu = vty->index; + int conn_id = atoi(argv[0]); + const char *data = argv[1]; + + osmo_sccp_tx_data(scu, conn_id, (const uint8_t *)data, strlen(data)+1); + return CMD_SUCCESS; +} + +DEFUN(scu_unitdata_req, scu_unitdata_req_cmd, + "unitdata-req DATA", + "N-UNITDATA.req\n") +{ + struct osmo_sccp_user *scu = vty->index; + const char *data = argv[0]; + + osmo_sccp_tx_unitdata(scu, &g_calling_addr, &g_called_addr, + (const uint8_t *)data, strlen(data)+1); + return CMD_SUCCESS; +} + +DEFUN(scu_disc_req, scu_disc_req_cmd, + "disconnect-req <0-16777216>", + "N-DISCONNT.req\n" + "Connection ID\n") +{ + struct osmo_sccp_user *scu = vty->index; + int conn_id = atoi(argv[0]); + + osmo_sccp_tx_disconn(scu, conn_id, NULL, 42); + return CMD_SUCCESS; +} + +static struct cmd_node scu_node = { + SCU_NODE, + "%s(sccp-user)# ", + 1, +}; + +DEFUN(scu, scu_cmd, + "sccp-user", + "Enter SCCP User Node\n") +{ + vty->node = SCU_NODE; + vty->index = g_scu; + return CMD_SUCCESS; +} + +static int testclnt_prim_cb(struct osmo_prim_hdr *oph, void *_scu) +{ + struct osmo_sccp_user *scu __attribute__((unused)) = _scu; + struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *) oph; + + switch (OSMO_PRIM_HDR(&scu_prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): + default: + break; + } + msgb_free(oph->msg); + return 0; +} + + +int sccp_test_user_vty_install(struct osmo_sccp_instance *inst, int ssn) +{ + g_scu = osmo_sccp_user_bind(inst, "test_client_vty", testclnt_prim_cb, ssn); + if (!g_scu) + return -1; + + g_calling_addr.ssn = ssn; + + install_node(&scu_node, NULL); + vty_install_default(SCU_NODE); + install_element(SCU_NODE, &scu_called_ssn_cmd); + install_element(SCU_NODE, &scu_conn_req_cmd); + install_element(SCU_NODE, &scu_conn_resp_cmd); + install_element(SCU_NODE, &scu_data_req_cmd); + install_element(SCU_NODE, &scu_unitdata_req_cmd); + install_element(SCU_NODE, &scu_disc_req_cmd); + + install_element(ENABLE_NODE, &scu_cmd); + + return 0; +} 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 . + +# 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..7cd4b57 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = sccp mtp osmocom diff --git a/include/mtp/Makefile.am b/include/mtp/Makefile.am new file mode 100644 index 0000000..dbd0e79 --- /dev/null +++ b/include/mtp/Makefile.am @@ -0,0 +1,2 @@ +mtp_HEADERS = mtp_level3.h mtp_pcap.h +mtpdir = $(includedir)/osmocom/mtp diff --git a/include/mtp/mtp_level3.h b/include/mtp/mtp_level3.h new file mode 100644 index 0000000..d0d24a1 --- /dev/null +++ b/include/mtp/mtp_level3.h @@ -0,0 +1,182 @@ +/* Q.701-Q.704, Q.706, Q.707 handling code */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#include + +#include +#include + + +/* + * pssible service information octets.. + */ +#define MTP_NI_NATION_NET 0x02 + +#define MTP_SI_MNT_SNM_MSG 0x00 +#define MTP_SI_MNT_REG_MSG 0x01 +#define MTP_SI_MNT_SCCP 0x03 +#define MTP_SI_MNT_ISUP 0x05 + +/* + * h0 contains the group, h1 the semantic of it + */ + +#define MTP_TST_MSG_GRP 0x01 +#define MTP_PROHIBIT_MSG_GRP 0x04 +#define MTP_SROUTE_MSG_GRP 0x05 +#define MTP_TRF_RESTR_MSG_GRP 0x07 + +/* h1 values for different groups */ +#define MTP_TST_MSG_SLTM 0x01 +#define MTP_TST_MSG_SLTA 0x02 + +#define MTP_RESTR_MSG_ALLWED 0x01 + +/* For the prohibit group */ +#define MTP_PROHIBIT_MSG_SIG 0x01 +#define MTP_PROHIBIT_MSG_TFA 0x05 + +/* For the Signalling-route-set-test */ +#define MTP_SROUTE_MSG_TEST 0x01 + + +#define SCCP_SST 0x03 +#define SCCP_SSP 0x02 +#define SCCP_SSA 0x01 + +#define MTP_LINK_MASK 0x0F +#define MTP_ADDR_MASK 0x3FFF +#define MTP_APOC_MASK 0x3f + + +#if OSMO_IS_LITTLE_ENDIAN +#define MTP_LINK_SLS(addr) ((addr >>28) & MTP_LINK_MASK) +#define MTP_ADDR(link, dpc, opc) \ + (((dpc) & MTP_ADDR_MASK) << 0 | \ + ((opc) & MTP_ADDR_MASK) << 14| \ + ((link) & MTP_LINK_MASK) << 28) +#define MTP_MAKE_APOC(apoc) \ + (apoc & 0x3fff) +#define MTP_READ_DPC(addr) \ + (((addr) >> 0) & MTP_ADDR_MASK) +#define MTP_READ_OPC(addr) \ + (((addr) >> 14) & MTP_ADDR_MASK) +#elif OSMO_IS_BIG_ENDIAN +static inline uint32_t c_swap_32(uint32_t in) +{ + return (((in & 0x000000ff) << 24) | + ((in & 0x0000ff00) << 8) | + ((in & 0x00ff0000) >> 8) | + ((in & 0xff000000) >> 24)); +} +static inline uint16_t c_swap_16(uint16_t in) +{ + return (((in & 0x00ff) << 8) | + (in & 0xff00) >> 8); +} +#define MTP_LINK_SLS(addr) ((c_swap_32(addr)>>28) & MTP_LINK_MASK) +#define MTP_ADDR(link, dpc, opc) \ + c_swap_32(((dpc) & MTP_ADDR_MASK) << 0 | \ + ((opc) & MTP_ADDR_MASK) << 14| \ + ((link) & MTP_LINK_MASK) << 28) +#define MTP_MAKE_APOC(apoc) \ + c_swap_16((apoc & 0x3fff)) +#define MTP_READ_DPC(addr) \ + (c_swap_32(addr) & MTP_ADDR_MASK) +#define MTP_READ_OPC(addr) \ + ((c_swap_32(addr) >> 14) & MTP_ADDR_MASK) +#else +#error "Unknown endian" +#endif + + + +/* + * not the on wire address... + */ +struct mtp_addr { + uint16_t dpc; + uint16_t opc; + uint8_t link; +} __attribute__((packed)); + +/* + * the struct is defined in Q.704 and can be seen in the + * wireshark dissectors too + */ +struct mtp_level_3_hdr { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t ser_ind : 4, + spare : 2, + ni : 2; +#elif OSMO_IS_BIG_ENDIAN + uint8_t ni : 2, + spare : 2, + ser_ind : 4; +#endif + uint32_t addr; + uint8_t data[0]; +} __attribute__((packed)); + +struct mtp_level_3_cmn { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t h0 : 4, + h1 : 4; +#elif OSMO_IS_BIG_ENDIAN + uint8_t h1 : 4, + h0 : 4; +#endif +} __attribute__((packed)); + +struct mtp_level_3_mng { + struct mtp_level_3_cmn cmn; +#if OSMO_IS_LITTLE_ENDIAN + uint8_t spare : 4, + length : 4; +#elif OSMO_IS_BIG_ENDIAN + uint8_t length : 4, + spare : 4; +#endif + uint8_t data[0]; +} __attribute__((packed)); + +struct mtp_level_3_prohib { + struct mtp_level_3_cmn cmn; + + uint16_t apoc; +} __attribute__((packed)); + +struct sccp_con_ctrl_prt_mgt { + uint8_t sst; + uint8_t assn; /* affected sub system number */ + uint16_t apoc; +#if OSMO_IS_LITTLE_ENDIAN + uint8_t mul_ind : 2, + spare : 6; +#elif OSMO_IS_BIG_ENDIAN + uint8_t spare : 6, + mul_ind : 2; +#endif +} __attribute__((packed)); + diff --git a/include/mtp/mtp_pcap.h b/include/mtp/mtp_pcap.h new file mode 100644 index 0000000..5e8f7d3 --- /dev/null +++ b/include/mtp/mtp_pcap.h @@ -0,0 +1,29 @@ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#ifndef mtp_pcap_h +#define mtp_pcap_h + +#include + +int mtp_pcap_write_header(int fd); +int mtp_pcap_write_msu(int fd, const uint8_t *data, int length); + +#endif diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am new file mode 100644 index 0000000..7e86a4b --- /dev/null +++ b/include/osmocom/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = sigtran diff --git a/include/osmocom/sigtran/Makefile.am b/include/osmocom/sigtran/Makefile.am new file mode 100644 index 0000000..0aa90cb --- /dev/null +++ b/include/osmocom/sigtran/Makefile.am @@ -0,0 +1,7 @@ +sigtran_HEADERS = xua_types.h xua_msg.h m2ua_types.h sccp_sap.h \ + sigtran_sap.h sccp_helpers.h mtp_sap.h osmo_ss7.h + +sigtrandir = $(includedir)/osmocom/sigtran + +sigtran_prot_HEADERS = protocol/sua.h protocol/m3ua.h protocol/mtp.h +sigtran_protdir = $(includedir)/osmocom/sigtran/protocol diff --git a/include/osmocom/sigtran/m2ua_types.h b/include/osmocom/sigtran/m2ua_types.h new file mode 100644 index 0000000..7184f54 --- /dev/null +++ b/include/osmocom/sigtran/m2ua_types.h @@ -0,0 +1,255 @@ +#ifndef m2ua_types_h +#define m2ua_types_h + +/** + * Types found in the M2UA RFC 3331 + */ + +#include + +#define M2UA_VERSION 1 +#define M2UA_SPARE 0 + +enum { + M2UA_CLS_MGMT, /* Management (MGMT) Message [IUA/M2UA/M3UA/SUA] */ + M2UA_CLS_TRANS, /* Transfer Messages [M3UA] */ + M2UA_CLS_SSNM, /* SS7 Signalling Network Management (SSNM) Messages [M3UA/SUA] */ + M2UA_CLS_ASPSM, /* ASP State Maintenance (ASPSM) Messages [IUA/M2UA/M3UA/SUA] */ + M2UA_CLS_ASPTM, /* ASP Traffic Maintenance (ASPTM) Messages [IUA/M2UA/M3UA/SUA] */ + M2UA_CLS_QPTM, /* Q.921/Q.931 Boundary Primitives Transport (QPTM) */ + M2UA_CLS_MAUP, /* MTP2 User Adaptation (MAUP) Messages [M2UA] */ + M2UA_CLS_SUA_LESS, /* Connectionless Messages [SUA] */ + M2UA_CLS_SUA_CONN, /* Connection-Oriented Messages [SUA] */ + M2UA_CLS_RKM, /* Routing Key Management (RKM) Messages (M3UA) */ + M2UA_CLS_IIM, /* Interface Identifier Management (IIM) Messages (M2UA) */ +}; + +/** + * MTP2 User Adaption = MAUP messages + */ +enum { + M2UA_MAUP_RESERVED, /* Reserved */ + M2UA_MAUP_DATA, /* Data */ + M2UA_MAUP_EST_REQ, /* Establish Request */ + M2UA_MAUP_EST_CON, /* Establish Confirm */ + M2UA_MAUP_REL_REQ, /* Release Request */ + M2UA_MAUP_REL_CON, /* Release Confirm */ + M2UA_MAUP_REL_IND, /* Release Indication */ + M2UA_MAUP_STATE_REQ, /* State Request */ + M2UA_MAUP_STATE_CON, /* State Confirm */ + M2UA_MAUP_STATE_IND, /* State Indication */ + M2UA_MAUP_RETR_REQ, /* Data Retrieval Request */ + M2UA_MAUP_D_RETR_CON, /* Data Retrieval Confirm */ + M2UA_MAUP_D_RETR_IND, /* Data Retrieval Indication */ + M2UA_MAUP_D_RETR_COMPL, /* Data Retrieval Complete Indication */ + M2UA_MAUP_CONG_IND, /* Congestion Indication */ + M2UA_MAUP_DATA_ACK, /* Data Acknowledge */ +}; + +/** + * Application Server Process State Maintaenance (ASPSM) messages + */ +enum { + M2UA_ASPSM_RESERVED, /* Reserved */ + M2UA_ASPSM_UP, /* ASP Up (UP) */ + M2UA_ASPSM_DOWN, /* ASP Down (DOWN) */ + M2UA_ASPSM_BEAT, /* Heartbeat (BEAT) */ + M2UA_ASPSM_UP_ACK, /* ASP Up Ack (UP ACK) */ + M2UA_ASPSM_DOWN_ACK, /* ASP Down Ack (DOWN ACK) */ + M2UA_ASPSM_BEAT_ACK, /* Heartbeat Ack (BEAT ACK) */ +}; + +/** + * Application Server Process Traffic Maintaenance (ASPTM) messages. + */ +enum { + M2UA_ASPTM_RESERVED, /* Reserved */ + M2UA_ASPTM_ACTIV, /* ASP Active (ACTIVE) */ + M2UA_ASPTM_INACTIV, /* ASP Inactive (INACTIVE) */ + M2UA_ASPTM_ACTIV_ACK, /* ASP Active Ack (ACTIVE ACK) */ + M2UA_ASPTM_INACTIV_ACK, /* ASP Inactive Ack (INACTIVE ACK) */ +}; + +/** + * Management (MGMT) messages + */ +enum { + M2UA_MGMT_ERROR, /* Error (ERR) */ + M2UA_MGMT_NTFY, /* Notify (NTFY) */ +}; + +/** + * Interface Identifier Management (IIM) Messages + */ +enum { + M2UA_IIM_RESERVED, /* Reserved */ + M2UA_IIM_REG_REQ, /* Registration Request (REG REQ) */ + M2UA_IIM_REG_RSP, /* Registration Response (REG RSP) */ + M2UA_IIM_DEREG_REQ, /* Deregistration Request (DEREG REQ) */ + M2UA_IIM_DEREG_RSP, /* Deregistration Response (DEREG RSP) */ +}; + +/** + * Tag Values for M2UA + */ +enum { + __m2ua_tag_start = 767, + + M2UA_TAG_DATA, /* Protocol Data 1 */ + M2UA_TAG_DATA_TTC, /* Protocol Data 2 (TTC) */ + M2UA_TAG_STATE_REQ, /* State Request */ + M2UA_TAG_STATE_EVENT, /* State Event */ + M2UA_TAG_CONG_STATUS, /* Congestion Status */ + M2UA_TAG_DISC_STATUS, /* Discard Status */ + M2UA_TAG_ACTION, /* Action */ + M2UA_TAG_SEQ_NO, /* Sequence Number */ + M2UA_TAG_RETR_RES, /* Retrieval Result */ + M2UA_TAG_LNK_KEY, /* Link Key */ + M2UA_TAG_L_LNK_KEY_ID, /* Local-LK-Identifier */ + M2UA_TAG_SDT, /* Signalling Data Terminal (SDT) Identifier */ + M2UA_TAG_SDL, /* Signalling Data Link (SDL) Identifier */ + M2UA_TAG_REG_RES, /* Registration Result */ + M2UA_TAG_RES_STATUS, /* Registration Status */ + M2UA_TAG_DEREG_RES, /* De-Registration Result */ + M2UA_TAG_DEREG_STATUS, /* De-Registration Status */ +}; + +/** + * 3.3.1.5 State Request + */ +enum { + M2UA_STATUS_LPO_SET, /* Request local processor outage */ + M2UA_STATUS_LPO_CLEAR, /* Request local processor outage recovered */ + M2UA_STATUS_EMER_SET, /* Request emergency alignment */ + M2UA_STATUS_EMER_CLEAR, /* Request normal alignment (cancel emergency) */ + M2UA_STATUS_FLUSH_BUFFERS, /* Flush or clear receive, transmit and retransmit queues */ + M2UA_STATUS_CONTINUE, /* Continue or Resume */ + M2UA_STATUS_CLEAR_RTB, /* Clear the retransmit queue */ + M2UA_STATUS_AUDIT, /* Audit state of link */ + M2UA_STATUS_CONG_CLEAR, /* Congestion cleared */ + M2UA_STATUS_CONG_ACCEPT, /* Congestion accept */ + M2UA_STATUS_CONG_DISCARD, /* Congestion discard */ +}; + +/** + * 3.3.1.7 State Indication + */ +enum { + __m2ua_event_dummy, + M2UA_EVENT_RPO_ENTER, /* Remote entered processor outage */ + M2UA_EVENT_RPO_EXIT, /* Remote exited processor outage */ + M2UA_EVENT_LPO_ENTER, /* Link entered processor outage */ + M2UA_EVENT_LPO_EXIT, /* Link exited processor outage */ +}; + +/** + * 3.3.1.8 Congestion Indication + */ +enum { + M2UA_LEVEL_NONE, /* No congestion */ + M2UA_LEVEL_1, /* Congestion Level 1 */ + M2UA_LEVEL_2, /* Congestion Level 2 */ + M2UA_LEVEL_3, /* Congestion Level 3 */ +}; + +/** + * 3.3.1.9 Retrieval Request + */ +enum { + M2UA_ACTION_RTRV_BSN, /* Retrieve the backward sequence number */ + M2UA_ACTION_RTRV_MSGS, /* Retrieve the PDUs from the transmit and retransmit queues. */ +}; + +/** + * 3.3.1.10 Retrieval Confirm + */ +enum { + M2UA_RESULT_SUCCESS, /* Action successful */ + M2UA_RESULT_FAILURE, /* Action failed */ +}; + +/** + * 3.3.2.7 ASP Active (ASPAC) + */ +enum { + M2UA_TRA_OVERRIDE = 1, /* Override */ + M2UA_TRA_LOAD_SHARE = 2, /* Load-share */ + M2UA_TRA_BROADCAST = 3, /* Broadcast */ +}; + +/** + * 3.3.3.1 Error (ERR) + */ +enum { + __m2ua_err_unused, + M2UA_ERR_INV_VER, /* Invalid Version */ + M2UA_ERR_INV_INT_IDENT, /* Invalid Interface Identifier */ + M2UA_ERR_UNS_MSG_CLASS, /* Unsupported Message Class */ + M2UA_ERR_UNS_MSG_TYPE, /* Unsupported Message Type */ + M2UA_ERR_UNS_TRA_MODE, /* Unsupported Traffic Handling Mode */ + M2UA_ERR_UNE_MSG, /* Unexpected Message */ + M2UA_ERR_PROTO_ERROR, /* Protocol Error */ + M2UA_ERR_UNS_INT_IDENT_T, /* Unsupported Interface Identifier Type */ + M2UA_ERR_INV_STR_IDENT, /* Invalid Stream Identifier */ + M2UA_ERR_UNUSED1, /* Unused in M2UA */ + M2UA_ERR_UNUSED2, /* Unused in M2UA */ + M2UA_ERR_UNUSED3, /* Unused in M2UA */ + M2UA_ERR_REFUSED, /* Refused - Management Blocking */ + M2UA_ERR_ASP_IDENT_REQ, /* ASP Identifier Required */ + M2UA_ERR_INV_ASP_IDENT, /* Invalid ASP Identifier */ + M2UA_ERR_ASP_ACT_FOR_IDENT, /* ASP Active for Interface Identifier(s) */ + M2UA_ERR_INV_PARAM_VAL, /* Invalid Parameter Value */ + M2UA_ERR_PARAM_FIELD_ERR, /* Parameter Field Error */ + M2UA_ERR_UNEXP_PARAM, /* Unexpected Parameter */ + M2UA_ERR_UNUSED4, /* Unused in M2UA */ + M2UA_ERR_UNUSED5, /* Unused in M2UA */ + M2UA_ERR_MISSING_PARAM, /* Missing Parameter */ +}; + +/** + * 3.3.3.2 Notify (NTFY) + */ +enum { + M2UA_STP_AS_STATE_CHG = 1, /* Application Server state change (AS_State_Change) */ + M2UA_STP_OTHER = 2, /* Other */ +}; + +enum { + /* this is for M2UA_STP_AS_STATE_CHG */ + M2UA_STP_AS_INACTIVE = 2, /* Application Server Inactive (AS_Inactive) */ + M2UA_STP_AS_ACTIVE = 3, /* Application Server Active (AS_Active) */ + M2UA_STP_AS_PENDING = 4, /* Application Server Pending (AS_Pending) */ + + /* this is for the other */ + M2UA_STP_O_INSUFF_ASP_RES = 1, /* Insufficient ASP resources active in AS */ + M2UA_STP_O_ALT_ASP_ACTIVR = 2, /* Alternate ASP Active */ + M2UA_STP_O_ASP_FAILURE = 3, /* ASP Failure */ +}; + +/** + * 3.3.4.3 Registration Response (REG RSP) + */ +enum { + M2UA_REG_SUCC, /* Successfully Registered */ + M2UA_REG_ERR_UNK, /* Error - Unknown */ + M2UA_REG_ERR_INV_SDLI, /* Error - Invalid SDLI */ + M2UA_REG_ERR_INV_SDTI, /* Error - Invalid SDTI */ + M2UA_REG_ERR_INV_LNK_KEY, /* Error - Invalid Link Key */ + M2UA_REG_ERR_PERM_DENIED, /* Error - Permission Denied */ + M2UA_REG_ERR_OVERLAP_KEY, /* Error - Overlapping (Non-unique) Link Key */ + M2UA_REG_ERR_LNK_KEY_NOT_PROV, /* Error - Link Key not Provisioned */ + M2UA_REG_ERR_INSUFF_RES, /* Error - Insufficient Resources */ +}; + +/** + * 3.3.4.4 De-Registration Response (DEREG RSP) + */ +enum { + M2UA_DEREG_SUCC, /* Successfully De-registered */ + M2UA_DEREG_ERR_UNK, /* Error - Unknown */ + M2UA_DEREG_ERR_INV_IDENT, /* Error - Invalid Interface Identifier */ + M2UA_DEREG_ERR_PERM_DENIED, /* Error - Permission Denied */ + M2UA_DEREG_ERR_NOT_REG, /* Error - Not Registered */ +}; + +#endif diff --git a/include/osmocom/sigtran/mtp_sap.h b/include/osmocom/sigtran/mtp_sap.h new file mode 100644 index 0000000..0ae8592 --- /dev/null +++ b/include/osmocom/sigtran/mtp_sap.h @@ -0,0 +1,68 @@ +#pragma once + +/* MTP User SAP description in accordance with ITU Q.701 */ + +/* (C) 2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include + +enum osmo_mtp_prim_type { + OSMO_MTP_PRIM_TRANSFER, + OSMO_MTP_PRIM_PAUSE, + OSMO_MTP_PRIM_RESUME, + OSMO_MTP_PRIM_STATUS, +}; + +#define MTP_SIO(service, net_ind) (((net_ind & 0x3) << 6) | (service & 0xF)) + +struct osmo_mtp_transfer_param { + uint32_t opc; + uint32_t dpc; + uint8_t sls; + uint8_t sio; +}; + +struct osmo_mtp_pause_param { + uint32_t affected_dpc; +}; + +struct osmo_mtp_resume_param { + uint32_t affected_dpc; +}; + +struct osmo_mtp_status_param { + uint32_t affected_dpc; + uint32_t cause; +}; + +struct osmo_mtp_prim { + struct osmo_prim_hdr oph; + union { + struct osmo_mtp_transfer_param transfer; + struct osmo_mtp_pause_param pause; + struct osmo_mtp_resume_param resume; + struct osmo_mtp_status_param status; + } u; +}; + +#define msgb_mtp_prim(msg) ((struct osmo_mtp_prim *)(msg)->l1h) + +char *osmo_mtp_prim_name(struct osmo_prim_hdr *oph); diff --git a/include/osmocom/sigtran/osmo_ss7.h b/include/osmocom/sigtran/osmo_ss7.h new file mode 100644 index 0000000..71c2022 --- /dev/null +++ b/include/osmocom/sigtran/osmo_ss7.h @@ -0,0 +1,488 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +extern struct llist_head osmo_ss7_instances; + +struct osmo_ss7_instance; +struct osmo_ss7_user; +struct osmo_sccp_instance; +struct osmo_mtp_prim; +struct osmo_xua_layer_manager; + +int osmo_ss7_init(void); +int osmo_ss7_find_free_rctx(struct osmo_ss7_instance *inst); + +bool osmo_ss7_pc_is_local(struct osmo_ss7_instance *inst, uint32_t pc); +int osmo_ss7_pointcode_parse(struct osmo_ss7_instance *inst, const char *str); +int osmo_ss7_pointcode_parse_mask_or_len(struct osmo_ss7_instance *inst, const char *in); +const char *osmo_ss7_pointcode_print(const struct osmo_ss7_instance *inst, uint32_t pc); +const char *osmo_ss7_pointcode_print2(const struct osmo_ss7_instance *inst, uint32_t pc); + +/* All known point-code formats have a length of or below 24 bit. + * A point-code value exceeding that is used to indicate an unset PC. */ +#define OSMO_SS7_PC_INVALID 0xffffffff +static inline bool osmo_ss7_pc_is_valid(uint32_t pc) +{ + return pc <= 0x00ffffff; +} + +/*********************************************************************** + * SS7 Routing Tables + ***********************************************************************/ + +struct osmo_ss7_route_table { + /*! member in list of routing tables */ + struct llist_head list; + /*! \ref osmo_ss7_instance to which we belong */ + struct osmo_ss7_instance *inst; + /*! list of \ref osmo_ss7_route */ + struct llist_head routes; + + struct { + char *name; + char *description; + } cfg; +}; + +struct osmo_ss7_route_table * +osmo_ss7_route_table_find(struct osmo_ss7_instance *inst, const char *name); +struct osmo_ss7_route_table * +osmo_ss7_route_table_find_or_create(struct osmo_ss7_instance *inst, const char *name); +void osmo_ss7_route_table_destroy(struct osmo_ss7_route_table *rtbl); + +/*********************************************************************** + * SS7 Instances + ***********************************************************************/ + +struct osmo_ss7_pc_fmt { + char delimiter; + uint8_t component_len[3]; +}; + +struct osmo_ss7_instance { + /*! member of global list of instances */ + struct llist_head list; + /*! list of \ref osmo_ss7_linkset */ + struct llist_head linksets; + /*! list of \ref osmo_ss7_as */ + struct llist_head as_list; + /*! list of \ref osmo_ss7_asp */ + struct llist_head asp_list; + /*! list of \ref osmo_ss7_route_table */ + struct llist_head rtable_list; + /*! list of \ref osmo_xua_servers */ + struct llist_head xua_servers; + /* array for faster lookup of user (indexed by service + * indicator) */ + const struct osmo_ss7_user *user[16]; + + struct osmo_ss7_route_table *rtable_system; + + struct osmo_sccp_instance *sccp; + + struct { + uint32_t id; + char *name; + char *description; + uint32_t primary_pc; + /* secondary PCs */ + /* capability PCs */ + uint8_t network_indicator; + struct osmo_ss7_pc_fmt pc_fmt; + bool permit_dyn_rkm_alloc; + struct llist_head sccp_address_book; + } cfg; +}; + +struct osmo_ss7_instance *osmo_ss7_instance_find(uint32_t id); +struct osmo_ss7_instance * +osmo_ss7_instance_find_or_create(void *ctx, uint32_t id); +void osmo_ss7_instance_destroy(struct osmo_ss7_instance *inst); +int osmo_ss7_instance_set_pc_fmt(struct osmo_ss7_instance *inst, + uint8_t c0, uint8_t c1, uint8_t c2); + +/*********************************************************************** + * MTP Users (Users of MTP, such as SCCP or ISUP) + ***********************************************************************/ + +struct osmo_ss7_user { + /* pointer back to SS7 instance */ + struct osmo_ss7_instance *inst; + /* name of the user */ + const char *name; + /* primitive call-back for incoming MTP primitives */ + osmo_prim_cb prim_cb; + /* private data */ + void *priv; +}; + +int osmo_ss7_user_register(struct osmo_ss7_instance *inst, uint8_t service_ind, + struct osmo_ss7_user *user); + +int osmo_ss7_user_unregister(struct osmo_ss7_instance *inst, uint8_t service_ind, + struct osmo_ss7_user *user); + +int osmo_ss7_mtp_to_user(struct osmo_ss7_instance *inst, struct osmo_mtp_prim *omp); + +/* SS7 User wants to issue MTP-TRANSFER.req */ +int osmo_ss7_user_mtp_xfer_req(struct osmo_ss7_instance *inst, + struct osmo_mtp_prim *omp); + +/*********************************************************************** + * SS7 Links + ***********************************************************************/ + +enum osmo_ss7_link_adm_state { + OSMO_SS7_LS_SHUTDOWN, + OSMO_SS7_LS_INHIBITED, + OSMO_SS7_LS_ENABLED, + _NUM_OSMO_SS7_LS +}; + +struct osmo_ss7_linkset; +struct osmo_ss7_link; + +struct osmo_ss7_link { + /*! \ref osmo_ss7_linkset to which we belong */ + struct osmo_ss7_linkset *linkset; + struct { + char *name; + char *description; + uint32_t id; + + enum osmo_ss7_link_adm_state adm_state; + } cfg; +}; + +void osmo_ss7_link_destroy(struct osmo_ss7_link *link); +struct osmo_ss7_link * +osmo_ss7_link_find_or_create(struct osmo_ss7_linkset *lset, uint32_t id); + +/*********************************************************************** + * SS7 Linksets + ***********************************************************************/ + +struct osmo_ss7_linkset { + struct llist_head list; + /*! \ref osmo_ss7_instance to which we belong */ + struct osmo_ss7_instance *inst; + /*! array of \ref osmo_ss7_link */ + struct osmo_ss7_link *links[16]; + + struct { + char *name; + char *description; + uint32_t adjacent_pc; + uint32_t local_pc; + } cfg; +}; + +void osmo_ss7_linkset_destroy(struct osmo_ss7_linkset *lset); +struct osmo_ss7_linkset * +osmo_ss7_linkset_find_by_name(struct osmo_ss7_instance *inst, const char *name); +struct osmo_ss7_linkset * +osmo_ss7_linkset_find_or_create(struct osmo_ss7_instance *inst, const char *name, uint32_t pc); + + +/*********************************************************************** + * SS7 Routes + ***********************************************************************/ + +struct osmo_ss7_route { + /*! member in \ref osmo_ss7_route_table.routes */ + struct llist_head list; + /*! \ref osmo_ss7_route_table to which we belong */ + struct osmo_ss7_route_table *rtable; + + struct { + /*! pointer to linkset (destination) of route */ + struct osmo_ss7_linkset *linkset; + /*! pointer to Application Server */ + struct osmo_ss7_as *as; + } dest; + + struct { + /* FIXME: presence? */ + uint32_t pc; + uint32_t mask; + /*! human-specified linkset name */ + char *linkset_name; + /*! lower priority is higher */ + uint32_t priority; + uint8_t qos_class; + } cfg; +}; + +struct osmo_ss7_route * +osmo_ss7_route_find_dpc(struct osmo_ss7_route_table *rtbl, uint32_t dpc); +struct osmo_ss7_route * +osmo_ss7_route_find_dpc_mask(struct osmo_ss7_route_table *rtbl, uint32_t dpc, + uint32_t mask); +struct osmo_ss7_route * +osmo_ss7_route_lookup(struct osmo_ss7_instance *inst, uint32_t dpc); +struct osmo_ss7_route * +osmo_ss7_route_create(struct osmo_ss7_route_table *rtbl, uint32_t dpc, + uint32_t mask, const char *linkset_name); +void osmo_ss7_route_destroy(struct osmo_ss7_route *rt); + + +/*********************************************************************** + * SS7 Application Servers + ***********************************************************************/ + +struct osmo_ss7_routing_key { + uint32_t context; + uint32_t l_rk_id; + + uint32_t pc; + uint8_t si; + uint32_t ssn; + /* FIXME: more complex routing keys */ +}; + +enum osmo_ss7_as_traffic_mode { + OSMO_SS7_AS_TMOD_OVERRIDE = 0, /* default */ + OSMO_SS7_AS_TMOD_BCAST, + OSMO_SS7_AS_TMOD_LOADSHARE, + OSMO_SS7_AS_TMOD_ROUNDROBIN, + _NUM_OSMO_SS7_ASP_TMOD +}; + +extern struct value_string osmo_ss7_as_traffic_mode_vals[]; + +static inline const char * +osmo_ss7_as_traffic_mode_name(enum osmo_ss7_as_traffic_mode mode) +{ + return get_value_string(osmo_ss7_as_traffic_mode_vals, mode); +} + +enum osmo_ss7_asp_protocol { + OSMO_SS7_ASP_PROT_NONE, + OSMO_SS7_ASP_PROT_SUA, + OSMO_SS7_ASP_PROT_M3UA, + OSMO_SS7_ASP_PROT_IPA, + _NUM_OSMO_SS7_ASP_PROT +}; + +extern struct value_string osmo_ss7_asp_protocol_vals[]; + +static inline const char * +osmo_ss7_asp_protocol_name(enum osmo_ss7_asp_protocol mode) +{ + return get_value_string(osmo_ss7_asp_protocol_vals, mode); +} + +int osmo_ss7_asp_protocol_port(enum osmo_ss7_asp_protocol prot); + +struct osmo_ss7_as { + /*! entry in 'ref osmo_ss7_instance.as_list */ + struct llist_head list; + struct osmo_ss7_instance *inst; + + /*! AS FSM */ + struct osmo_fsm_inst *fi; + + /*! Were we dynamically allocated by RKM? */ + bool rkm_dyn_allocated; + + struct { + char *name; + char *description; + enum osmo_ss7_asp_protocol proto; + struct osmo_ss7_routing_key routing_key; + enum osmo_ss7_as_traffic_mode mode; + uint32_t recovery_timeout_msec; + uint8_t qos_class; + struct { + uint32_t dpc; + } pc_override; + + struct osmo_ss7_asp *asps[16]; + } cfg; +}; + +struct osmo_ss7_as * +osmo_ss7_as_find_by_name(struct osmo_ss7_instance *inst, const char *name); +struct osmo_ss7_as * +osmo_ss7_as_find_by_rctx(struct osmo_ss7_instance *inst, uint32_t rctx); +struct osmo_ss7_as * +osmo_ss7_as_find_by_l_rk_id(struct osmo_ss7_instance *inst, uint32_t l_rk_id); +struct osmo_ss7_as *osmo_ss7_as_find_by_proto(struct osmo_ss7_instance *inst, + enum osmo_ss7_asp_protocol proto); +struct osmo_ss7_as * +osmo_ss7_as_find_or_create(struct osmo_ss7_instance *inst, const char *name, + enum osmo_ss7_asp_protocol proto); +int osmo_ss7_as_add_asp(struct osmo_ss7_as *as, const char *asp_name); +int osmo_ss7_as_del_asp(struct osmo_ss7_as *as, const char *asp_name); +void osmo_ss7_as_destroy(struct osmo_ss7_as *as); +bool osmo_ss7_as_has_asp(struct osmo_ss7_as *as, + struct osmo_ss7_asp *asp); +void osmo_ss7_asp_disconnect(struct osmo_ss7_asp *asp); + + +/*********************************************************************** + * SS7 Application Server Processes + ***********************************************************************/ + +struct osmo_ss7_asp_peer { + char *host; + uint16_t port; +}; + +enum osmo_ss7_asp_admin_state { + /*! no SCTP association with peer */ + OSMO_SS7_ASP_ADM_S_SHUTDOWN, + /*! SCP association, but reject ASP-ACTIVE */ + OSMO_SS7_ASP_ADM_S_BLOCKED, + /*! in normal operation */ + OSMO_SS7_ASP_ADM_S_ENABLED, +}; + +struct osmo_ss7_asp { + /*! entry in \ref osmo_ss7_instance.asp_list */ + struct llist_head list; + struct osmo_ss7_instance *inst; + + /*! ASP FSM */ + struct osmo_fsm_inst *fi; + + /*! \ref osmo_xua_server over which we were established */ + struct osmo_xua_server *xua_server; + struct llist_head siblings; + + /*! osmo_stream / libosmo-netif handles */ + struct osmo_stream_cli *client; + struct osmo_stream_srv *server; + /*! pre-formatted human readable local/remote socket name */ + char *sock_name; + + /* ASP Identifier for ASP-UP + NTFY */ + uint32_t asp_id; + bool asp_id_present; + + /* Layer Manager to which we talk */ + const struct osmo_xua_layer_manager *lm; + void *lm_priv; + + /*! Were we dynamically allocated */ + bool dyn_allocated; + + /*! Pending message for non-blocking IPA read */ + struct msgb *pending_msg; + + struct { + char *name; + char *description; + enum osmo_ss7_asp_protocol proto; + enum osmo_ss7_asp_admin_state adm_state; + bool is_server; + + struct osmo_ss7_asp_peer local; + struct osmo_ss7_asp_peer remote; + uint8_t qos_class; + } cfg; +}; + +struct osmo_ss7_asp * +osmo_ss7_asp_find_by_name(struct osmo_ss7_instance *inst, const char *name); +struct osmo_ss7_asp +*osmo_ss7_asp_find_by_proto(struct osmo_ss7_as *as, + enum osmo_ss7_asp_protocol proto); +struct osmo_ss7_asp * +osmo_ss7_asp_find_or_create(struct osmo_ss7_instance *inst, const char *name, + uint16_t remote_port, uint16_t local_port, + enum osmo_ss7_asp_protocol proto); +void osmo_ss7_asp_destroy(struct osmo_ss7_asp *asp); +int osmo_ss7_asp_send(struct osmo_ss7_asp *asp, struct msgb *msg); +int osmo_ss7_asp_restart(struct osmo_ss7_asp *asp); +int osmo_ss7_asp_use_default_lm(struct osmo_ss7_asp *asp, int log_level); + +#define LOGPASP(asp, subsys, level, fmt, args ...) \ + LOGP(subsys, level, "asp-%s: " fmt, (asp)->cfg.name, ## args) + +/*********************************************************************** + * xUA Servers + ***********************************************************************/ + +struct osmo_xua_layer_manager { + osmo_prim_cb prim_cb; +}; + +struct osmo_xua_server { + struct llist_head list; + struct osmo_ss7_instance *inst; + + /* list of ASPs established via this server */ + struct llist_head asp_list; + + struct osmo_stream_srv_link *server; + + struct { + bool accept_dyn_reg; + struct osmo_ss7_asp_peer local; + enum osmo_ss7_asp_protocol proto; + } cfg; +}; + +struct osmo_xua_server * +osmo_ss7_xua_server_find(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, + uint16_t local_port); + +struct osmo_xua_server * +osmo_ss7_xua_server_create(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, + uint16_t local_port, const char *local_host); + +int +osmo_ss7_xua_server_set_local_host(struct osmo_xua_server *xs, const char *local_host); + +void osmo_ss7_xua_server_destroy(struct osmo_xua_server *xs); + +struct osmo_sccp_instance * +osmo_sccp_simple_client(void *ctx, const char *name, uint32_t default_pc, + enum osmo_ss7_asp_protocol prot, int default_local_port, + const char *default_local_ip, int default_remote_port, + const char *default_remote_ip); + +struct osmo_sccp_instance * +osmo_sccp_simple_client_on_ss7_id(void *ctx, uint32_t ss7_id, const char *name, + uint32_t default_pc, + enum osmo_ss7_asp_protocol prot, + int default_local_port, + const char *default_local_ip, + int default_remote_port, + const char *default_remote_ip); + +struct osmo_sccp_instance * +osmo_sccp_simple_server(void *ctx, uint32_t pc, + enum osmo_ss7_asp_protocol prot, int local_port, + const char *local_ip); + +struct osmo_sccp_instance * +osmo_sccp_simple_server_on_ss7_id(void *ctx, uint32_t ss7_id, uint32_t pc, + enum osmo_ss7_asp_protocol prot, + int local_port, const char *local_ip); + +struct osmo_sccp_instance * +osmo_sccp_simple_server_add_clnt(struct osmo_sccp_instance *inst, + enum osmo_ss7_asp_protocol prot, + const char *name, uint32_t pc, + int local_port, int remote_port, + const char *remote_ip); + +enum osmo_ss7_as_traffic_mode osmo_ss7_tmode_from_xua(uint32_t in); +int osmo_ss7_tmode_to_xua(enum osmo_ss7_as_traffic_mode tmod); + +/* VTY related */ +struct vty; +void osmo_ss7_vty_init_asp(void *ctx); +void osmo_ss7_vty_init_sg(void *ctx); +int osmo_ss7_vty_go_parent(struct vty *vty); +int osmo_ss7_is_config_node(struct vty *vty, int node); diff --git a/include/osmocom/sigtran/protocol/m3ua.h b/include/osmocom/sigtran/protocol/m3ua.h new file mode 100644 index 0000000..ecbe37b --- /dev/null +++ b/include/osmocom/sigtran/protocol/m3ua.h @@ -0,0 +1,179 @@ +/* RFC 4666 M3UA SCCP User Adaption */ + +/* (C) 2017 by Harald Welte + * + * 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 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, see . + */ + +#pragma once +#include + +#define M3UA_VERSION 1 +#define M3UA_PPID 3 +#define M3UA_PORT 2905 + +/* 3.1.2 Message Classes */ +#define M3UA_MSGC_MGMT 0 +#define M3UA_MSGC_XFER 1 +#define M3UA_MSGC_SNM 2 +#define M3UA_MSGC_ASPSM 3 +#define M3UA_MSGC_ASPTM 4 +#define M3UA_MSGC_RKM 9 + +/* 3.1.3 Message Types */ +#define M3UA_MGMT_ERR 0 +#define M3UA_MGMT_NTFY 1 + +#define M3UA_XFER_DATA 1 + +#define M3UA_SNM_DUNA 1 +#define M3UA_SNM_DAVA 2 +#define M3UA_SNM_DAUD 3 +#define M3UA_SNM_SCON 4 +#define M3UA_SNM_DUPU 5 +#define M3UA_SNM_DRST 6 + +#define M3UA_ASPSM_UP 1 +#define M3UA_ASPSM_DOWN 2 +#define M3UA_ASPSM_BEAT 3 +#define M3UA_ASPSM_UP_ACK 4 +#define M3UA_ASPSM_DOWN_ACK 5 +#define M3UA_ASPSM_BEAT_ACK 6 + +#define M3UA_ASPTM_ACTIVE 1 +#define M3UA_ASPTM_INACTIVE 2 +#define M3UA_ASPTM_ACTIVE_ACK 3 +#define M3UA_ASPTM_INACTIVE_ACK 4 + +#define M3UA_RKM_REG_REQ 1 +#define M3UA_RKM_REG_RSP 2 +#define M3UA_RKM_DEREG_REQ 3 +#define M3UA_RKM_DEREG_RSP 4 + +#define M3UA_IEI_INFO_STRING 0x0004 +#define M3UA_IEI_ROUTE_CTX 0x0006 +#define M3UA_IEI_DIAG_INFO 0x0007 +#define M3UA_IEI_HEARDBT_DATA 0x0009 +#define M3UA_IEI_TRAF_MODE_TYP 0x000b +#define M3UA_IEI_ERR_CODE 0x000c +#define M3UA_IEI_STATUS 0x000d +#define M3UA_IEI_ASP_ID 0x0011 +#define M3UA_IEI_AFFECTED_PC 0x0012 +#define M3UA_IEI_CORR_ID 0x0013 + +/* 3.2 M3UA specific parameters */ + +#define M3UA_IEI_NET_APPEAR 0x0200 +#define M3UA_IEI_USER_CAUSE 0x0204 +#define M3UA_IEI_CONG_IND 0x0205 +#define M3UA_IEI_CONC_DEST 0x0206 +#define M3UA_IEI_ROUT_KEY 0x0207 +#define M3UA_IEI_REG_RESULT 0x0208 +#define M3UA_IEI_DEREG_RESULT 0x0209 +#define M3UA_IEI_LOC_RKEY_ID 0x020a +#define M3UA_IEI_DEST_PC 0x020b +#define M3UA_IEI_SVC_IND 0x020c +#define M3UA_IEI_ORIG_PC 0x020e +#define M3UA_IEI_PROT_DATA 0x0210 +#define M3UA_IEI_REG_STATUS 0x0212 +#define M3UA_IEI_DEREG_STATUS 0x0213 + +/* 3.3.1 Payload Data Message */ +struct m3ua_data_hdr { + uint32_t opc; /* Originating Point Code */ + uint32_t dpc; /* Destination Point Code */ + uint8_t si; /* Service Indicator */ + uint8_t ni; /* Network Indicator */ + uint8_t mp; /* Message Priority */ + uint8_t sls; /* Signalling Link Selection */ +} __attribute__ ((packed)); + +/* 3.8.2 Notify */ + +#define M3UA_NOTIFY(type, info) ((info) << 16 | (type)) +#define M3UA_NOTIFY_T_STATCHG 1 +#define M3UA_NOTIFY_T_OTHER 2 + +#define M3UA_NOTIFY_I_RESERVED 1 +#define M3UA_NOTIFY_I_AS_INACT 2 +#define M3UA_NOTIFY_I_AS_ACT 3 +#define M3UA_NOTIFY_I_AS_PEND 4 + +#define M3UA_NOTIFY_I_OT_INS_RES 1 +#define M3UA_NOTIFY_I_OT_ALT_ASP_ACT 2 +#define M3UA_NOTIFY_I_OT_ASP_FAILURE 3 + +/* 3.6.2 Registration Status */ +enum m3ua_rkm_reg_status { + M3UA_RKM_REG_SUCCESS = 0, + M3UA_RKM_REG_ERR_UNKNOWN = 1, + M3UA_RKM_REG_ERR_INVAL_DPC = 2, + M3UA_RKM_REG_ERR_INVAL_NET_APPEAR = 3, + M3UA_RKM_REG_ERR_INVAL_RKEY = 4, + M3UA_RKM_REG_ERR_PERM_DENIED = 5, + M3UA_RKM_REG_ERR_CANT_SUPP_UNQ_RT = 6, + M3UA_RKM_REG_ERR_RKEY_NOT_PROVD = 7, + M3UA_RKM_REG_ERR_INSUFF_RESRC = 8, + M3UA_RKM_REG_ERR_UNSUPP_RK_PARAM = 9, + M3UA_RKM_REG_ERR_UNSUPP_TRAF_MODE = 10, + M3UA_RKM_REG_ERR_RKEY_CHG_REFUSED = 11, + M3UA_RKM_REG_ERR_RKEY_ALRDY_REGD = 12, +}; + +enum m3ua_rkm_dereg_satus { + M3UA_RKM_DEREG_SUCCESS = 0, + M3UA_RKM_DEREG_ERR_UNKNOWN = 1, + M3UA_RKM_DEREG_ERR_INVAL_RCTX = 2, + M3UA_RKM_DEREG_ERR_PERM_DENIED = 3, + M3UA_RKM_DEREG_ERR_NOT_REGD = 4, + M3UA_RKM_DEREG_ERR_ASP_ACTIVE = 5, +}; + +/* 3.8.1 Error */ +enum m3ua_error_code { + M3UA_ERR_INVALID_VERSION = 0x01, + /* not used in M3UA */ + M3UA_ERR_UNSUPP_MSG_CLASS = 0x03, + M3UA_ERR_UNSUPP_MSG_TYPE = 0x04, + M3UA_ERR_UNSUPP_TRAF_MOD_TYP = 0x05, + M3UA_ERR_UNEXPECTED_MSG = 0x06, + M3UA_ERR_PROTOCOL_ERR = 0x07, + /* not used in M3UA */ + M3UA_ERR_INVAL_STREAM_ID = 0x09, + /* not used in M3UA */ + /* not used in M3UA */ + /* not used in M3UA */ + M3UA_ERR_REFUSED_MGMT_BLOCKING = 0x0d, + M3UA_ERR_ASP_ID_REQD = 0x0e, + M3UA_ERR_INVAL_ASP_ID = 0x0f, + /* not used in M3UA */ + M3UA_ERR_INVAL_PARAM_VAL = 0x11, + M3UA_ERR_PARAM_FIELD_ERR = 0x12, + M3UA_ERR_UNEXP_PARAM = 0x13, + M3UA_ERR_DEST_STATUS_UNKN = 0x14, + M3UA_ERR_INVAL_NET_APPEAR = 0x15, + M3UA_ERR_MISSING_PARAM = 0x16, + /* not used in M3UA */ + /* not used in M3UA */ + M3UA_ERR_INVAL_ROUT_CTX = 0x19, + M3UA_ERR_NO_CONFGD_AS_FOR_ASP = 0x1a, +}; + +enum m3ua_traffic_mode { + M3UA_TMOD_OVERRIDE = 1, + M3UA_TMOD_LOADSHARE = 2, + M3UA_TMOD_BCAST = 3, +}; diff --git a/include/osmocom/sigtran/protocol/mtp.h b/include/osmocom/sigtran/protocol/mtp.h new file mode 100644 index 0000000..8b990c0 --- /dev/null +++ b/include/osmocom/sigtran/protocol/mtp.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +/* Chapter 15.17.4 of Q.704 + RFC4666 3.4.5. */ +/* Section 5.1 of ETSI EG 201 693: MTP SI code allocations (for NI= 00) */ +enum mtp_si_ni00 { + MTP_SI_SNM = 0, + MTP_SI_STM = 1, + MTP_SI_SCCP = 3, + MTP_SI_TUP = 4, + MTP_SI_ISUP = 5, + MTP_SI_DUP = 6, /* call related */ + MTP_SI_DUP_FAC = 7, /* facility related */ + MTP_SI_TESTING = 8, + MTP_SI_B_ISUP = 9, + MTP_SI_SAT_ISUP = 10, + MTP_SI_SPEECH = 11, /* speech processing element */ + MTP_SI_AAL2_SIG = 12, + MTP_SI_BICC = 13, + MTP_SI_GCP = 14, +}; + +extern const struct value_string mtp_si_vals[]; diff --git a/include/osmocom/sigtran/protocol/sua.h b/include/osmocom/sigtran/protocol/sua.h new file mode 100644 index 0000000..70c16ba --- /dev/null +++ b/include/osmocom/sigtran/protocol/sua.h @@ -0,0 +1,160 @@ +/* RFC 3868 SUA SCCP User Adaption */ + +/* (C) 2012 by Harald Welte + * + * 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 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, see . + */ + +#pragma once +#include + +#include + +#define SUA_VERSION 1 +#define SUA_PPID 4 +#define SUA_PORT 14001 + +/* 3.1.2 Message Classes */ +#define SUA_MSGC_MGMT 0 +#define SUA_MSGC_SNM 2 +#define SUA_MSGC_ASPSM 3 +#define SUA_MSGC_ASPTM 4 +#define SUA_MSGC_CL 7 +#define SUA_MSGC_CO 8 +#define SUA_MSGC_RKM 9 + +/* 3.1.3 Message Types */ +#define SUA_MGMT_ERR 0 +#define SUA_MGMT_NTFY 1 + +#define SUA_SNM_DUNA 1 +#define SUA_SNM_DAVA 2 +#define SUA_SNM_DAUD 3 +#define SUA_SNM_SCON 4 +#define SUA_SNM_DUPU 5 +#define SUA_SNM_DRST 6 + +#define SUA_ASPSM_UP 1 +#define SUA_ASPSM_DOWN 2 +#define SUA_ASPSM_BEAT 3 +#define SUA_ASPSM_UP_ACK 4 +#define SUA_ASPSM_DOWN_ACK 5 +#define SUA_ASPSM_BEAT_ACK 6 + +#define SUA_ASPTM_ACTIVE 1 +#define SUA_ASPTM_INACTIVE 2 +#define SUA_ASPTM_ACTIVE_ACK 3 +#define SUA_ASPTM_INACTIVE_ACK 4 + +#define SUA_RKM_REG_REQ 1 +#define SUA_RKM_REG_RSP 2 +#define SUA_RKM_DEREG_REQ 3 +#define SUA_RKM_DEREG_RSP 4 + +#define SUA_CL_CLDT 1 +#define SUA_CL_CLDR 2 + +#define SUA_CO_CORE 1 +#define SUA_CO_COAK 2 +#define SUA_CO_COREF 3 +#define SUA_CO_RELRE 4 +#define SUA_CO_RELCO 5 +#define SUA_CO_RESCO 6 +#define SUA_CO_RESRE 7 +#define SUA_CO_CODT 8 +#define SUA_CO_CODA 9 +#define SUA_CO_COERR 10 +#define SUA_CO_COIT 11 /* Connection Oriented Inactiviy Test */ + +#define SUA_IEI_INFO_STRING M3UA_IEI_INFO_STRING +#define SUA_IEI_ROUTE_CTX M3UA_IEI_ROUTE_CTX +#define SUA_IEI_DIAG_INFO M3UA_IEI_DIAG_INFO +#define SUA_IEI_HEARTBT_DATA M3UA_IEI_HEARDBT_DATA +#define SUA_IEI_TRAF_MODE_TYP M3UA_IEI_TRAF_MODE_TYP +#define SUA_IEI_ERR_CODE M3UA_IEI_ERR_CODE +#define SUA_IEI_STATUS M3UA_IEI_STATUS +#define SUA_IEI_ASP_ID M3UA_IEI_ASP_ID +#define SUA_IEI_AFFECTED_PC M3UA_IEI_AFFECTED_PC +#define SUA_IEI_CORR_ID M3UA_IEI_CORR_ID +#define SUA_IEI_REG_RESULT 0x0014 +#define SUA_IEI_DEREG_RESULT 0x0015 + +/* 3.10 SUA specific parameters */ + +#define SUA_IEI_S7_HOP_CTR 0x0101 +#define SUA_IEI_SRC_ADDR 0x0102 +#define SUA_IEI_DEST_ADDR 0x0103 +#define SUA_IEI_SRC_REF 0x0104 +#define SUA_IEI_DEST_REF 0x0105 +#define SUA_IEI_CAUSE 0x0106 +#define SUA_IEI_SEQ_NR 0x0107 +#define SUA_IEI_RX_SEQ_NR 0x0108 +#define SUA_IEI_ASP_CAPA 0x0109 +#define SUA_IEI_CREDIT 0x010A +#define SUA_IEI_DATA 0x010B +#define SUA_IEI_USER_CAUSE 0x010C +#define SUA_IEI_NET_APPEARANCE 0x010D +#define SUA_IEI_ROUTING_KEY 0x010E +#define SUA_IEI_DRN 0x010F +#define SUA_IEI_TID 0x0110 +#define SUA_IEI_SMI 0x0112 +#define SUA_IEI_IMPORTANCE 0x0113 +#define SUA_IEI_MSG_PRIO 0x0114 +#define SUA_IEI_PROTO_CLASS 0x0115 +#define SUA_IEI_SEQ_CTRL 0x0116 +#define SUA_IEI_SEGMENTATION 0x0117 +#define SUA_IEI_CONG_LEVEL 0x0118 + +#define SUA_IEI_GT 0x8001 +#define SUA_IEI_PC 0x8002 +#define SUA_IEI_SSN 0x8003 +#define SUA_IEI_IPv4 0x8004 +#define SUA_IEI_HOST 0x8005 +#define SUA_IEI_IPv6 0x8006 + +#define SUA_RI_GT 1 +#define SUA_RI_SSN_PC 2 +#define SUA_RI_HOST 3 +#define SUA_RI_SSN_IP 4 + +#define SUA_CAUSE_T_MASK 0xff00 +#define SUA_CAUSE_T_RETURN 0x0100 +#define SUA_CAUSE_T_REFUSAL 0x0200 +#define SUA_CAUSE_T_RELEASE 0x0300 +#define SUA_CAUSE_T_RESET 0x0400 +#define SUA_CAUSE_T_ERROR 0x0500 + +/* 3.9.12 Error: Identical to M3UA, extended by two at the bottom */ +#define SUA_ERR_INVALID_VERSION M3UA_ERR_INVALID_VERSION +#define SUA_ERR_UNSUPP_MSG_CLASS M3UA_ERR_UNSUPP_MSG_CLASS +#define SUA_ERR_UNSUPP_MSG_TYPE M3UA_ERR_UNSUPP_MSG_TYPE +#define SUA_ERR_UNSUPP_TRAF_MOD_TYP M3UA_ERR_UNSUPP_TRAF_MOD_TYP +#define SUA_ERR_UNEXPECTED_MSG M3UA_ERR_UNEXPECTED_MSG +#define SUA_ERR_PROTOCOL_ERR M3UA_ERR_PROTOCOL_ERR +#define SUA_ERR_INVAL_STREAM_ID M3UA_ERR_INVAL_STREAM_ID +#define SUA_ERR_REFUSED_MGMT_BLOCKING M3UA_ERR_REFUSED_MGMT_BLOCKING +#define SUA_ERR_ASP_ID_REQD M3UA_ERR_ASP_ID_REQD +#define SUA_ERR_INVAL_ASP_ID M3UA_ERR_INVAL_ASP_ID +#define SUA_ERR_INVAL_PARAM_VAL M3UA_ERR_INVAL_PARAM_VAL +#define SUA_ERR_PARAM_FIELD_ERR M3UA_ERR_PARAM_FIELD_ERR +#define SUA_ERR_UNEXP_PARAM M3UA_ERR_UNEXP_PARAM +#define SUA_ERR_DEST_STATUS_UNKN M3UA_ERR_DEST_STATUS_UNKN +#define SUA_ERR_INVAL_NET_APPEAR M3UA_ERR_INVAL_NET_APPEAR +#define SUA_ERR_MISSING_PARAM M3UA_ERR_MISSING_PARAM +#define SUA_ERR_INVAL_ROUT_CTX M3UA_ERR_INVAL_ROUT_CTX +#define SUA_ERR_NO_CONFGD_AS_FOR_ASP M3UA_ERR_NO_CONFGD_AS_FOR_ASP +#define SUA_ERR_SUBSYS_STATUS_UNKN 0x1b +#define SUA_ERR_INVAL_LOADSH_LEVEL 0x1c diff --git a/include/osmocom/sigtran/sccp_helpers.h b/include/osmocom/sigtran/sccp_helpers.h new file mode 100644 index 0000000..c2175f4 --- /dev/null +++ b/include/osmocom/sigtran/sccp_helpers.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +int osmo_sccp_tx_unitdata(struct osmo_sccp_user *scu, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + const uint8_t *data, unsigned int len); + +int osmo_sccp_tx_unitdata_msg(struct osmo_sccp_user *scu, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + struct msgb *msg); + +void osmo_sccp_make_addr_pc_ssn(struct osmo_sccp_addr *addr, + uint32_t pc, uint32_t ssn); + +void osmo_sccp_addr_set_ssn(struct osmo_sccp_addr *addr, uint32_t ssn); + +int osmo_sccp_tx_unitdata_ranap(struct osmo_sccp_user *scu, + uint32_t src_point_code, + uint32_t dst_point_code, + const uint8_t *data, unsigned int len); + +int osmo_sccp_tx_conn_req(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + const uint8_t *data, unsigned int len); + +int osmo_sccp_tx_conn_req_msg(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + struct msgb *msg); + +int osmo_sccp_tx_data(struct osmo_sccp_user *scu, uint32_t conn_id, + const uint8_t *data, unsigned int len); + +int osmo_sccp_tx_data_msg(struct osmo_sccp_user *scu, uint32_t conn_id, + struct msgb *msg); + +int osmo_sccp_tx_disconn(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *resp_addr, + uint32_t cause); + +int osmo_sccp_tx_conn_resp_msg(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *resp_addr, + struct msgb *msg); + +int osmo_sccp_tx_conn_resp(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *resp_addr, + const uint8_t *data, unsigned int len); + +char *osmo_sccp_gt_dump(const struct osmo_sccp_gt *gt); +char *osmo_sccp_addr_dump(const struct osmo_sccp_addr *addr); +char *osmo_sccp_addr_name(const struct osmo_ss7_instance *ss7, const struct osmo_sccp_addr *addr); diff --git a/include/osmocom/sigtran/sccp_sap.h b/include/osmocom/sigtran/sccp_sap.h new file mode 100644 index 0000000..7a4f9bf --- /dev/null +++ b/include/osmocom/sigtran/sccp_sap.h @@ -0,0 +1,277 @@ +#pragma once + +/* SCCP User SAP description */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include + +#include + +/* detailed coding of primitives at the SAP_SCCP_USER */ + +/*! \brief SCCP-User primitives as per Q.711 */ +enum osmo_scu_prim_type { + /* connection oriented, 6.1.1 */ + OSMO_SCU_PRIM_N_CONNECT, + OSMO_SCU_PRIM_N_DATA, + OSMO_SCU_PRIM_N_EXPEDITED_DATA, + OSMO_SCU_PRIM_N_DISCONNECT, + OSMO_SCU_PRIM_N_RESET, + OSMO_SCU_PRIM_N_INFORM, + /* connectionless, 6.2.2 */ + OSMO_SCU_PRIM_N_UNITDATA, + OSMO_SCU_PRIM_N_NOTICE, + /* management */ + OSMO_SCU_PRIM_N_COORD, + OSMO_SCU_PRIM_N_STATE, + OSMO_SCU_PRIM_N_PCSTATE, +}; + +#define OSMO_SCCP_ADDR_T_GT 0x0001 /* global title */ +#define OSMO_SCCP_ADDR_T_PC 0x0002 /* signalling point code */ +#define OSMO_SCCP_ADDR_T_SSN 0x0004 /* subsystem number */ +#define OSMO_SCCP_ADDR_T_IPv4 0x0008 +#define OSMO_SCCP_ADDR_T_IPv6 0x0010 + +/* Q.713 3.4.1 + RFC 3868 3.10.2.3 */ +enum osmo_sccp_routing_ind { + OSMO_SCCP_RI_NONE, + OSMO_SCCP_RI_GT, + OSMO_SCCP_RI_SSN_PC, + OSMO_SCCP_RI_SSN_IP, +}; + +extern const struct value_string osmo_sccp_routing_ind_names[]; +static inline const char *osmo_sccp_routing_ind_name(enum osmo_sccp_routing_ind val) +{ return get_value_string(osmo_sccp_routing_ind_names, val); } + + +/* Q.713 3.4.1 + RFC 3868 3.10.2.3 */ +enum osmo_sccp_gti { + OSMO_SCCP_GTI_NO_GT, + OSMO_SCCP_GTI_NAI_ONLY, + OSMO_SCCP_GTI_TT_ONLY, + OSMO_SCCP_GTI_TT_NPL_ENC, + OSMO_SCCP_GTI_TT_NPL_ENC_NAI, +}; + +extern const struct value_string osmo_sccp_gti_names[]; +static inline const char *osmo_sccp_gti_name(enum osmo_sccp_gti val) +{ return get_value_string(osmo_sccp_gti_names, val); } + +/* RFC 3868 3.10.2.3 */ +enum osmo_sccp_npi { + OSMO_SCCP_NPI_UNKNOWN = 0, + OSMO_SCCP_NPI_E164_ISDN = 1, + OSMO_SCCP_NPI_GEERIC = 2, + OSMO_SCCP_NPI_X121_DATA = 3, + OSMO_SCCP_NPI_F69_TELEX = 4, + OSMO_SCCP_NPI_E210_MARITIME = 5, + OSMO_SCCP_NPI_E212_LAND = 6, + OSMO_SCCP_NPI_E214_ISDN_MOBILE = 7, + OSMO_SCCP_NPI_PRIVATE = 14, +}; + +/* Q.713 3.4.2.3.1 + RFC 3868 3.10.2.3 */ +enum osmo_sccp_nai { + OSMO_SCCP_NAI_UNKNOWN = 0, + OSMO_SCCP_NAI_SUBSCR = 1, + OSMO_SCCP_NAI_RES_NAT_USE = 2, + OSMO_SCCP_NAI_NATL = 3, + OSMO_SCCP_NAI_INTL = 4, + /* 5.. 255: Spare */ +}; + +/* Q.713 3.4.2.2 */ +enum osmo_sccp_ssn { + /* globally standardised for GSM/UMTS: 1-31 */ + OSMO_SCCP_SSN_MGMT = 1, + OSMO_SCCP_SSN_ISUP = 3, + OSMO_SCCP_SSN_OMAP = 4, + OSMO_SCCP_SSN_MAP = 5, + OSMO_SCCP_SSN_HLR = 6, + OSMO_SCCP_SSN_VLR = 7, + OSMO_SCCP_SSN_MSC = 8, + OSMO_SCCP_SSN_EIR = 9, + OSMO_SCCP_SSN_AUC = 0x0a, + /* Q.713 */ + OSMO_SCCP_SSN_ISDN_SS = 0x0b, + OSMO_SCCP_SSN_RES_INTL = 0x0c, + OSMO_SCCP_SSN_BISDN = 0x0d, + OSMO_SCCP_SSN_TC_TEST = 0x0e, + /* national network SSN for wihin and between GSM/UMTS: 129-150 */ + OSMO_SCCP_SSN_RANAP = 142, + OSMO_SCCP_SSN_RNSAP = 143, + OSMO_SCCP_SSN_GMLC_MAP = 145, + OSMO_SCCP_SSN_CAP = 146, + OSMO_SCCP_SSN_gsmSCF_MAP = 147, + OSMO_SCCP_SSN_SIWF_MAP = 148, + OSMO_SCCP_SSN_SGSN_MAP = 149, + OSMO_SCCP_SSN_GGSN_MAP = 150, + /* national network SSN within GSM/UMTS: 32-128 + 151-254 */ + OSMO_SCCP_SSN_PCAP = 249, + OSMO_SCCP_SSN_BSC_BSSAP_LE = 250, + OSMO_SCCP_SSN_MSC_BSSAP_LE = 251, + OSMO_SCCP_SSN_SMLC_BSSAP = 252, + OSMO_SCCP_SSN_BSS_OAM = 253, + OSMO_SCCP_SSN_BSSAP = 254, +}; + +extern const struct value_string osmo_sccp_ssn_names[]; +static inline const char *osmo_sccp_ssn_name(enum osmo_sccp_ssn val) +{ return get_value_string(osmo_sccp_ssn_names, val); } + +struct osmo_sccp_gt { + uint8_t gti; + uint8_t tt; + uint32_t npi; + uint32_t nai; + char digits[32]; +}; + +struct osmo_sccp_addr { + uint32_t presence; + enum osmo_sccp_routing_ind ri; + struct osmo_sccp_gt gt; + uint32_t pc; + uint32_t ssn; + union { + struct in_addr v4; + struct in6_addr v6; + } ip; + /* we don't do hostnames */ +}; + +/* OSMO_SCU_PRIM_N_CONNECT */ +struct osmo_scu_connect_param { + struct osmo_sccp_addr called_addr; + struct osmo_sccp_addr calling_addr; + struct osmo_sccp_addr responding_addr; + //struct osmo_sccp_qos_pars qos_pars; + uint32_t sccp_class; + uint32_t importance; + uint32_t conn_id; + /* user data */ +}; + +/* OSMO_SCU_PRIM_N_DATA / OSMO_SCU_PRIM_N_EXPEDITED_DATA */ +struct osmo_scu_data_param { + uint32_t conn_id; + uint32_t importance; + /* user data */ +}; + +enum osmo_sccp_originator { + OSMO_SCCP_ORIG_NS_PROVIDER, + OSMO_SCCP_ORIG_NS_USER, + OSMO_SCCP_ORIG_UNDEFINED, +}; + +/* OSMO_SCU_PRIM_N_DISCONNECT */ +struct osmo_scu_disconn_param { + enum osmo_sccp_originator originator; + struct osmo_sccp_addr responding_addr; + uint32_t cause; + uint32_t conn_id; + uint32_t importance; + /* user data */ +}; + +/* OSMO_SCU_PRIM_N_RESET */ +struct osmo_scu_reset_param { + enum osmo_sccp_originator originator; + uint32_t cause; + uint32_t conn_id; +}; + +/* OSMO_SCU_PRIM_N_UNITDATA */ +struct osmo_scu_unitdata_param { + struct osmo_sccp_addr called_addr; + struct osmo_sccp_addr calling_addr; + uint32_t in_sequence_control; + uint32_t return_option; + uint32_t importance; + /* user data */ +}; + +/* OSMO_SCU_PRIM_N_NOTICE */ +struct osmo_scu_notice_param { + struct osmo_sccp_addr called_addr; + struct osmo_sccp_addr calling_addr; + uint32_t cause; + uint32_t importance; + /* user data */ +}; + +struct osmo_scu_prim { + struct osmo_prim_hdr oph; + union { + struct osmo_scu_connect_param connect; + struct osmo_scu_data_param data; + struct osmo_scu_disconn_param disconnect; + struct osmo_scu_reset_param reset; + struct osmo_scu_unitdata_param unitdata; + struct osmo_scu_notice_param notice; + } u; +}; + +#define msgb_scu_prim(msg) ((struct osmo_scu_prim *)(msg)->l1h) + +char *osmo_scu_prim_name(struct osmo_prim_hdr *oph); + +struct osmo_ss7_instance; +struct osmo_sccp_instance; +struct osmo_sccp_user; + +void osmo_sccp_vty_init(void); + +struct osmo_sccp_instance * +osmo_sccp_instance_create(struct osmo_ss7_instance *ss7, void *priv); +void osmo_sccp_instance_destroy(struct osmo_sccp_instance *inst); +struct osmo_ss7_instance *osmo_sccp_get_ss7(const struct osmo_sccp_instance *sccp); +struct osmo_sccp_instance *osmo_sccp_get_sccp(const struct osmo_sccp_user *scu); + +void osmo_sccp_user_unbind(struct osmo_sccp_user *scu); +void osmo_sccp_user_set_priv(struct osmo_sccp_user *scu, void *priv); +void *osmo_sccp_user_get_priv(struct osmo_sccp_user *scu); + +struct osmo_sccp_user * +osmo_sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc); + +struct osmo_sccp_user * +osmo_sccp_user_bind(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn); + +int osmo_sccp_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph); + +struct osmo_ss7_instance * +osmo_sccp_addr_by_name(struct osmo_sccp_addr *dest_addr, + const char *name); + +const char *osmo_sccp_name_by_addr(const struct osmo_sccp_addr *addr); + +void osmo_sccp_local_addr_by_instance(struct osmo_sccp_addr *dest_addr, + const struct osmo_sccp_instance *inst, + uint32_t ssn); + +bool osmo_sccp_check_addr(struct osmo_sccp_addr *addr, uint32_t presence); diff --git a/include/osmocom/sigtran/sigtran_sap.h b/include/osmocom/sigtran/sigtran_sap.h new file mode 100644 index 0000000..87504c8 --- /dev/null +++ b/include/osmocom/sigtran/sigtran_sap.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include + + +enum osmo_sigtran_sap { + SCCP_SAP_USER = _SAP_SS7_BASE, + /* xUA Layer Manager */ + XUA_SAP_LM, + MTP_SAP_USER, +}; + +enum osmo_xlm_prim_type { + OSMO_XLM_PRIM_M_SCTP_ESTABLISH, + OSMO_XLM_PRIM_M_SCTP_RELEASE, + OSMO_XLM_PRIM_M_SCTP_RESTART, + OSMO_XLM_PRIM_M_SCTP_STATUS, + OSMO_XLM_PRIM_M_ASP_STATUS, + OSMO_XLM_PRIM_M_AS_STATUS, + OSMO_XLM_PRIM_M_NOTIFY, + OSMO_XLM_PRIM_M_ERROR, + OSMO_XLM_PRIM_M_ASP_UP, + OSMO_XLM_PRIM_M_ASP_DOWN, + OSMO_XLM_PRIM_M_ASP_ACTIVE, + OSMO_XLM_PRIM_M_ASP_INACTIVE, + OSMO_XLM_PRIM_M_AS_ACTIVE, + OSMO_XLM_PRIM_M_AS_INACTIVE, + OSMO_XLM_PRIM_M_AS_DOWN, + /* optional as per spec, not implemented yet */ + OSMO_XLM_PRIM_M_RK_REG, + OSMO_XLM_PRIM_M_RK_DEREG, +}; + +#define NOTIFY_PAR_P_ASP_ID (1 << 0) +#define NOTIFY_PAR_P_ROUTE_CTX (1 << 1) + +struct osmo_xlm_prim_notify { + uint32_t presence; + uint16_t status_type; + uint16_t status_info; + uint32_t asp_id; + uint32_t route_ctx; + char *info_string; +}; + +struct osmo_xlm_prim_error { + uint32_t code; +}; + +struct osmo_xlm_prim_rk_reg { + /* routing key */ + struct osmo_ss7_routing_key key; + enum osmo_ss7_as_traffic_mode traf_mode; + + /* Status: Confirm only */ + uint32_t status; +}; + +struct osmo_xlm_prim_rk_dereg { + uint32_t route_ctx; + + /* Status: Confirm only */ + uint32_t status; +}; + +struct osmo_xlm_prim { + struct osmo_prim_hdr oph; + union { + struct osmo_xlm_prim_notify notify; + struct osmo_xlm_prim_error error; + struct osmo_xlm_prim_rk_reg rk_reg; + struct osmo_xlm_prim_rk_dereg rk_dereg; + } u; +}; + +#define msgb_xlm_prim(msg) ((struct osmo_xlm_prim *)(msg)->l1h) + +char *osmo_xlm_prim_name(struct osmo_prim_hdr *oph); + +/* XUA LM-SAP towards stack */ +int osmo_xlm_sap_down(struct osmo_ss7_asp *asp, struct osmo_prim_hdr *oph); diff --git a/include/osmocom/sigtran/xua_msg.h b/include/osmocom/sigtran/xua_msg.h new file mode 100644 index 0000000..423adbc --- /dev/null +++ b/include/osmocom/sigtran/xua_msg.h @@ -0,0 +1,103 @@ +/* Routines for generating and parsing messages */ +/* (C) 2011 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 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, see . + * + */ +#pragma once + +#include "xua_types.h" + +#include +#include + +#define XUA_HDR(class, type) ((struct xua_common_hdr) { .spare = 0, .msg_class = (class), .msg_type = (type) }) + +struct msgb; +struct osmo_sccp_addr; +struct osmo_sccp_gt; + +struct xua_msg { + struct xua_common_hdr hdr; + struct osmo_mtp_transfer_param mtp; + + struct llist_head headers; +}; + +struct xua_msg_part { + struct llist_head entry; + + uint16_t tag; + uint16_t len; + uint8_t *dat; + + /* TODO: keep small data in the struct for perf reasons */ +}; + +struct xua_msg_class { + const char *name; + const struct value_string *msgt_names; + const struct value_string *iei_names; + const uint16_t *mand_ies[256]; +}; + +struct xua_dialect { + const char *name; + uint16_t port; + uint16_t ppid; + int log_subsys; + const struct xua_msg_class *class[256]; +}; + +struct xua_msg_event_map { + uint8_t msg_class; + uint8_t msg_type; + int event; +}; + +extern const struct xua_dialect xua_dialect_sua; +extern const struct xua_dialect xua_dialect_m3ua; + +struct xua_msg *xua_msg_alloc(void); +void xua_msg_free(struct xua_msg *msg); + +int xua_msg_add_data(struct xua_msg *msg, uint16_t tag, uint16_t len, uint8_t *dat); + +struct xua_msg_part *xua_msg_find_tag(const struct xua_msg *msg, uint16_t tag); +int xua_msg_free_tag(struct xua_msg *xua, uint16_t tag); +int xua_msg_copy_part(struct xua_msg *xua_out, uint16_t tag_out, + const struct xua_msg *xua_in, uint16_t tag_in); + +struct xua_msg *xua_from_msg(const int version, uint16_t len, uint8_t *data); +struct msgb *xua_to_msg(const int version, struct xua_msg *msg); + +struct xua_msg *xua_from_nested(struct xua_msg_part *outer); + +int msgb_t16l16vp_put(struct msgb *msg, uint16_t tag, uint16_t len, const uint8_t *data); +int msgb_t16l16vp_put_u32(struct msgb *msg, uint16_t tag, uint32_t val); +int xua_msg_add_u32(struct xua_msg *xua, uint16_t iei, uint32_t val); +uint32_t xua_msg_part_get_u32(struct xua_msg_part *part); +uint32_t xua_msg_get_u32(struct xua_msg *xua, uint16_t iei); +void xua_part_add_gt(struct msgb *msg, const struct osmo_sccp_gt *gt); +int xua_msg_add_sccp_addr(struct xua_msg *xua, uint16_t iei, const struct osmo_sccp_addr *addr); + +const char *xua_class_msg_name(const struct xua_msg_class *xmc, uint16_t msg_type); +const char *xua_class_iei_name(const struct xua_msg_class *xmc, uint16_t iei); +char *xua_hdr_dump(struct xua_msg *xua, const struct xua_dialect *dialect); +char *xua_msg_dump(struct xua_msg *xua, const struct xua_dialect *dialect); +int xua_dialect_check_all_mand_ies(const struct xua_dialect *dialect, struct xua_msg *xua); + +int xua_msg_event_map(const struct xua_msg *xua, + const struct xua_msg_event_map *maps, + unsigned int num_maps); diff --git a/include/osmocom/sigtran/xua_types.h b/include/osmocom/sigtran/xua_types.h new file mode 100644 index 0000000..6568283 --- /dev/null +++ b/include/osmocom/sigtran/xua_types.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +/** + * Common tag values used by all user adaption layers + */ +enum { + MUA_TAG_RESERVED, /* Reserved */ + MUA_TAG_IDENT_INT, /* Interface Identifier (Integer) (M2UA) */ + MUA_TAG_UNUSED1, /* Unused */ + MUA_TAG_IDENT_TEXT, /* Interface Identifier (Text) (M2UA) */ + MUA_TAG_INFO, /* Info String */ + MUA_TAG_UNUSED2, /* Unused */ + MUA_TAG_ROUTING_CTX, /* Routing Context (M3UA) */ + MUA_TAG_DIAG_INF, /* Diagnostic Information */ + MUA_TAG_IDENT_RANGE, /* Interface Identifier (Integer Range) */ + MUA_TAG_BEAT_DATA, /* Heartbeat Data */ + MUA_TAG_UNUSED4, /* Unused */ + MUA_TAG_TRA_MODE, /* Traffic Mode Type */ + MUA_TAG_ERR_CODE, /* Error Code */ + MUA_TAG_STATUS, /* Status Type/Information */ + MUA_TAG_UNUSED5, /* Unused */ + MUA_TAG_UNUSED6, /* Unused */ + MUA_TAG_UNUSED7, /* Unused */ + MUA_TAG_ASP_IDENT, /* ASP Identifier */ + MUA_TAG_AFF_PC, /* Affected Point Code (M3UA) */ + MUA_TAG_CORREL_ID, /* Correlation Id */ +}; + +struct xua_common_hdr { + uint8_t version; + uint8_t spare; + uint8_t msg_class; + uint8_t msg_type; + uint32_t msg_length; + uint8_t data[0]; +} __attribute__((packed)); + + +struct xua_parameter_hdr { + uint16_t tag; + uint16_t len; + uint8_t data[0]; +} __attribute__((packed)); diff --git a/include/sccp/Makefile.am b/include/sccp/Makefile.am new file mode 100644 index 0000000..c64db26 --- /dev/null +++ b/include/sccp/Makefile.am @@ -0,0 +1,2 @@ +sccp_HEADERS = sccp_types.h sccp.h +sccpdir = $(includedir)/osmocom/sccp diff --git a/include/sccp/sccp.h b/include/sccp/sccp.h new file mode 100644 index 0000000..4293792 --- /dev/null +++ b/include/sccp/sccp.h @@ -0,0 +1,204 @@ +/* + * SCCP management code + * + * (C) 2009, 2010, 2013 by Holger Hans Peter Freyther + * (C) 2009, 2010, 2013 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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef SCCP_H +#define SCCP_H + +#include + +#include +#include + +#include + +#include "sccp_types.h" + +struct msgb; +struct sccp_system; + +enum { + SCCP_CONNECTION_STATE_NONE, + SCCP_CONNECTION_STATE_REQUEST, + SCCP_CONNECTION_STATE_CONFIRM, + SCCP_CONNECTION_STATE_ESTABLISHED, + SCCP_CONNECTION_STATE_RELEASE, + SCCP_CONNECTION_STATE_RELEASE_COMPLETE, + SCCP_CONNECTION_STATE_REFUSED, + SCCP_CONNECTION_STATE_SETUP_ERROR, +}; + +struct sockaddr_sccp { + sa_family_t sccp_family; /* AF_SCCP in the future??? */ + uint8_t sccp_ssn; /* subssystem number for routing */ + + /* TODO fill in address indicator... if that is ever needed */ + + /* optional gti information */ + uint8_t *gti; + int gti_len; + + /* any of SCCP_TITLE_IND_* */ + uint8_t gti_ind; + + int use_poi; + uint8_t poi[2]; + + /* not sure about these */ + /* uint8_t sccp_class; */ +}; + +/* + * parsed structure of an address + */ +struct sccp_address { + struct sccp_called_party_address address; + uint8_t ssn; + uint8_t poi[2]; + + uint8_t *gti_data; + int gti_len; +}; + +struct sccp_optional_data { + uint8_t data_len; + uint8_t data_start; +}; + +struct sccp_connection { + /* public */ + void *data_ctx; + void (*data_cb)(struct sccp_connection *conn, struct msgb *msg, unsigned int len); + + void *state_ctx; + void (*state_cb)(struct sccp_connection *, int old_state); + + struct sccp_source_reference source_local_reference; + struct sccp_source_reference destination_local_reference; + + int connection_state; + + /* private */ + /* list of active connections */ + struct llist_head list; + struct sccp_system *system; + int incoming; +}; + +/** + * system functionality to implement on top of any other transport layer: + * call sccp_system_incoming for incoming data (from the network) + * sccp will call outgoing whenever outgoing data exists + * The conn is NULL for UDT and other messages without a connection + */ +int sccp_system_init(void (*outgoing)(struct sccp_connection *conn, struct msgb *data, void *gctx, void *ctx), void *context); +int sccp_system_incoming_ctx(struct msgb *data, void *ctx); +int sccp_system_incoming(struct msgb *data); + +/** + * Send data on an existing connection + */ +int sccp_connection_write(struct sccp_connection *connection, struct msgb *data); +int sccp_connection_send_it(struct sccp_connection *connection); +int sccp_connection_close(struct sccp_connection *connection, int cause); +int sccp_connection_free(struct sccp_connection *connection); + +/** + * internal.. + */ +int sccp_connection_force_free(struct sccp_connection *conn); + +/** + * Create a new socket. Set your callbacks and then call bind to open + * the connection. + */ +struct sccp_connection *sccp_connection_socket(void); + +/** + * Open the connection and send additional data + */ +int sccp_connection_connect(struct sccp_connection *conn, + const struct sockaddr_sccp *sccp_called, + struct msgb *data); + +/** + * mostly for testing purposes only. Set the accept callback. + * TODO: add true routing information... in analogy to socket, bind, accept + */ +int sccp_connection_set_incoming(const struct sockaddr_sccp *sock, + int (*accept_cb)(struct sccp_connection *connection, void *data), + void *user_data); + +/** + * Send data in terms of unit data. A fixed address indicator will be used. + */ +int sccp_write(struct msgb *data, + const struct sockaddr_sccp *sock_sender, + const struct sockaddr_sccp *sock_target, + int class, void *ctx); +int sccp_set_read(const struct sockaddr_sccp *sock, + int (*read_cb)(struct msgb *msgb, unsigned int, void *user_data), + void *user_data); + +/* generic sock addresses */ +extern const struct sockaddr_sccp sccp_ssn_bssap; + +/* helpers */ +uint32_t sccp_src_ref_to_int(struct sccp_source_reference *ref); +struct sccp_source_reference sccp_src_ref_from_int(uint32_t); + +struct msgb *sccp_create_cr(const struct sccp_source_reference *src_ref, const struct sockaddr_sccp *called, const uint8_t *data, size_t length); +struct msgb *sccp_create_refuse(struct sccp_source_reference *src_ref, int cause, uint8_t *data, int length); +struct msgb *sccp_create_cc(struct sccp_source_reference *src_ref, struct sccp_source_reference *dst_ref); +struct msgb *sccp_create_rlsd(struct sccp_source_reference *src_ref, struct sccp_source_reference *dst_ref, int cause); +struct msgb *sccp_create_dt1(struct sccp_source_reference *dst_ref, uint8_t *data, uint8_t len); +struct msgb *sccp_create_udt(int _class, const struct sockaddr_sccp *sock_sender, + const struct sockaddr_sccp *sock_target, uint8_t *data, int len); + +/** + * Below this are helper functions and structs for parsing SCCP messages + */ +struct sccp_parse_result { + struct sccp_address called; + struct sccp_address calling; + + /* point to the msg packet */ + struct sccp_source_reference *source_local_reference; + struct sccp_source_reference *destination_local_reference; + + /* data pointer */ + int data_len; +}; + +/* + * helper functions for the nat code + */ +int sccp_determine_msg_type(struct msgb *msg); +int sccp_parse_header(struct msgb *msg, struct sccp_parse_result *result); + +/* + * osmocore logging features + */ +void sccp_set_log_area(int log_area); + +#endif diff --git a/include/sccp/sccp_types.h b/include/sccp/sccp_types.h new file mode 100644 index 0000000..962bde4 --- /dev/null +++ b/include/sccp/sccp_types.h @@ -0,0 +1,441 @@ +/* + * ITU Q.713 defined types for SCCP + * + * (C) 2009 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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef SCCP_TYPES_H +#define SCCP_TYPES_H + +#include +#include + +/* Table 1/Q.713 - SCCP message types */ +enum sccp_message_types { + SCCP_MSG_TYPE_CR = 1, + SCCP_MSG_TYPE_CC = 2, + SCCP_MSG_TYPE_CREF = 3, + SCCP_MSG_TYPE_RLSD = 4, + SCCP_MSG_TYPE_RLC = 5, + SCCP_MSG_TYPE_DT1 = 6, + SCCP_MSG_TYPE_DT2 = 7, + SCCP_MSG_TYPE_AK = 8, + SCCP_MSG_TYPE_UDT = 9, + SCCP_MSG_TYPE_UDTS = 10, + SCCP_MSG_TYPE_ED = 11, + SCCP_MSG_TYPE_EA = 12, + SCCP_MSG_TYPE_RSR = 13, + SCCP_MSG_TYPE_RSC = 14, + SCCP_MSG_TYPE_ERR = 15, + SCCP_MSG_TYPE_IT = 16, + SCCP_MSG_TYPE_XUDT = 17, + SCCP_MSG_TYPE_XUDTS = 18, + SCCP_MSG_TYPE_LUDT = 19, + SCCP_MSG_TYPE_LUDTS = 20 +}; + +/* Table 2/Q.713 - SCCP parameter name codes */ +enum sccp_parameter_name_codes { + SCCP_PNC_END_OF_OPTIONAL = 0, + SCCP_PNC_DESTINATION_LOCAL_REFERENCE = 1, + SCCP_PNC_SOURCE_LOCAL_REFERENCE = 2, + SCCP_PNC_CALLED_PARTY_ADDRESS = 3, + SCCP_PNC_CALLING_PARTY_ADDRESS = 4, + SCCP_PNC_PROTOCOL_CLASS = 5, + SCCP_PNC_SEGMENTING = 6, + SCCP_PNC_RECEIVE_SEQ_NUMBER = 7, + SCCP_PNC_SEQUENCING = 8, + SCCP_PNC_CREDIT = 9, + SCCP_PNC_RELEASE_CAUSE = 10, + SCCP_PNC_RETURN_CAUSE = 11, + SCCP_PNC_RESET_CAUSE = 12, + SCCP_PNC_ERROR_CAUSE = 13, + SCCP_PNC_REFUSAL_CAUSE = 14, + SCCP_PNC_DATA = 15, + SCCP_PNC_SEGMENTATION = 16, + SCCP_PNC_HOP_COUNTER = 17, + SCCP_PNC_IMPORTANCE = 18, + SCCP_PNC_LONG_DATA = 19, +}; + +/* Figure 3/Q.713 Called/calling party address */ +enum { + SCCP_TITLE_IND_NONE = 0, + SCCP_TITLE_IND_NATURE_ONLY = 1, + SCCP_TITLE_IND_TRANSLATION_ONLY = 2, + SCCP_TITLE_IND_TRANS_NUM_ENC = 3, + SCCP_TITLE_IND_TRANS_NUM_ENC_NATURE = 4, +}; + +enum { + SCCP_CALL_ROUTE_ON_SSN = 1, + SCCP_CALL_ROUTE_ON_GT = 0, +}; + +struct sccp_called_party_address { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t point_code_indicator : 1, + ssn_indicator : 1, + global_title_indicator : 4, + routing_indicator : 1, + reserved : 1; +#elif OSMO_IS_BIG_ENDIAN + uint8_t reserved : 1, + routing_indicator : 1, + global_title_indicator : 4, + ssn_indicator : 1, + point_code_indicator : 1; +#endif + uint8_t data[0]; +} __attribute__((packed)); + +/* indicator indicates presence in the above order */ + +/* Figure 6/Q.713 */ +struct sccp_signalling_point_code { + uint8_t lsb; +#if OSMO_IS_LITTLE_ENDIAN + uint8_t msb : 6, + reserved : 2; +#elif OSMO_IS_BIG_ENDIAN + uint8_t reserved : 2, + msb : 6; +#endif +} __attribute__((packed)); + +/* SSN == subsystem number */ +enum sccp_subsystem_number { + SCCP_SSN_NOT_KNOWN_OR_USED = 0, + SCCP_SSN_MANAGEMENT = 1, + SCCP_SSN_RESERVED_ITU = 2, + SCCP_SSN_ISDN_USER_PART = 3, + SCCP_SSN_OMAP = 4, /* operation, maint and administration part */ + SCCP_SSN_MAP = 5, /* mobile application part */ + SCCP_SSN_HLR = 6, + SCCP_SSN_VLR = 7, + SCCP_SSN_MSC = 8, + SCCP_SSN_EIC = 9, /* equipent identifier centre */ + SCCP_SSN_AUC = 10, /* authentication centre */ + SCCP_SSN_ISDN_SUPPL_SERVICES = 11, + SCCP_SSN_RESERVED_INTL = 12, + SCCP_SSN_ISDN_EDGE_TO_EDGE = 13, + SCCP_SSN_TC_TEST_RESPONDER = 14, + + /* From GSM 03.03 8.2 */ + SCCP_SSN_BSSAP = 254, + SCCP_SSN_BSSOM = 253, +}; + +/* Q.713, 3.4.2.3 */ +enum { + SCCP_NAI_UNKNOWN = 0, + SCCP_NAI_SUBSCRIBER_NUMBER = 1, + SCCP_NAI_RESERVED_NATIONAL = 2, + SCCP_NAI_NATIONAL_SIGNIFICANT = 3, + SCCP_NAI_INTERNATIONAL = 4, +}; + +struct sccp_global_title { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t nature_of_addr_ind : 7, + odd_even : 1; +#elif OSMO_IS_BIG_ENDIAN + uint8_t odd_even : 1, + nature_of_addr_ind : 7; +#endif + uint8_t data[0]; +} __attribute__((packed)); + +/* Q.713, 3.3 */ +struct sccp_source_reference { + uint8_t octet1; + uint8_t octet2; + uint8_t octet3; +} __attribute__((packed)); + +/* Q.714, 3.6 */ +enum sccp_protocol_class { + SCCP_PROTOCOL_CLASS_0 = 0, + SCCP_PROTOCOL_CLASS_1 = 1, + SCCP_PROTOCOL_CLASS_2 = 2, + SCCP_PROTOCOL_CLASS_3 = 3, +}; + +/* bits 5-8 when class0, class1 is used */ +enum sccp_protocol_options { + SCCP_PROTOCOL_NO_SPECIAL = 0, + SCCP_PROTOCOL_RETURN_MESSAGE = 8, +}; + +enum sccp_release_cause { + SCCP_RELEASE_CAUSE_END_USER_ORIGINATED = 0, + SCCP_RELEASE_CAUSE_END_USER_CONGESTION = 1, + SCCP_RELEASE_CAUSE_END_USER_FAILURE = 2, + SCCP_RELEASE_CAUSE_SCCP_USER_ORIGINATED = 3, + SCCP_RELEASE_CAUSE_REMOTE_PROCEDURE_ERROR = 4, + SCCP_RELEASE_CAUSE_INCONSISTENT_CONN_DATA = 5, + SCCP_RELEASE_CAUSE_ACCESS_FAILURE = 6, + SCCP_RELEASE_CAUSE_ACCESS_CONGESTION = 7, + SCCP_RELEASE_CAUSE_SUBSYSTEM_FAILURE = 8, + SCCP_RELEASE_CAUSE_SUBSYSTEM_CONGESTION = 9, + SCCP_RELEASE_CAUSE_MTP_FAILURE = 10, + SCCP_RELEASE_CAUSE_NETWORK_CONGESTION = 11, + SCCP_RELEASE_CAUSE_EXPIRATION_RESET = 12, + SCCP_RELEASE_CAUSE_EXPIRATION_INACTIVE = 13, + SCCP_RELEASE_CAUSE_RESERVED = 14, + SCCP_RELEASE_CAUSE_UNQUALIFIED = 15, + SCCP_RELEASE_CAUSE_SCCP_FAILURE = 16, +}; + +enum sccp_return_cause { + SCCP_RETURN_CAUSE_NO_TRANSLATION_NATURE = 0, + SCCP_RETURN_CAUSE_NO_TRANSLATION = 1, + SCCP_RETURN_CAUSE_SUBSYSTEM_CONGESTION = 2, + SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE = 3, + SCCP_RETURN_CAUSE_UNEQUIPPED_USER = 4, + SCCP_RETURN_CAUSE_MTP_FAILURE = 5, + SCCP_RETURN_CAUSE_NETWORK_CONGESTION = 6, + SCCP_RETURN_CAUSE_UNQUALIFIED = 7, + SCCP_RETURN_CAUSE_ERROR_IN_MSG_TRANSPORT = 8, + SCCP_RETURN_CAUSE_ERROR_IN_LOCAL_PROCESSING = 9, + SCCP_RETURN_CAUSE_DEST_CANNOT_PERFORM_REASSEMBLY = 10, + SCCP_RETURN_CAUSE_SCCP_FAILURE = 11, + SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION = 12, + SCCP_RETURN_CAUSE_SEGMENTATION_NOT_SUPPORTED= 13, + SCCP_RETURN_CAUSE_SEGMENTATION_FAILURE = 14 +}; + +enum sccp_reset_cause { + SCCP_RESET_CAUSE_END_USER_ORIGINATED = 0, + SCCP_RESET_CAUSE_SCCP_USER_ORIGINATED = 1, + SCCP_RESET_CAUSE_MSG_OUT_OF_ORDER_PS = 2, + SCCP_RESET_CAUSE_MSG_OUT_OF_ORDER_PR = 3, + SCCP_RESET_CAUSE_RPC_OUT_OF_WINDOW = 4, + SCCP_RESET_CAUSE_RPC_INCORRECT_PS = 5, + SCCP_RESET_CAUSE_RPC_GENERAL = 6, + SCCP_RESET_CAUSE_REMOTE_END_USER_OPERATIONAL= 7, + SCCP_RESET_CAUSE_NETWORK_OPERATIONAL = 8, + SCCP_RESET_CAUSE_ACCESS_OPERATIONAL = 9, + SCCP_RESET_CAUSE_NETWORK_CONGESTION = 10, + SCCP_RESET_CAUSE_RESERVED = 11, +}; + +enum sccp_error_cause { + SCCP_ERROR_LRN_MISMATCH_UNASSIGNED = 0, /* local reference number */ + SCCP_ERROR_LRN_MISMATCH_INCONSISTENT = 1, + SCCP_ERROR_POINT_CODE_MISMATCH = 2, + SCCP_ERROR_SERVICE_CLASS_MISMATCH = 3, + SCCP_ERROR_UNQUALIFIED = 4, +}; + +enum sccp_refusal_cause { + SCCP_REFUSAL_END_USER_ORIGINATED = 0, + SCCP_REFUSAL_END_USER_CONGESTION = 1, + SCCP_REFUSAL_END_USER_FAILURE = 2, + SCCP_REFUSAL_SCCP_USER_ORIGINATED = 3, + SCCP_REFUSAL_DESTINATION_ADDRESS_UKNOWN = 4, + SCCP_REFUSAL_DESTINATION_INACCESSIBLE = 5, + SCCP_REFUSAL_NET_QOS_NON_TRANSIENT = 6, + SCCP_REFUSAL_NET_QOS_TRANSIENT = 7, + SCCP_REFUSAL_ACCESS_FAILURE = 8, + SCCP_REFUSAL_ACCESS_CONGESTION = 9, + SCCP_REFUSAL_SUBSYSTEM_FAILURE = 10, + SCCP_REFUSAL_SUBSYTEM_CONGESTION = 11, + SCCP_REFUSAL_EXPIRATION = 12, + SCCP_REFUSAL_INCOMPATIBLE_USER_DATA = 13, + SCCP_REFUSAL_RESERVED = 14, + SCCP_REFUSAL_UNQUALIFIED = 15, + SCCP_REFUSAL_HOP_COUNTER_VIOLATION = 16, + SCCP_REFUSAL_SCCP_FAILURE = 17, + SCCP_REFUSAL_UNEQUIPPED_USER = 18, +}; + +/* + * messages... as of Q.713 Chapter 4 + */ +struct sccp_connection_request { + /* mandatory */ + uint8_t type; + struct sccp_source_reference source_local_reference; + uint8_t proto_class; + + + /* variable */ + uint8_t variable_called; +#if VARIABLE + called_party_address +#endif + + /* optional */ + uint8_t optional_start; + +#if OPTIONAL + credit 3 + callingparty var 4-n + data 3-130 + hop_counter 3 + importance 3 + end_of_optional 1 +#endif + + uint8_t data[0]; +} __attribute__((packed)); + +struct sccp_connection_confirm { + /* mandatory */ + uint8_t type; + struct sccp_source_reference destination_local_reference; + struct sccp_source_reference source_local_reference; + uint8_t proto_class; + + /* optional */ + uint8_t optional_start; + + /* optional */ +#if OPTIONAL + credit 3 + called party 4 + data 3-130 + importance 3 + end_of_optional 1 +#endif + + uint8_t data[0]; +} __attribute__((packed)); + +struct sccp_connection_refused { + /* mandatory */ + uint8_t type; + struct sccp_source_reference destination_local_reference; + uint8_t cause; + + /* optional */ + uint8_t optional_start; + + /* optional */ +#if OPTIONAL + called party 4 + data 3-130 + importance 3 + end_of_optional 1 +#endif + + uint8_t data[0]; +} __attribute__((packed)); + +struct sccp_connection_released { + /* mandatory */ + uint8_t type; + struct sccp_source_reference destination_local_reference; + struct sccp_source_reference source_local_reference; + uint8_t release_cause; + + + /* optional */ + uint8_t optional_start; + +#if OPTIONAL + data 3-130 + importance 3 + end_of_optional 1 +#endif + uint8_t data[0]; +} __attribute__((packed)); + +struct sccp_connection_release_complete { + uint8_t type; + struct sccp_source_reference destination_local_reference; + struct sccp_source_reference source_local_reference; +} __attribute__((packed)); + +struct sccp_data_form1 { + /* mandatory */ + uint8_t type; + struct sccp_source_reference destination_local_reference; + uint8_t segmenting; + + /* variable */ + uint8_t variable_start; + +#if VARIABLE + data 2-256; +#endif + + uint8_t data[0]; +} __attribute__((packed)); + + +struct sccp_data_unitdata { + /* mandatory */ + uint8_t type; + uint8_t proto_class; + + + /* variable */ + uint8_t variable_called; + uint8_t variable_calling; + uint8_t variable_data; + +#if VARIABLE + called party address + calling party address +#endif + + uint8_t data[0]; +} __attribute__((packed)); + +struct sccp_data_unitdata_service { + /* mandantory */ + uint8_t type; + uint8_t return_cause; + + + /* variable */ + uint8_t variable_called; + uint8_t variable_calling; + uint8_t variable_data; + +#if VARIABLE + called party address + calling party address +#endif + + uint8_t data[0]; +} __attribute__((packed)); + + +struct sccp_data_it { + /* mandatory */ + uint8_t type; + struct sccp_source_reference destination_local_reference; + struct sccp_source_reference source_local_reference; + uint8_t proto_class; + + uint8_t sequencing[2]; + uint8_t credit; +} __attribute__((packed)); + +struct sccp_proto_err { + uint8_t type; + struct sccp_source_reference destination_local_reference; + uint8_t error_cause; +}; + +#endif diff --git a/libosmo-mtp.pc.in b/libosmo-mtp.pc.in new file mode 100644 index 0000000..675d0d3 --- /dev/null +++ b/libosmo-mtp.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmo MTP Lib +Description: Osmo MTP Lib +Version: @VERSION@ +Libs: -L${libdir} -lmtp +Cflags: -I${includedir}/ diff --git a/libosmo-sccp.pc.in b/libosmo-sccp.pc.in new file mode 100644 index 0000000..eda8d49 --- /dev/null +++ b/libosmo-sccp.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: OpenBSC SCCP Lib +Description: OpenBSC SCCP Lib +Version: @VERSION@ +Libs: -L${libdir} -lsccp +Cflags: -I${includedir}/ diff --git a/libosmo-sigtran.pc.in b/libosmo-sigtran.pc.in new file mode 100644 index 0000000..d735797 --- /dev/null +++ b/libosmo-sigtran.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom SIGTRAN Library +Description: Osmocom implementation of (parts of) SIGTRAN +Version: @VERSION@ +Libs: -L${libdir} -losmo-sigtran +Cflags: -I${includedir}/ diff --git a/libosmo-xua.pc.in b/libosmo-xua.pc.in new file mode 100644 index 0000000..28f8285 --- /dev/null +++ b/libosmo-xua.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmo XUA Lib +Description: Osmo XUA Lib +Version: @VERSION@ +Libs: -L${libdir} -losmo-xua +Cflags: -I${includedir}/ diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..1001d19 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,37 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) + +noinst_HEADERS = sccp_internal.h xua_asp_fsm.h xua_as_fsm.h xua_internal.h + +# Legacy static libs + +sccpdir = $(libdir) +sccp_LIBRARIES = libsccp.a libmtp.a libxua.a + +libsccp_a_SOURCES = sccp.c +libmtp_a_SOURCES = mtp_pcap.c +libxua_a_SOURCES = xua_msg.c +# ensure that the file for the static lib is built with different C +# flags, working around automake complaining that xua_msg.o is built +# both with libtool (below) and without (here) +libxua_a_CPPFLAGS = $(AM_CPPFLAGS) -DDUMMY -UDUMMY + + +# New shared lib + +lib_LTLIBRARIES = libosmo-sigtran.la + +# This is _NOT_ the library release version, it's an API version. +# Please read Chapter 6 "Library interface versions" of the libtool +# documentation before making any modification +LIBVERSION=0:0:0 + +libosmo_sigtran_la_SOURCES = sccp_sap.c sua.c m3ua.c xua_msg.c sccp_helpers.c \ + sccp2sua.c sccp_scrc.c sccp_sclc.c sccp_scoc.c \ + sccp_user.c xua_rkm.c xua_default_lm_fsm.c \ + osmo_ss7.c osmo_ss7_hmrt.c xua_asp_fsm.c xua_as_fsm.c \ + osmo_ss7_vty.c sccp_vty.c ipa.c +libosmo_sigtran_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -export-symbols-regex '^osmo_' +libosmo_sigtran_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) \ + $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS) diff --git a/src/ipa.c b/src/ipa.c new file mode 100644 index 0000000..a7060db --- /dev/null +++ b/src/ipa.c @@ -0,0 +1,215 @@ +/* implementation of IPA/SCCPlite transport */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +//#include +#include +#include + +#include +#include +#include +#include + +#include "xua_internal.h" +#include "xua_asp_fsm.h" + + +/*! \brief Send a given xUA message via a given IPA "Application Server" + * \param[in] as Application Server through which to send \a xua + * \param[in] xua xUA message to be sent + * \return 0 on success; negative on error */ +int ipa_tx_xua_as(struct osmo_ss7_as *as, struct xua_msg *xua) +{ + struct xua_msg_part *data_ie; + struct msgb *msg; + unsigned int src_len; + const uint8_t *src; + uint8_t *dst; + + OSMO_ASSERT(as->cfg.proto == OSMO_SS7_ASP_PROT_IPA); + + /* we're actually only interested in the data part */ + data_ie = xua_msg_find_tag(xua, M3UA_IEI_PROT_DATA); + if (!data_ie || data_ie->len < sizeof(struct m3ua_data_hdr)) + return -1; + + /* and even the data part still has the header prepended */ + src = data_ie->dat + sizeof(struct m3ua_data_hdr); + src_len = data_ie->len - sizeof(struct m3ua_data_hdr); + + /* sufficient headroom for osmo_ipa_msg_push_header() */ + msg = ipa_msg_alloc(16); + if (!msg) + return -1; + + dst = msgb_put(msg, src_len); + memcpy(dst, src, src_len); + + /* TODO: if we ever need something beyond SCCP, we can use the + * M3UA SIO to determine the protocol */ + osmo_ipa_msg_push_header(msg, IPAC_PROTO_SCCP); + + return xua_as_transmit_msg(as, msg); +} + +static int ipa_rx_msg_ccm(struct osmo_ss7_asp *asp, struct msgb *msg) +{ + uint8_t msg_type = msg->l2h[0]; + + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s:%s\n", __func__, msgb_hexdump(msg)); + + /* Convert CCM into events to the IPA_ASP_FSM */ + switch (msg_type) { + case IPAC_MSGT_ID_ACK: + osmo_fsm_inst_dispatch(asp->fi, IPA_ASP_E_ID_ACK, msg); + break; + case IPAC_MSGT_ID_RESP: + osmo_fsm_inst_dispatch(asp->fi, IPA_ASP_E_ID_RESP, msg); + break; + case IPAC_MSGT_ID_GET: + osmo_fsm_inst_dispatch(asp->fi, IPA_ASP_E_ID_GET, msg); + break; + case IPAC_MSGT_PING: + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_ASPSM_BEAT, msg); + break; + case IPAC_MSGT_PONG: + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_ASPSM_BEAT_ACK, msg); + break; + default: + LOGPASP(asp, DLSS7, LOGL_NOTICE, "Unknown CCM Message 0x%02x: %s\n", + msg_type, msgb_hexdump(msg)); + return -1; + } + + msgb_free(msg); + + return 0; +} + +static struct osmo_ss7_as *find_as_for_asp(struct osmo_ss7_asp *asp) +{ + struct osmo_ss7_as *as; + + /* in the IPA case, weassume there is a 1:1 mapping between the + * ASP and the AS. An AS without ASP means there is no + * connection, and an ASP without AS means that we don't (yet?) + * know the identity of the peer */ + + llist_for_each_entry(as, &asp->inst->as_list, list) { + if (osmo_ss7_as_has_asp(as, asp)) + return as; + } + return NULL; +} + +static int ipa_rx_msg_sccp(struct osmo_ss7_asp *asp, struct msgb *msg) +{ + struct m3ua_data_hdr data_hdr; + struct xua_msg *xua = xua_msg_alloc(); + struct osmo_ss7_as *as = find_as_for_asp(asp); + + if (!as) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Rx message for IPA ASP without AS?!\n"); + msgb_free(msg); + return -1; + } + + /* pull the IPA header */ + msgb_pull_to_l2(msg); + + /* We have received an IPA-encapsulated SCCP message, without + * any MTP routing label. This means we have no real idea where + * it came from, nor where it goes to. We could simply treat it + * as being for the local point code, but then this means that + * we would have to implement SCCP connection coupling in order + * to route the connections to any other point code. The reason + * for this is the lack of addressing information inside the + * non-CR/CC connection oriented messages. + * + * The only other alternative we have is to simply have a + * STP (server) side configuration that specifies which point + * code those messages are to be routed to, and then use this + * 'override DPC' in the routing decision. We could do the same + * for the source point code to ensure responses are routed back + * to us. This is all quite ugly, but then what can we do :/ + */ + + memset(&data_hdr, 0, sizeof(data_hdr)); + data_hdr.si = MTP_SI_SCCP; + if (asp->cfg.is_server) { + /* Source: the PC of the routing key */ + data_hdr.opc = as->cfg.routing_key.pc; + /* Destination: Based on VTY config */ + data_hdr.dpc = as->cfg.pc_override.dpc; + } else { + /* Source: Based on VTY config */ + data_hdr.opc = as->cfg.pc_override.dpc; + /* Destination: PC of the routing key */ + data_hdr.dpc = as->cfg.routing_key.pc; + } + xua = m3ua_xfer_from_data(&data_hdr, msgb_l2(msg), msgb_l2len(msg)); + + return m3ua_hmdc_rx_from_l2(asp->inst, xua); +} + +/*! \brief process M3UA message received from socket + * \param[in] asp Application Server Process receiving \a msg + * \param[in] msg received message buffer. Callee takes ownership! + * \returns 0 on success; negative on error */ +int ipa_rx_msg(struct osmo_ss7_asp *asp, struct msgb *msg) +{ + struct ipaccess_head *hh; + int rc; + + OSMO_ASSERT(asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA); + + /* osmo_ipa_process_msg() will already have verified length + * consistency and set up l2h poiter */ + hh = (struct ipaccess_head *) msg->l1h; + + switch (hh->proto) { + case IPAC_PROTO_IPACCESS: + rc = ipa_rx_msg_ccm(asp, msg); + break; + case IPAC_PROTO_SCCP: + rc = ipa_rx_msg_sccp(asp, msg); + break; + default: + LOGPASP(asp, DLSS7, LOGL_DEBUG, "Unknown Stream ID 0x%02x: %s\n", + hh->proto, msgb_hexdump(msg)); + rc = -1; + } + + return rc; +} diff --git a/src/m3ua.c b/src/m3ua.c new file mode 100644 index 0000000..c43dfa4 --- /dev/null +++ b/src/m3ua.c @@ -0,0 +1,771 @@ +/* Minimal implementation of RFC 4666 - MTP3 User Adaptation Layer */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "xua_as_fsm.h" +#include "xua_asp_fsm.h" +#include "xua_internal.h" + +#define M3UA_MSGB_SIZE 1500 + +/*********************************************************************** + * Protocol Definition (string tables, mandatory IE checking) + ***********************************************************************/ + +/* Section 3.8.1 */ +const struct value_string m3ua_err_names[] = { + { M3UA_ERR_INVALID_VERSION, "Invalid Version" }, + { M3UA_ERR_UNSUPP_MSG_CLASS, "Unsupported Message Class" }, + { M3UA_ERR_UNSUPP_MSG_TYPE, "Unsupported Message Type" }, + { M3UA_ERR_UNSUPP_TRAF_MOD_TYP, "Unsupported Traffic Mode Type" }, + { M3UA_ERR_UNEXPECTED_MSG, "Unexpected Message" }, + { M3UA_ERR_PROTOCOL_ERR, "Protocol Error" }, + { M3UA_ERR_INVAL_STREAM_ID, "Invalid Stream Identifier" }, + { M3UA_ERR_REFUSED_MGMT_BLOCKING, "Refused - Management Blocking" }, + { M3UA_ERR_ASP_ID_REQD, "ASP Identifier Required" }, + { M3UA_ERR_INVAL_ASP_ID, "Invalid ASP Identifier" }, + { M3UA_ERR_INVAL_PARAM_VAL, "Invalid Parameter Value" }, + { M3UA_ERR_PARAM_FIELD_ERR, "Parameter Field Error" }, + { M3UA_ERR_UNEXP_PARAM, "Unexpected Parameter" }, + { M3UA_ERR_DEST_STATUS_UNKN, "Destination Status Unknown" }, + { M3UA_ERR_INVAL_NET_APPEAR, "Invalid Network Appearance" }, + { M3UA_ERR_MISSING_PARAM, "Missing Parameter" }, + { M3UA_ERR_INVAL_ROUT_CTX, "Invalid Routing Context" }, + { M3UA_ERR_NO_CONFGD_AS_FOR_ASP,"No Configured AS for ASP" }, + { SUA_ERR_SUBSYS_STATUS_UNKN, "Subsystem Status Unknown" }, + { SUA_ERR_INVAL_LOADSH_LEVEL, "Invalid loadsharing level" }, + { 0, NULL } +}; + +const struct value_string m3ua_ntfy_type_names[] = { + { M3UA_NOTIFY_T_STATCHG, "State Change" }, + { M3UA_NOTIFY_T_OTHER, "Other" }, + { 0, NULL } +}; + +const struct value_string m3ua_ntfy_stchg_names[] = { + { M3UA_NOTIFY_I_RESERVED, "Reserved" }, + { M3UA_NOTIFY_I_AS_INACT, "AS Inactive" }, + { M3UA_NOTIFY_I_AS_ACT, "AS Active" }, + { M3UA_NOTIFY_I_AS_PEND, "AS Pending" }, + { 0, NULL } +}; + +const struct value_string m3ua_ntfy_other_names[] = { + { M3UA_NOTIFY_I_OT_INS_RES, "Insufficient ASP Resouces active in AS" }, + { M3UA_NOTIFY_I_OT_ALT_ASP_ACT, "Alternative ASP Active" }, + { M3UA_NOTIFY_I_OT_ASP_FAILURE, "ASP Failure" }, + { 0, NULL } +}; + +static const struct value_string m3ua_iei_names[] = { + { M3UA_IEI_INFO_STRING, "INFO String" }, + { M3UA_IEI_ROUTE_CTX, "Routing Context" }, + { M3UA_IEI_DIAG_INFO, "Diagnostic Info" }, + { M3UA_IEI_HEARDBT_DATA, "Heartbeat Data" }, + { M3UA_IEI_TRAF_MODE_TYP, "Traffic Mode Type" }, + { M3UA_IEI_ERR_CODE, "Error Code" }, + { M3UA_IEI_STATUS, "Status" }, + { M3UA_IEI_ASP_ID, "ASP Identifier" }, + { M3UA_IEI_AFFECTED_PC, "Affected Point Code" }, + { M3UA_IEI_CORR_ID, "Correlation Id" }, + + { M3UA_IEI_NET_APPEAR, "Network Appearance" }, + { M3UA_IEI_USER_CAUSE, "User/Cause" }, + { M3UA_IEI_CONG_IND, "Congestion Indication" }, + { M3UA_IEI_CONC_DEST, "Concerned Destination" }, + { M3UA_IEI_ROUT_KEY, "Routing Key" }, + { M3UA_IEI_REG_RESULT, "Registration Result" }, + { M3UA_IEI_DEREG_RESULT, "De-Registration Result" }, + { M3UA_IEI_LOC_RKEY_ID, "Local Routing-Key Identifier" }, + { M3UA_IEI_DEST_PC, "Destination Point Code" }, + { M3UA_IEI_SVC_IND, "Service Indicators" }, + { M3UA_IEI_ORIG_PC, "Originating Point Code List" }, + { M3UA_IEI_PROT_DATA, "Protocol Data" }, + { M3UA_IEI_REG_STATUS, "Registration Status" }, + { M3UA_IEI_DEREG_STATUS, "De-Registration Status" }, + { 0, NULL } +}; + +#define MAND_IES(msgt, ies) [msgt] = (ies) + +/* XFER */ +static const uint16_t data_mand_ies[] = { + M3UA_IEI_PROT_DATA, 0 +}; +static const struct value_string m3ua_xfer_msgt_names[] = { + { M3UA_XFER_DATA, "DATA" }, + { 0, NULL } +}; +static const struct xua_msg_class msg_class_xfer = { + .name = "XFER", + .msgt_names = m3ua_xfer_msgt_names, + .mand_ies = { + MAND_IES(M3UA_XFER_DATA, data_mand_ies), + }, +}; + +/* SNM */ +static const uint16_t duna_mand_ies[] = { + M3UA_IEI_AFFECTED_PC, 0 +}; +static const uint16_t dava_mand_ies[] = { + M3UA_IEI_AFFECTED_PC, 0 +}; +static const uint16_t daud_mand_ies[] = { + M3UA_IEI_AFFECTED_PC, 0 +}; +static const uint16_t scon_mand_ies[] = { + M3UA_IEI_AFFECTED_PC, 0 +}; +static const uint16_t dupu_mand_ies[] = { + M3UA_IEI_AFFECTED_PC, M3UA_IEI_USER_CAUSE, 0 +}; +static const uint16_t drst_mand_ies[] = { + M3UA_IEI_AFFECTED_PC, 0 +}; +static const struct value_string m3ua_snm_msgt_names[] = { + { M3UA_SNM_DUNA, "DUNA" }, + { M3UA_SNM_DAVA, "DAVA" }, + { M3UA_SNM_DAUD, "DAUD" }, + { M3UA_SNM_SCON, "SCON" }, + { M3UA_SNM_DUPU, "DUPU" }, + { M3UA_SNM_DRST, "DRST" }, + { 0, NULL } +}; +const struct xua_msg_class m3ua_msg_class_snm = { + .name = "SNM", + .msgt_names = m3ua_snm_msgt_names, + .mand_ies = { + MAND_IES(M3UA_SNM_DUNA, duna_mand_ies), + MAND_IES(M3UA_SNM_DAVA, dava_mand_ies), + MAND_IES(M3UA_SNM_DAUD, daud_mand_ies), + MAND_IES(M3UA_SNM_SCON, scon_mand_ies), + MAND_IES(M3UA_SNM_DUPU, dupu_mand_ies), + MAND_IES(M3UA_SNM_DRST, drst_mand_ies), + }, +}; + +/* ASPSM */ +static const struct value_string m3ua_aspsm_msgt_names[] = { + { M3UA_ASPSM_UP, "UP" }, + { M3UA_ASPSM_DOWN, "DOWN" }, + { M3UA_ASPSM_BEAT, "BEAT" }, + { M3UA_ASPSM_UP_ACK, "UP-ACK" }, + { M3UA_ASPSM_DOWN_ACK, "DOWN-ACK" }, + { M3UA_ASPSM_BEAT_ACK, "BEAT-ACK" }, + { 0, NULL } +}; +const struct xua_msg_class m3ua_msg_class_aspsm = { + .name = "ASPSM", + .msgt_names = m3ua_aspsm_msgt_names, +}; + +/* ASPTM */ +const struct value_string m3ua_asptm_msgt_names[] = { + { M3UA_ASPTM_ACTIVE, "ACTIVE" }, + { M3UA_ASPTM_INACTIVE, "INACTIVE" }, + { M3UA_ASPTM_ACTIVE_ACK,"ACTIVE-ACK" }, + { M3UA_ASPTM_INACTIVE_ACK, "INACTIVE-ACK" }, + { 0, NULL } +}; +const struct xua_msg_class m3ua_msg_class_asptm = { + .name = "ASPTM", + .msgt_names = m3ua_asptm_msgt_names, + .iei_names = m3ua_iei_names, +}; + +/* MGMT */ +static const uint16_t err_req_ies[] = { + M3UA_IEI_ERR_CODE, 0 +}; +static const uint16_t ntfy_req_ies[] = { + M3UA_IEI_STATUS, 0 +}; +static const struct value_string m3ua_mgmt_msgt_names[] = { + { M3UA_MGMT_ERR, "ERROR" }, + { M3UA_MGMT_NTFY, "NOTIFY" }, + { 0, NULL } +}; +const struct xua_msg_class m3ua_msg_class_mgmt = { + .name = "MGMT", + .msgt_names = m3ua_mgmt_msgt_names, + .iei_names = m3ua_iei_names, + .mand_ies = { + MAND_IES(M3UA_MGMT_ERR, err_req_ies), + MAND_IES(M3UA_MGMT_NTFY, ntfy_req_ies), + }, +}; + +/* RKM */ +static const uint16_t reg_req_ies[] = { + M3UA_IEI_ROUT_KEY, 0 +}; +static const uint16_t reg_rsp_ies[] = { + M3UA_IEI_REG_RESULT, 0 +}; +static const uint16_t dereg_req_ies[] = { + M3UA_IEI_ROUTE_CTX, 0 +}; +static const uint16_t dereg_rsp_ies[] = { + M3UA_IEI_DEREG_RESULT, 0 +}; +static const struct value_string m3ua_rkm_msgt_names[] = { + { M3UA_RKM_REG_REQ, "REG-REQ" }, + { M3UA_RKM_REG_RSP, "REG-RESP" }, + { M3UA_RKM_DEREG_REQ, "DEREG-REQ" }, + { M3UA_RKM_DEREG_RSP, "DEREG-RESP" }, + { 0, NULL } +}; +const struct xua_msg_class m3ua_msg_class_rkm = { + .name = "RKM", + .msgt_names = m3ua_rkm_msgt_names, + .iei_names = m3ua_iei_names, + .mand_ies = { + MAND_IES(M3UA_RKM_REG_REQ, reg_req_ies), + MAND_IES(M3UA_RKM_REG_RSP, reg_rsp_ies), + MAND_IES(M3UA_RKM_DEREG_REQ, dereg_req_ies), + MAND_IES(M3UA_RKM_DEREG_RSP, dereg_rsp_ies), + }, +}; + +/* M3UA dialect of XUA, MGMT,XFER,SNM,ASPSM,ASPTM,RKM */ +const struct xua_dialect xua_dialect_m3ua = { + .name = "M3UA", + .ppid = M3UA_PPID, + .port = M3UA_PORT, + .log_subsys = DLM3UA, + .class = { + [M3UA_MSGC_MGMT] = &m3ua_msg_class_mgmt, + [M3UA_MSGC_XFER] = &msg_class_xfer, + [M3UA_MSGC_SNM] = &m3ua_msg_class_snm, + [M3UA_MSGC_ASPSM] = &m3ua_msg_class_aspsm, + [M3UA_MSGC_ASPTM] = &m3ua_msg_class_asptm, + [M3UA_MSGC_RKM] = &m3ua_msg_class_rkm, + }, +}; + +/* convert osmo_mtp_transfer_param to m3ua_data_hdr */ +void mtp_xfer_param_to_m3ua_dh(struct m3ua_data_hdr *mdh, + const struct osmo_mtp_transfer_param *param) +{ + mdh->opc = htonl(param->opc); + mdh->dpc = htonl(param->dpc); + mdh->si = param->sio & 0xF; + mdh->ni = (param->sio >> 6) & 0x3; + mdh->mp = (param->sio >> 4) & 0x3; + mdh->sls = param->sls; +} + +/* convert m3ua_data_hdr to osmo_mtp_transfer_param */ +void m3ua_dh_to_xfer_param(struct osmo_mtp_transfer_param *param, + const struct m3ua_data_hdr *mdh) +{ + param->opc = ntohl(mdh->opc); + param->dpc = ntohl(mdh->dpc); + param->sls = mdh->sls; + /* re-construct SIO */ + param->sio = (mdh->si & 0xF) | + (mdh->mp & 0x3 << 4) | + (mdh->ni & 0x3 << 6); +} + +#define M3UA_MSG_SIZE 2048 +#define M3UA_MSG_HEADROOM 512 + +struct msgb *m3ua_msgb_alloc(const char *name) +{ + if (!name) + name = "M3UA"; + return msgb_alloc_headroom(M3UA_MSG_SIZE+M3UA_MSG_HEADROOM, + M3UA_MSG_HEADROOM, name); +} + +struct xua_msg *m3ua_xfer_from_data(const struct m3ua_data_hdr *data_hdr, + const uint8_t *data, unsigned int data_len) +{ + struct xua_msg *xua = xua_msg_alloc(); + struct xua_msg_part *data_part; + + xua->hdr = XUA_HDR(M3UA_MSGC_XFER, M3UA_XFER_DATA); + /* Network Appearance: Optional */ + /* Routing Context: Conditional */ + /* Protocol Data: Mandatory */ + data_part = talloc_zero(xua, struct xua_msg_part); + OSMO_ASSERT(data_part); + data_part->tag = M3UA_IEI_PROT_DATA; + data_part->len = sizeof(*data_hdr) + data_len; + data_part->dat = talloc_size(data_part, data_part->len); + OSMO_ASSERT(data_part->dat); + memcpy(data_part->dat, data_hdr, sizeof(*data_hdr)); + memcpy(data_part->dat+sizeof(*data_hdr), data, data_len); + llist_add_tail(&data_part->entry, &xua->headers); + /* Correlation Id: Optional */ + + return xua; +} + +/*********************************************************************** + * ERROR generation + ***********************************************************************/ + +static struct xua_msg *m3ua_gen_error(uint32_t err_code) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(M3UA_MSGC_MGMT, M3UA_MGMT_ERR); + xua->hdr.version = M3UA_VERSION; + xua_msg_add_u32(xua, M3UA_IEI_ERR_CODE, err_code); + + return xua; +} + +static struct xua_msg *m3ua_gen_error_msg(uint32_t err_code, struct msgb *msg) +{ + struct xua_msg *xua = m3ua_gen_error(err_code); + unsigned int len_max_40 = msgb_length(msg); + + if (len_max_40 > 40) + len_max_40 = 40; + + xua_msg_add_data(xua, M3UA_IEI_DIAG_INFO, len_max_40, msgb_data(msg)); + + return xua; +} + +/*********************************************************************** + * NOTIFY generation + ***********************************************************************/ + +/* RFC4666 Ch. 3.8.2. Notify */ +struct xua_msg *m3ua_encode_notify(const struct osmo_xlm_prim_notify *npar) +{ + struct xua_msg *xua = xua_msg_alloc(); + uint32_t status; + + xua->hdr = XUA_HDR(M3UA_MSGC_MGMT, M3UA_MGMT_NTFY); + + status = M3UA_NOTIFY(htons(npar->status_type), htons(npar->status_info)); + /* cannot use xua_msg_add_u32() as it does endian conversion */ + xua_msg_add_data(xua, M3UA_IEI_STATUS, sizeof(status), (uint8_t *) &status); + + /* Conditional: ASP Identifier */ + if (npar->presence & NOTIFY_PAR_P_ASP_ID) + xua_msg_add_u32(xua, M3UA_IEI_ASP_ID, npar->asp_id); + + /* Optional Routing Context */ + if (npar->presence & NOTIFY_PAR_P_ROUTE_CTX) + xua_msg_add_u32(xua, M3UA_IEI_ROUTE_CTX, npar->route_ctx); + + /* Optional: Info String */ + if (npar->info_string) + xua_msg_add_data(xua, M3UA_IEI_INFO_STRING, + strlen(npar->info_string)+1, + (uint8_t *) npar->info_string); + + return xua; +} + +/* RFC4666 Ch. 3.8.2. Notify */ +int m3ua_decode_notify(struct osmo_xlm_prim_notify *npar, void *ctx, + const struct xua_msg *xua) +{ + struct xua_msg_part *info_ie, *aspid_ie, *status_ie, *rctx_ie; + uint32_t status; + + /* cannot use xua_msg_get_u32() as it does endian conversion */ + status_ie = xua_msg_find_tag(xua, M3UA_IEI_STATUS); + if (!status_ie) { + LOGP(DLM3UA, LOGL_ERROR, "M3UA NOTIFY without Status IE\n"); + return -1; + } + status = *(uint32_t *) status_ie->dat; + + aspid_ie = xua_msg_find_tag(xua, M3UA_IEI_ASP_ID); + rctx_ie = xua_msg_find_tag(xua, M3UA_IEI_ROUTE_CTX); + info_ie = xua_msg_find_tag(xua, M3UA_IEI_INFO_STRING); + + npar->presence = 0; + npar->status_type = ntohs(status & 0xffff); + npar->status_info = ntohs(status >> 16); + + if (aspid_ie) { + npar->asp_id = xua_msg_part_get_u32(aspid_ie); + npar->presence |= NOTIFY_PAR_P_ASP_ID; + } + + if (rctx_ie) { + npar->route_ctx = xua_msg_part_get_u32(rctx_ie); + npar->presence |= NOTIFY_PAR_P_ROUTE_CTX; + } + + if (info_ie) { + npar->info_string = talloc_size(ctx, info_ie->len); + memcpy(npar->info_string, info_ie->dat, info_ie->len); + } else + npar->info_string = NULL; + + return 0; +} + +/*********************************************************************** + * Transmitting M3UA messsages to SCTP + ***********************************************************************/ + +/* Convert M3UA from xua_msg to msgb and set PPID/stream */ +static struct msgb *m3ua_to_msg(struct xua_msg *xua) +{ + struct msgb *msg = xua_to_msg(M3UA_VERSION, xua); + + if (!msg) { + LOGP(DLM3UA, LOGL_ERROR, "Error encoding M3UA Msg\n"); + return NULL; + } + + if (xua->hdr.msg_class == M3UA_MSGC_XFER) + msgb_sctp_stream(msg) = 1; + else + msgb_sctp_stream(msg) = 0; + msgb_sctp_ppid(msg) = M3UA_PPID; + + return msg; +} + +/* transmit given xua_msg via given ASP */ +static int m3ua_tx_xua_asp(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct msgb *msg = m3ua_to_msg(xua); + + OSMO_ASSERT(asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA); + + if (!msg) + return -1; + + return osmo_ss7_asp_send(asp, msg); +} + +/*! \brief Send a given xUA message via a given M3UA Application Server + * \param[in] as Application Server through which to send \ref xua + * \param[in] xua xUA message to be sent + * \return 0 on success; negative on error */ +int m3ua_tx_xua_as(struct osmo_ss7_as *as, struct xua_msg *xua) +{ + struct msgb *msg; + int rc; + + OSMO_ASSERT(as->cfg.proto == OSMO_SS7_ASP_PROT_M3UA); + + /* Add RC for this AS */ + if (as->cfg.routing_key.context) + xua_msg_add_u32(xua, M3UA_IEI_ROUTE_CTX, as->cfg.routing_key.context); + + msg = m3ua_to_msg(xua); + if (!msg) + return -1; + + /* send the msg to the AS for transmission. The AS FSM might + * (depending on its state) enqueue it before trnsmission */ + rc = osmo_fsm_inst_dispatch(as->fi, XUA_AS_E_TRANSFER_REQ, msg); + if (rc < 0) + msgb_free(msg); + return rc; +} + +/*********************************************************************** + * Receiving M3UA messsages from SCTP + ***********************************************************************/ + +/* obtain the destination point code from a M3UA message in XUA fmt * */ +struct m3ua_data_hdr *data_hdr_from_m3ua(struct xua_msg *xua) +{ + struct xua_msg_part *data_ie; + struct m3ua_data_hdr *data_hdr; + + if (xua->hdr.msg_class != M3UA_MSGC_XFER || + xua->hdr.msg_type != M3UA_XFER_DATA) + return NULL; + + data_ie = xua_msg_find_tag(xua, M3UA_IEI_PROT_DATA); + if (!data_ie) + return NULL; + data_hdr = (struct m3ua_data_hdr *) data_ie->dat; + + return data_hdr; +} + +static int m3ua_rx_xfer(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + uint32_t rctx = xua_msg_get_u32(xua, M3UA_IEI_ROUTE_CTX); + struct m3ua_data_hdr *dh; + struct osmo_ss7_as *as; + + LOGPASP(asp, DLM3UA, LOGL_DEBUG, "m3ua_rx_xfer\n"); + + if (xua->hdr.msg_type != M3UA_XFER_DATA) { + LOGPASP(asp, DLM3UA, LOGL_ERROR, + "%s(): unsupported message type: %s\n", + __func__, + get_value_string(m3ua_xfer_msgt_names, xua->hdr.msg_type)); + return M3UA_ERR_UNSUPP_MSG_TYPE; + } + + /* Use routing context IE to look up the AS for which the + * message was received. */ + as = osmo_ss7_as_find_by_rctx(asp->inst, rctx); + if (!as) { + LOGPASP(asp, DLM3UA, LOGL_ERROR, + "%s(): invalid routing context: %u\n", + __func__, rctx); + return M3UA_ERR_INVAL_ROUT_CTX; + } + + /* Verify that this ASP is part of the AS. */ + if (!osmo_ss7_as_has_asp(as, asp)) { + LOGPASP(asp, DLM3UA, LOGL_ERROR, + "%s(): This Application Server Process is not part of the AS resolved by" + " routing context %u\n", + __func__, rctx); + return M3UA_ERR_NO_CONFGD_AS_FOR_ASP; + } + + /* FIXME: check for AS state == ACTIVE */ + + /* store the MTP-level information in the xua_msg for use by + * higher layer protocols */ + dh = data_hdr_from_m3ua(xua); + OSMO_ASSERT(dh); + m3ua_dh_to_xfer_param(&xua->mtp, dh); + LOGPASP(asp, DLM3UA, LOGL_DEBUG, + "%s(): M3UA data header: opc=%u=%s dpc=%u=%s\n", + __func__, xua->mtp.opc, osmo_ss7_pointcode_print(asp->inst, xua->mtp.opc), + xua->mtp.dpc, osmo_ss7_pointcode_print2(asp->inst, xua->mtp.dpc)); + + /* remove ROUTE_CTX as in the routing case we want to add a new + * routing context on the outbound side */ + xua_msg_free_tag(xua, M3UA_IEI_ROUTE_CTX); + + return m3ua_hmdc_rx_from_l2(asp->inst, xua); +} + +static int m3ua_rx_mgmt_err(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + uint32_t err_code = xua_msg_get_u32(xua, M3UA_IEI_ERR_CODE); + struct osmo_xlm_prim *prim; + + LOGPASP(asp, DLM3UA, LOGL_ERROR, "Received MGMT_ERR '%s': %s\n", + get_value_string(m3ua_err_names, err_code), + xua_msg_dump(xua, &xua_dialect_m3ua)); + + /* report this to layer manager */ + prim = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_ERROR, PRIM_OP_INDICATION); + prim->u.error.code = err_code; + xua_asp_send_xlm_prim(asp, prim); + + /* NEVER return != 0 here, as we cannot respont to an ERR + * message with another ERR! */ + return 0; +} + +static int m3ua_rx_mgmt_ntfy(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct osmo_xlm_prim_notify ntfy; + const char *type_name, *info_name; + struct osmo_xlm_prim *prim; + + m3ua_decode_notify(&ntfy, asp, xua); + + type_name = get_value_string(m3ua_ntfy_type_names, ntfy.status_type); + + switch (ntfy.status_type) { + case M3UA_NOTIFY_T_STATCHG: + info_name = get_value_string(m3ua_ntfy_stchg_names, + ntfy.status_info); + break; + case M3UA_NOTIFY_T_OTHER: + info_name = get_value_string(m3ua_ntfy_other_names, + ntfy.status_info); + break; + default: + info_name = "NULL"; + break; + } + LOGPASP(asp, DLM3UA, LOGL_NOTICE, "Received NOTIFY Type %s:%s (%s)\n", + type_name, info_name, + ntfy.info_string ? ntfy.info_string : ""); + + /* report this to layer manager */ + prim = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_NOTIFY, PRIM_OP_INDICATION); + prim->u.notify = ntfy; + xua_asp_send_xlm_prim(asp,prim); + + if (ntfy.info_string) + talloc_free(ntfy.info_string); + + return 0; +} + +static int m3ua_rx_mgmt(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + switch (xua->hdr.msg_type) { + case M3UA_MGMT_ERR: + return m3ua_rx_mgmt_err(asp, xua); + case M3UA_MGMT_NTFY: + return m3ua_rx_mgmt_ntfy(asp, xua); + default: + return M3UA_ERR_UNSUPP_MSG_TYPE; + } +} + +/* map from M3UA ASPSM/ASPTM to xua_asp_fsm event */ +static const struct xua_msg_event_map m3ua_aspxm_map[] = { + { M3UA_MSGC_ASPSM, M3UA_ASPSM_UP, XUA_ASP_E_ASPSM_ASPUP }, + { M3UA_MSGC_ASPSM, M3UA_ASPSM_DOWN, XUA_ASP_E_ASPSM_ASPDN }, + { M3UA_MSGC_ASPSM, M3UA_ASPSM_BEAT, XUA_ASP_E_ASPSM_BEAT }, + { M3UA_MSGC_ASPSM, M3UA_ASPSM_UP_ACK, XUA_ASP_E_ASPSM_ASPUP_ACK }, + { M3UA_MSGC_ASPSM, M3UA_ASPSM_DOWN_ACK, XUA_ASP_E_ASPSM_ASPDN_ACK }, + { M3UA_MSGC_ASPSM, M3UA_ASPSM_BEAT_ACK, XUA_ASP_E_ASPSM_BEAT_ACK }, + { M3UA_MSGC_ASPTM, M3UA_ASPTM_ACTIVE, XUA_ASP_E_ASPTM_ASPAC }, + { M3UA_MSGC_ASPTM, M3UA_ASPTM_INACTIVE, XUA_ASP_E_ASPTM_ASPIA }, + { M3UA_MSGC_ASPTM, M3UA_ASPTM_ACTIVE_ACK, XUA_ASP_E_ASPTM_ASPAC_ACK }, + { M3UA_MSGC_ASPTM, M3UA_ASPTM_INACTIVE_ACK, XUA_ASP_E_ASPTM_ASPIA_ACK }, +}; + + +static int m3ua_rx_asp(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + int event; + + /* map from the M3UA message class and message type to the XUA + * ASP FSM event number */ + event = xua_msg_event_map(xua, m3ua_aspxm_map, + ARRAY_SIZE(m3ua_aspxm_map)); + if (event < 0) + return M3UA_ERR_UNSUPP_MSG_TYPE; + + /* deliver that event to the ASP FSM */ + if (osmo_fsm_inst_dispatch(asp->fi, event, xua) < 0) + return M3UA_ERR_UNEXPECTED_MSG; + + return 0; +} + +/*! \brief process M3UA message received from socket + * \param[in] asp Application Server Process receiving \ref msg + * \param[in] msg received message buffer + * \returns 0 on success; negative on error */ +int m3ua_rx_msg(struct osmo_ss7_asp *asp, struct msgb *msg) +{ + struct xua_msg *xua = NULL, *err = NULL; + int rc = 0; + + OSMO_ASSERT(asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA); + + /* caller owns msg memory, we shall neither free it here nor + * keep references beyond the executin of this function and its + * callees */ + + xua = xua_from_msg(M3UA_VERSION, msgb_length(msg), msgb_data(msg)); + if (!xua) { + struct xua_common_hdr *hdr = (struct xua_common_hdr *) msg->data; + + LOGPASP(asp, DLM3UA, LOGL_ERROR, "Unable to parse incoming " + "M3UA message\n"); + + if (hdr->version != M3UA_VERSION) + err = m3ua_gen_error_msg(M3UA_ERR_INVALID_VERSION, msg); + else + err = m3ua_gen_error_msg(M3UA_ERR_PARAM_FIELD_ERR, msg); + goto out; + } + + LOGPASP(asp, DLM3UA, LOGL_DEBUG, "Received M3UA Message (%s)\n", + xua_hdr_dump(xua, &xua_dialect_m3ua)); + + if (!xua_dialect_check_all_mand_ies(&xua_dialect_m3ua, xua)) { + err = m3ua_gen_error_msg(M3UA_ERR_MISSING_PARAM, msg); + goto out; + } + + /* TODO: check if any AS configured in ASP */ + /* TODO: check for valid routing context */ + + switch (xua->hdr.msg_class) { + case M3UA_MSGC_XFER: + /* The DATA message MUST NOT be sent on stream 0. */ + if (msgb_sctp_stream(msg) == 0) { + rc = M3UA_ERR_INVAL_STREAM_ID; + break; + } + rc = m3ua_rx_xfer(asp, xua); + break; + case M3UA_MSGC_ASPSM: + case M3UA_MSGC_ASPTM: + rc = m3ua_rx_asp(asp, xua); + break; + case M3UA_MSGC_MGMT: + rc = m3ua_rx_mgmt(asp, xua); + break; + case M3UA_MSGC_RKM: + rc = m3ua_rx_rkm(asp, xua); + break; + case M3UA_MSGC_SNM: + /* FIXME */ + LOGPASP(asp, DLM3UA, LOGL_NOTICE, "Received unsupported M3UA " + "Message Class %u\n", xua->hdr.msg_class); + err = m3ua_gen_error_msg(M3UA_ERR_UNSUPP_MSG_CLASS, msg); + break; + default: + LOGPASP(asp, DLM3UA, LOGL_NOTICE, "Received unknown M3UA " + "Message Class %u\n", xua->hdr.msg_class); + err = m3ua_gen_error_msg(M3UA_ERR_UNSUPP_MSG_CLASS, msg); + break; + } + + if (rc > 0) + err = m3ua_gen_error_msg(rc, msg); + +out: + if (err) + m3ua_tx_xua_asp(asp, err); + + xua_msg_free(xua); + + return rc; +} diff --git a/src/mtp_pcap.c b/src/mtp_pcap.c new file mode 100644 index 0000000..052813f --- /dev/null +++ b/src/mtp_pcap.c @@ -0,0 +1,86 @@ +/* PCAP code from OpenBSC done by Holger Freyther */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include + +#include + +#include + +#define static_assert(exp, name) typedef int dummy##name [(exp) ? 1 : -1]; + +/* + * pcap writing of the misdn load + * pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat + */ +struct pcap_hdr { + uint32_t magic_number; + uint16_t version_major; + uint16_t version_minor; + int32_t thiszone; + uint32_t sigfigs; + uint32_t snaplen; + uint32_t network; +} __attribute__((packed)); + +struct pcaprec_hdr { + uint32_t ts_sec; + uint32_t ts_usec; + uint32_t incl_len; + uint32_t orig_len; +} __attribute__((packed)); + +int mtp_pcap_write_header(int fd) +{ + static struct pcap_hdr hdr = { + .magic_number = 0xa1b2c3d4, + .version_major = 2, + .version_minor = 4, + .thiszone = 0, + .sigfigs = 0, + .snaplen = 65535, + .network = 141, + }; + + return write(fd, &hdr, sizeof(hdr)); +} + +int mtp_pcap_write_msu(int fd, const uint8_t *data, int length) +{ + int rc_h, rc_d; + struct timeval tv; + struct pcaprec_hdr payload_header = { + .ts_sec = 0, + .ts_usec = 0, + .incl_len = length, + .orig_len = length, + }; + + gettimeofday(&tv, NULL); + payload_header.ts_sec = tv.tv_sec; + payload_header.ts_usec = tv.tv_usec; + + rc_h = write(fd, &payload_header, sizeof(payload_header)); + rc_d = write(fd, data, length); + + return rc_h == sizeof(payload_header) && rc_d == length; +} diff --git a/src/osmo_ss7.c b/src/osmo_ss7.c new file mode 100644 index 0000000..7b0b0e4 --- /dev/null +++ b/src/osmo_ss7.c @@ -0,0 +1,1874 @@ +/* Core SS7 Instance/Linkset/Link/AS/ASP Handling */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sccp_internal.h" +#include "xua_internal.h" +#include "xua_asp_fsm.h" +#include "xua_as_fsm.h" + +#define MAX_PC_STR_LEN 32 + +static bool ss7_initialized = false; + +LLIST_HEAD(osmo_ss7_instances); +static int32_t next_rctx = 1; +static int32_t next_l_rk_id = 1; + +struct value_string osmo_ss7_as_traffic_mode_vals[] = { + { OSMO_SS7_AS_TMOD_BCAST, "broadcast" }, + { OSMO_SS7_AS_TMOD_LOADSHARE, "loadshare" }, + { OSMO_SS7_AS_TMOD_ROUNDROBIN, "round-robin" }, + { OSMO_SS7_AS_TMOD_OVERRIDE, "override" }, + { 0, NULL } +}; + +struct value_string osmo_ss7_asp_protocol_vals[] = { + { OSMO_SS7_ASP_PROT_NONE, "none" }, + { OSMO_SS7_ASP_PROT_SUA, "sua" }, + { OSMO_SS7_ASP_PROT_M3UA, "m3ua" }, + { OSMO_SS7_ASP_PROT_IPA, "ipa" }, + { 0, NULL } +}; + +#define LOGSS7(inst, level, fmt, args ...) \ + LOGP(DLSS7, level, "%u: " fmt, inst ? (inst)->cfg.id : 0, ## args) + +static int asp_proto_to_ip_proto(enum osmo_ss7_asp_protocol proto) +{ + switch (proto) { + case OSMO_SS7_ASP_PROT_IPA: + return IPPROTO_TCP; + case OSMO_SS7_ASP_PROT_SUA: + case OSMO_SS7_ASP_PROT_M3UA: + default: + return IPPROTO_SCTP; + } +} + +int osmo_ss7_find_free_rctx(struct osmo_ss7_instance *inst) +{ + int32_t rctx; + + for (rctx = next_rctx; rctx; rctx = ++next_rctx) { + if (!osmo_ss7_as_find_by_rctx(inst, next_rctx)) + return rctx; + } + return -1; +} + +static uint32_t find_free_l_rk_id(struct osmo_ss7_instance *inst) +{ + uint32_t l_rk_id; + + for (l_rk_id = next_l_rk_id; next_l_rk_id; l_rk_id = ++next_l_rk_id) { + if (!osmo_ss7_as_find_by_l_rk_id(inst, next_l_rk_id)) + return l_rk_id; + } + return -1; +} + + +/*********************************************************************** + * SS7 Point Code Parsing / Printing + ***********************************************************************/ + +static const struct osmo_ss7_pc_fmt default_pc_fmt = { + .delimiter = '.', + .component_len = { 3, 8, 3}, +}; + +/* like strcat() but appends a single character */ +static int strnappendchar(char *str, char c, size_t n) +{ + unsigned int curlen = strlen(str); + + if (n < curlen + 2) + return -1; + + str[curlen] = c; + str[curlen+1] = '\0'; + + return curlen+1; +} + +/* generate a format string for formatting a point code. The result can + * e.g. be used with sscanf() or sprintf() */ +static const char *gen_pc_fmtstr(const struct osmo_ss7_pc_fmt *pc_fmt, + unsigned int *num_comp_exp) +{ + static char buf[MAX_PC_STR_LEN]; + unsigned int num_comp = 0; + + buf[0] = '\0'; + strcat(buf, "%u"); + num_comp++; + + if (pc_fmt->component_len[1] == 0) + goto out; + strnappendchar(buf, pc_fmt->delimiter, sizeof(buf)); + strcat(buf, "%u"); + num_comp++; + + if (pc_fmt->component_len[2] == 0) + goto out; + strnappendchar(buf, pc_fmt->delimiter, sizeof(buf)); + strcat(buf, "%u"); + num_comp++; +out: + if (num_comp_exp) + *num_comp_exp = num_comp; + return buf; +} + +/* get number of components we expect for a point code, depending on the + * configuration of this ss7_instance */ +static unsigned int num_pc_comp_exp(const struct osmo_ss7_pc_fmt *pc_fmt) +{ + unsigned int num_comp_exp = 1; + + if (pc_fmt->component_len[1]) + num_comp_exp++; + if (pc_fmt->component_len[2]) + num_comp_exp++; + + return num_comp_exp; +} + +/* get the total width (in bits) of the point-codes in this ss7_instance */ +static unsigned int get_pc_width(const struct osmo_ss7_pc_fmt *pc_fmt) +{ + return pc_fmt->component_len[0] + pc_fmt->component_len[1] + pc_fmt->component_len[2]; +} + +/* get the number of bits we must shift the given component of a point + * code in this ss7_instance */ +static unsigned int get_pc_comp_shift(const struct osmo_ss7_pc_fmt *pc_fmt, + unsigned int comp_num) +{ + uint32_t pc_width = get_pc_width(pc_fmt); + switch (comp_num) { + case 0: + return pc_width - pc_fmt->component_len[0]; + case 1: + return pc_width - pc_fmt->component_len[0] - pc_fmt->component_len[1]; + case 2: + return 0; + default: + return -EINVAL; + } +} + +static uint32_t pc_comp_shift_and_mask(const struct osmo_ss7_pc_fmt *pc_fmt, + unsigned int comp_num, uint32_t pc) +{ + unsigned int shift = get_pc_comp_shift(pc_fmt, comp_num); + uint32_t mask = (1 << pc_fmt->component_len[comp_num]) - 1; + + return (pc >> shift) & mask; +} + +/* parse a point code according to the structure configured for this + * ss7_instance */ +int osmo_ss7_pointcode_parse(struct osmo_ss7_instance *inst, const char *str) +{ + unsigned int component[3]; + const struct osmo_ss7_pc_fmt *pc_fmt = inst ? &inst->cfg.pc_fmt : &default_pc_fmt; + unsigned int num_comp_exp = num_pc_comp_exp(pc_fmt); + const char *fmtstr = gen_pc_fmtstr(pc_fmt, &num_comp_exp); + int i, rc; + + rc = sscanf(str, fmtstr, &component[0], &component[1], &component[2]); + /* ensure all components were parsed */ + if (rc != num_comp_exp) + goto err; + + /* check none of the component values exceeds what can be + * represented within its bit-width */ + for (i = 0; i < num_comp_exp; i++) { + if (component[i] >= (1 << pc_fmt->component_len[i])) + goto err; + } + + /* shift them all together */ + rc = (component[0] << get_pc_comp_shift(pc_fmt, 0)); + if (num_comp_exp > 1) + rc |= (component[1] << get_pc_comp_shift(pc_fmt, 1)); + if (num_comp_exp > 2) + rc |= (component[2] << get_pc_comp_shift(pc_fmt, 2)); + + return rc; + +err: + LOGSS7(inst, LOGL_NOTICE, "Error parsing Pointcode '%s'\n", str); + return -EINVAL; +} + +const char *_osmo_ss7_pointcode_print(char *buf, size_t len, const struct osmo_ss7_instance *inst, uint32_t pc) +{ + const struct osmo_ss7_pc_fmt *pc_fmt; + unsigned int num_comp_exp; + const char *fmtstr; + + if (!osmo_ss7_pc_is_valid(pc)) + return "(no PC)"; + + pc_fmt = inst ? &inst->cfg.pc_fmt : &default_pc_fmt; + num_comp_exp = num_pc_comp_exp(pc_fmt); + fmtstr = gen_pc_fmtstr(pc_fmt, &num_comp_exp); + OSMO_ASSERT(fmtstr); + snprintf(buf, len, fmtstr, + pc_comp_shift_and_mask(pc_fmt, 0, pc), + pc_comp_shift_and_mask(pc_fmt, 1, pc), + pc_comp_shift_and_mask(pc_fmt, 2, pc)); + + return buf; +} + + +/* print a pointcode according to the structure configured for this + * ss7_instance */ +const char *osmo_ss7_pointcode_print(const struct osmo_ss7_instance *inst, uint32_t pc) +{ + static char buf[MAX_PC_STR_LEN]; + return _osmo_ss7_pointcode_print(buf, sizeof(buf), inst, pc); +} + +/* same as osmo_ss7_pointcode_print() but using a separate buffer, useful for multiple point codes in the + * same LOGP/printf. */ +const char *osmo_ss7_pointcode_print2(const struct osmo_ss7_instance *inst, uint32_t pc) +{ + static char buf[MAX_PC_STR_LEN]; + return _osmo_ss7_pointcode_print(buf, sizeof(buf), inst, pc); +} + +int osmo_ss7_pointcode_parse_mask_or_len(struct osmo_ss7_instance *inst, const char *in) +{ + unsigned int width = get_pc_width(inst ? &inst->cfg.pc_fmt : &default_pc_fmt); + + if (in[0] == '/') { + /* parse mask by length */ + int masklen = atoi(in+1); + if (masklen < 0 || masklen > 32) + return -EINVAL; + if (masklen == 0) + return 0; + return (0xFFFFFFFF << (width - masklen)) & ((1 << width)-1); + } else { + /* parse mask as point code */ + return osmo_ss7_pointcode_parse(inst, in); + } +} + +static const uint16_t prot2port[] = { + [OSMO_SS7_ASP_PROT_NONE] = 0, + [OSMO_SS7_ASP_PROT_SUA] = SUA_PORT, + [OSMO_SS7_ASP_PROT_M3UA] = M3UA_PORT, +}; + +int osmo_ss7_asp_protocol_port(enum osmo_ss7_asp_protocol prot) +{ + if (prot >= ARRAY_SIZE(prot2port)) + return -EINVAL; + else + return prot2port[prot]; +} + +/*********************************************************************** + * SS7 Instance + ***********************************************************************/ + +/*! \brief Find a SS7 Instance with given ID + * \param[in] id ID for which to search + * \returns \ref osmo_ss7_instance on success; NULL on error */ +struct osmo_ss7_instance * +osmo_ss7_instance_find(uint32_t id) +{ + OSMO_ASSERT(ss7_initialized); + + struct osmo_ss7_instance *inst; + llist_for_each_entry(inst, &osmo_ss7_instances, list) { + if (inst->cfg.id == id) + return inst; + } + return NULL; +} + +/*! \brief Find or create a SS7 Instance + * \param[in] ctx talloc allocation context to use for allocations + * \param[in] id ID of SS7 Instance + * \returns \ref osmo_ss7_instance on success; NULL on error */ +struct osmo_ss7_instance * +osmo_ss7_instance_find_or_create(void *ctx, uint32_t id) +{ + struct osmo_ss7_instance *inst; + + OSMO_ASSERT(ss7_initialized); + + inst = osmo_ss7_instance_find(id); + if (inst) + return inst; + + inst = talloc_zero(ctx, struct osmo_ss7_instance); + if (!inst) + return NULL; + + inst->cfg.primary_pc = OSMO_SS7_PC_INVALID; + + inst->cfg.id = id; + LOGSS7(inst, LOGL_INFO, "Creating SS7 Instance\n"); + + INIT_LLIST_HEAD(&inst->linksets); + INIT_LLIST_HEAD(&inst->as_list); + INIT_LLIST_HEAD(&inst->asp_list); + INIT_LLIST_HEAD(&inst->rtable_list); + INIT_LLIST_HEAD(&inst->xua_servers); + inst->rtable_system = osmo_ss7_route_table_find_or_create(inst, "system"); + + /* default point code structure + formatting */ + inst->cfg.pc_fmt.delimiter = '.'; + inst->cfg.pc_fmt.component_len[0] = 3; + inst->cfg.pc_fmt.component_len[1] = 8; + inst->cfg.pc_fmt.component_len[2] = 3; + + llist_add(&inst->list, &osmo_ss7_instances); + + INIT_LLIST_HEAD(&inst->cfg.sccp_address_book); + + return inst; +} + +/*! \brief Destroy a SS7 Instance + * \param[in] inst SS7 Instance to be destroyed */ +void osmo_ss7_instance_destroy(struct osmo_ss7_instance *inst) +{ + struct osmo_ss7_linkset *lset; + struct osmo_ss7_as *as; + struct osmo_ss7_asp *asp; + + OSMO_ASSERT(ss7_initialized); + LOGSS7(inst, LOGL_INFO, "Destroying SS7 Instance\n"); + + llist_for_each_entry(asp, &inst->asp_list, list) + osmo_ss7_asp_destroy(asp); + + llist_for_each_entry(as, &inst->as_list, list) + osmo_ss7_as_destroy(as); + + llist_for_each_entry(lset, &inst->linksets, list) + osmo_ss7_linkset_destroy(lset); + + llist_del(&inst->list); + talloc_free(inst); +} + +/*! \brief Set the point code format used in given SS7 instance */ +int osmo_ss7_instance_set_pc_fmt(struct osmo_ss7_instance *inst, + uint8_t c0, uint8_t c1, uint8_t c2) +{ + if (c0+c1+c2 > 32) + return -EINVAL; + + if (c0+c1+c2 > 14) + LOGSS7(inst, LOGL_NOTICE, "Point Code Format %u-%u-%u " + "is longer than 14 bits, odd?\n", c0, c1, c2); + + inst->cfg.pc_fmt.component_len[0] = c0; + inst->cfg.pc_fmt.component_len[1] = c1; + inst->cfg.pc_fmt.component_len[2] = c2; + + return 0; +} + +/*********************************************************************** + * MTP Users (Users of MTP, such as SCCP or ISUP) + ***********************************************************************/ + +/*! \brief Register a MTP user for a given service indicator + * \param[in] inst SS7 instance for which we register the user + * \param[in] service_ind Service (ISUP, SCCP, ...) + * \param[in] user SS7 user (including primitive call-back) + * \returns 0 on success; negative on error */ +int osmo_ss7_user_register(struct osmo_ss7_instance *inst, uint8_t service_ind, + struct osmo_ss7_user *user) +{ + if (service_ind >= ARRAY_SIZE(inst->user)) + return -EINVAL; + + if (inst->user[service_ind]) + return -EBUSY; + + DEBUGP(DLSS7, "registering user=%s for SI %u with priv %p\n", + user->name, service_ind, user->priv); + + user->inst = inst; + inst->user[service_ind] = user; + + return 0; +} + +/*! \brief Unregister a MTP user for a given service indicator + * \param[in] inst SS7 instance for which we register the user + * \param[in] service_ind Service (ISUP, SCCP, ...) + * \param[in] user (optional) SS7 user. If present, we will not + * unregister other users + * \returns 0 on success; negative on error */ +int osmo_ss7_user_unregister(struct osmo_ss7_instance *inst, uint8_t service_ind, + struct osmo_ss7_user *user) +{ + if (service_ind >= ARRAY_SIZE(inst->user)) + return -EINVAL; + + if (!inst->user[service_ind]) + return -ENODEV; + + if (user && (inst->user[service_ind] != user)) + return -EINVAL; + + if (user) + user->inst = NULL; + inst->user[service_ind] = NULL; + + return 0; +} + +/* deliver to a local MTP user */ +int osmo_ss7_mtp_to_user(struct osmo_ss7_instance *inst, struct osmo_mtp_prim *omp) +{ + uint32_t service_ind; + const struct osmo_ss7_user *osu; + + if (omp->oph.sap != MTP_SAP_USER || + omp->oph.primitive != OSMO_MTP_PRIM_TRANSFER || + omp->oph.operation != PRIM_OP_INDICATION) { + LOGP(DLSS7, LOGL_ERROR, "Unsupported Primitive\n"); + return -EINVAL; + } + + service_ind = omp->u.transfer.sio & 0xF; + osu = inst->user[service_ind]; + + if (!osu) { + LOGP(DLSS7, LOGL_NOTICE, "No MTP-User for SI %u\n", service_ind); + return -ENODEV; + } + + DEBUGP(DLSS7, "delivering MTP-TRANSFER.ind to user %s, priv=%p\n", + osu->name, osu->priv); + return osu->prim_cb(&omp->oph, (void *) osu->priv); +} + +/*********************************************************************** + * SS7 Linkset + ***********************************************************************/ + +/*! \brief Destroy a SS7 Linkset + * \param[in] lset Linkset to be destroyed */ +void osmo_ss7_linkset_destroy(struct osmo_ss7_linkset *lset) +{ + struct osmo_ss7_route *rt, *rt2; + unsigned int i; + + OSMO_ASSERT(ss7_initialized); + LOGSS7(lset->inst, LOGL_INFO, "Destroying Linkset %s\n", + lset->cfg.name); + + /* find any routes pointing to this AS and remove them */ + llist_for_each_entry_safe(rt, rt2, &lset->inst->rtable_system->routes, list) { + if (rt->dest.linkset == lset) + osmo_ss7_route_destroy(rt); + } + + for (i = 0; i < ARRAY_SIZE(lset->links); i++) { + struct osmo_ss7_link *link = lset->links[i]; + if (!link) + continue; + osmo_ss7_link_destroy(link); + } + llist_del(&lset->list); + talloc_free(lset); +} + +/*! \brief Find SS7 Linkset by given name + * \param[in] inst SS7 Instance in which to look + * \param[in] name Name of SS7 Linkset + * \returns pointer to linkset on success; NULL on error */ +struct osmo_ss7_linkset * +osmo_ss7_linkset_find_by_name(struct osmo_ss7_instance *inst, const char *name) +{ + struct osmo_ss7_linkset *lset; + OSMO_ASSERT(ss7_initialized); + llist_for_each_entry(lset, &inst->linksets, list) { + if (!strcmp(name, lset->cfg.name)) + return lset; + } + return NULL; +} + +/*! \brief Find or allocate SS7 Linkset + * \param[in] inst SS7 Instance in which we operate + * \param[in] name Name of SS7 Linkset + * \param[in] pc Adjacent Pointcode + * \returns pointer to Linkset on success; NULL on error */ +struct osmo_ss7_linkset * +osmo_ss7_linkset_find_or_create(struct osmo_ss7_instance *inst, const char *name, uint32_t pc) +{ + struct osmo_ss7_linkset *lset; + + OSMO_ASSERT(ss7_initialized); + lset = osmo_ss7_linkset_find_by_name(inst, name); + if (lset && lset->cfg.adjacent_pc != pc) + return NULL; + + if (!lset) { + LOGSS7(inst, LOGL_INFO, "Creating Linkset %s\n", name); + lset = talloc_zero(inst, struct osmo_ss7_linkset); + lset->inst = inst; + lset->cfg.adjacent_pc = pc; + lset->cfg.name = talloc_strdup(lset, name); + llist_add_tail(&lset->list, &inst->linksets); + } + + return lset; +} + +/*********************************************************************** + * SS7 Link + ***********************************************************************/ + +/*! \brief Destryo SS7 Link + * \param[in] link SS7 Link to be destroyed */ +void osmo_ss7_link_destroy(struct osmo_ss7_link *link) +{ + struct osmo_ss7_linkset *lset = link->linkset; + + OSMO_ASSERT(ss7_initialized); + LOGSS7(lset->inst, LOGL_INFO, "Destroying Link %s:%u\n", + lset->cfg.name, link->cfg.id); + /* FIXME: do cleanup */ + lset->links[link->cfg.id] = NULL; + talloc_free(link); +} + +/*! \brief Find or create SS7 Link with given ID in given Linkset + * \param[in] lset SS7 Linkset on which we operate + * \param[in] id Link number within Linkset + * \returns pointer to SS7 Link on success; NULL on error */ +struct osmo_ss7_link * +osmo_ss7_link_find_or_create(struct osmo_ss7_linkset *lset, uint32_t id) +{ + struct osmo_ss7_link *link; + + OSMO_ASSERT(ss7_initialized); + if (id >= ARRAY_SIZE(lset->links)) + return NULL; + + if (lset->links[id]) { + link = lset->links[id]; + } else { + LOGSS7(lset->inst, LOGL_INFO, "Creating Link %s:%u\n", + lset->cfg.name, id); + link = talloc_zero(lset, struct osmo_ss7_link); + if (!link) + return NULL; + link->linkset = lset; + lset->links[id] = link; + link->cfg.id = id; + } + + return link; +} + + +/*********************************************************************** + * SS7 Route Tables + ***********************************************************************/ + +struct osmo_ss7_route_table * +osmo_ss7_route_table_find(struct osmo_ss7_instance *inst, const char *name) +{ + struct osmo_ss7_route_table *rtbl; + OSMO_ASSERT(ss7_initialized); + llist_for_each_entry(rtbl, &inst->rtable_list, list) { + if (!strcmp(rtbl->cfg.name, name)) + return rtbl; + } + return NULL; +} + +struct osmo_ss7_route_table * +osmo_ss7_route_table_find_or_create(struct osmo_ss7_instance *inst, const char *name) +{ + struct osmo_ss7_route_table *rtbl; + + OSMO_ASSERT(ss7_initialized); + rtbl = osmo_ss7_route_table_find(inst, name); + if (!rtbl) { + LOGSS7(inst, LOGL_INFO, "Creating Route Table %s\n", name); + rtbl = talloc_zero(inst, struct osmo_ss7_route_table); + rtbl->inst = inst; + rtbl->cfg.name = talloc_strdup(rtbl, name); + INIT_LLIST_HEAD(&rtbl->routes); + llist_add_tail(&rtbl->list, &inst->rtable_list); + } + return rtbl; +} + +void osmo_ss7_route_table_destroy(struct osmo_ss7_route_table *rtbl) +{ + llist_del(&rtbl->list); + /* routes are allocated as children of route table, will be + * automatically freed() */ + talloc_free(rtbl); +} + +/*********************************************************************** + * SS7 Routes + ***********************************************************************/ + +/*! \brief Find a SS7 route for given destination point code in given table */ +struct osmo_ss7_route * +osmo_ss7_route_find_dpc(struct osmo_ss7_route_table *rtbl, uint32_t dpc) +{ + struct osmo_ss7_route *rt; + + OSMO_ASSERT(ss7_initialized); + /* we assume the routes are sorted by mask length, i.e. more + * specific routes first, and less specific routes with shorter + * mask later */ + llist_for_each_entry(rt, &rtbl->routes, list) { + if ((dpc & rt->cfg.mask) == rt->cfg.pc) + return rt; + } + return NULL; +} + +/*! \brief Find a SS7 route for given destination point code + mask in given table */ +struct osmo_ss7_route * +osmo_ss7_route_find_dpc_mask(struct osmo_ss7_route_table *rtbl, uint32_t dpc, + uint32_t mask) +{ + struct osmo_ss7_route *rt; + + OSMO_ASSERT(ss7_initialized); + /* we assume the routes are sorted by mask length, i.e. more + * specific routes first, and less specific routes with shorter + * mask later */ + llist_for_each_entry(rt, &rtbl->routes, list) { + if (dpc == rt->cfg.pc && mask == rt->cfg.mask) + return rt; + } + return NULL; +} + +/*! \brief Find a SS7 route for given destination point code in given SS7 */ +struct osmo_ss7_route * +osmo_ss7_route_lookup(struct osmo_ss7_instance *inst, uint32_t dpc) +{ + OSMO_ASSERT(ss7_initialized); + return osmo_ss7_route_find_dpc(inst->rtable_system, dpc); +} + +/* insert the route in the ordered list of routes. The list is sorted by + * mask length, so that the more specific (longer mask) routes are + * first, while the less specific routes with shorter masks are last. + * Hence, the first matching route in a linear iteration is the most + * specific match. */ +static void route_insert_sorted(struct osmo_ss7_route_table *rtbl, + struct osmo_ss7_route *cmp) +{ + struct osmo_ss7_route *rt; + + llist_for_each_entry(rt, &rtbl->routes, list) { + if (rt->cfg.mask < cmp->cfg.mask) { + /* insert before the current entry */ + llist_add(&cmp->list, rt->list.prev); + return; + } + } + /* not added, i.e. no smaller mask length found: we are the + * smallest mask and thus should go last */ + llist_add_tail(&cmp->list, &rtbl->routes); +} + +/*! \brief Create a new route in the given routing table + * \param[in] rtbl Routing Table in which the route is to be created + * \param[in] pc Point Code of the destination of the route + * \param[in] mask Mask of the destination Point Code \ref pc + * \param[in] linkset_name string name of the linkset to be used + * \returns caller-allocated + initialized route, NULL on error + */ +struct osmo_ss7_route * +osmo_ss7_route_create(struct osmo_ss7_route_table *rtbl, uint32_t pc, + uint32_t mask, const char *linkset_name) +{ + struct osmo_ss7_route *rt; + struct osmo_ss7_linkset *lset; + struct osmo_ss7_as *as = NULL; + + OSMO_ASSERT(ss7_initialized); + lset = osmo_ss7_linkset_find_by_name(rtbl->inst, linkset_name); + if (!lset) { + as = osmo_ss7_as_find_by_name(rtbl->inst, linkset_name); + if (!as) + return NULL; + } + + rt = talloc_zero(rtbl, struct osmo_ss7_route); + if (!rt) + return NULL; + + rt->cfg.pc = pc; + rt->cfg.mask = mask; + rt->cfg.linkset_name = talloc_strdup(rt, linkset_name); + if (lset) { + rt->dest.linkset = lset; + LOGSS7(rtbl->inst, LOGL_INFO, "Creating route: pc=%u=%s mask=0x%x via linkset '%s'\n", + pc, osmo_ss7_pointcode_print(rtbl->inst, pc), mask, lset->cfg.name); + } else { + rt->dest.as = as; + LOGSS7(rtbl->inst, LOGL_INFO, "Creating route: pc=%u=%s mask=0x%x via AS '%s'\n", + pc, osmo_ss7_pointcode_print(rtbl->inst, pc), mask, as->cfg.name); + } + rt->rtable = rtbl; + + route_insert_sorted(rtbl, rt); + + return rt; +} + +/*! \brief Destroy a given SS7 route */ +void osmo_ss7_route_destroy(struct osmo_ss7_route *rt) +{ + OSMO_ASSERT(ss7_initialized); + llist_del(&rt->list); + talloc_free(rt); +} + +/*********************************************************************** + * SS7 Application Server + ***********************************************************************/ + +/*! \brief Find Application Server by given name + * \param[in] inst SS7 Instance on which we operate + * \param[in] name Name of AS + * \returns pointer to Application Server on success; NULL otherwise */ +struct osmo_ss7_as * +osmo_ss7_as_find_by_name(struct osmo_ss7_instance *inst, const char *name) +{ + struct osmo_ss7_as *as; + + OSMO_ASSERT(ss7_initialized); + llist_for_each_entry(as, &inst->as_list, list) { + if (!strcmp(name, as->cfg.name)) + return as; + } + return NULL; +} + +/*! \brief Find Application Server by given routing context + * \param[in] inst SS7 Instance on which we operate + * \param[in] rctx Routing Context + * \returns pointer to Application Server on success; NULL otherwise */ +struct osmo_ss7_as * +osmo_ss7_as_find_by_rctx(struct osmo_ss7_instance *inst, uint32_t rctx) +{ + struct osmo_ss7_as *as; + + OSMO_ASSERT(ss7_initialized); + llist_for_each_entry(as, &inst->as_list, list) { + if (as->cfg.routing_key.context == rctx) + return as; + } + return NULL; +} + +/*! \brief Find Application Server by given local routing key ID + * \param[in] inst SS7 Instance on which we operate + * \param[in] l_rk_id Local Routing Key ID + * \returns pointer to Application Server on success; NULL otherwise */ +struct osmo_ss7_as * +osmo_ss7_as_find_by_l_rk_id(struct osmo_ss7_instance *inst, uint32_t l_rk_id) +{ + struct osmo_ss7_as *as; + + OSMO_ASSERT(ss7_initialized); + llist_for_each_entry(as, &inst->as_list, list) { + if (as->cfg.routing_key.l_rk_id == l_rk_id) + return as; + } + return NULL; +} + +/*! \brief Find Application Server (AS) by given protocol. + * \param[in] inst SS7 Instance on which we operate + * \param[in] proto Protocol identifier that must match + * \returns pointer to AS on success; NULL otherwise + * If an AS has an ASP also matching the given protocol, that AS is preferred. + * If there are multiple matches, return the first matching AS. */ +struct osmo_ss7_as *osmo_ss7_as_find_by_proto(struct osmo_ss7_instance *inst, + enum osmo_ss7_asp_protocol proto) +{ + struct osmo_ss7_as *as; + struct osmo_ss7_as *as_without_asp = NULL; + + OSMO_ASSERT(ss7_initialized); + + /* Loop through the list with AS and try to find one where the proto + matches up */ + llist_for_each_entry(as, &inst->as_list, list) { + if (as->cfg.proto == proto) { + + /* Put down the first AS that matches the proto, just in + * case we will not find any matching ASP */ + if (!as_without_asp) + as_without_asp = as; + + /* Check if the candicate we have here has any suitable + * ASP */ + if (osmo_ss7_asp_find_by_proto(as, proto)) + return as; + } + } + + /* Return with the second best find, if there is any */ + return as_without_asp; +} + +/*! \brief Find or Create Application Server + * \param[in] inst SS7 Instance on which we operate + * \param[in] name Name of Application Server + * \param[in] proto Protocol of Application Server + * \returns pointer to Application Server on suuccess; NULL otherwise */ +struct osmo_ss7_as * +osmo_ss7_as_find_or_create(struct osmo_ss7_instance *inst, const char *name, + enum osmo_ss7_asp_protocol proto) +{ + struct osmo_ss7_as *as; + + OSMO_ASSERT(ss7_initialized); + as = osmo_ss7_as_find_by_name(inst, name); + + if (as && as->cfg.proto != proto) + return NULL; + + if (!as) { + LOGSS7(inst, LOGL_INFO, "Creating AS %s\n", name); + as = talloc_zero(inst, struct osmo_ss7_as); + if (!as) + return NULL; + as->inst = inst; + as->cfg.name = talloc_strdup(as, name); + as->cfg.proto = proto; + as->cfg.mode = OSMO_SS7_AS_TMOD_LOADSHARE; + as->cfg.recovery_timeout_msec = 2000; + as->cfg.routing_key.l_rk_id = find_free_l_rk_id(inst); + as->fi = xua_as_fsm_start(as, LOGL_DEBUG); + llist_add_tail(&as->list, &inst->as_list); + } + + return as; +} + +/*! \brief Add given ASP to given AS + * \param[in] as Application Server to which \ref asp is added + * \param[in] asp Application Server Process to be added to \ref as + * \returns 0 on success; negative in case of error */ +int osmo_ss7_as_add_asp(struct osmo_ss7_as *as, const char *asp_name) +{ + struct osmo_ss7_asp *asp; + unsigned int i; + + OSMO_ASSERT(ss7_initialized); + asp = osmo_ss7_asp_find_by_name(as->inst, asp_name); + if (!asp) + return -ENODEV; + + LOGSS7(as->inst, LOGL_INFO, "Adding ASP %s to AS %s\n", + asp->cfg.name, as->cfg.name); + + if (osmo_ss7_as_has_asp(as, asp)) + return 0; + + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + if (!as->cfg.asps[i]) { + as->cfg.asps[i] = asp; + return 0; + } + } + + return -ENOSPC; +} + +/*! \brief Delete given ASP from given AS + * \param[in] as Application Server from which \ref asp is deleted + * \param[in] asp Application Server Process to delete from \ref as + * \returns 0 on success; negative in case of error */ +int osmo_ss7_as_del_asp(struct osmo_ss7_as *as, const char *asp_name) +{ + struct osmo_ss7_asp *asp; + unsigned int i; + + OSMO_ASSERT(ss7_initialized); + asp = osmo_ss7_asp_find_by_name(as->inst, asp_name); + if (!asp) + return -ENODEV; + + LOGSS7(as->inst, LOGL_INFO, "Removing ASP %s from AS %s\n", + asp->cfg.name, as->cfg.name); + + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + if (as->cfg.asps[i] == asp) { + as->cfg.asps[i] = NULL; + return 0; + } + } + + return -EINVAL; +} + +/*! \brief Destroy given Application Server + * \param[in] as Application Server to destroy */ +void osmo_ss7_as_destroy(struct osmo_ss7_as *as) +{ + struct osmo_ss7_route *rt, *rt2; + + OSMO_ASSERT(ss7_initialized); + LOGSS7(as->inst, LOGL_INFO, "Destroying AS %s\n", as->cfg.name); + + if (as->fi) + osmo_fsm_inst_term(as->fi, OSMO_FSM_TERM_REQUEST, NULL); + + /* find any routes pointing to this AS and remove them */ + llist_for_each_entry_safe(rt, rt2, &as->inst->rtable_system->routes, list) { + if (rt->dest.as == as) + osmo_ss7_route_destroy(rt); + } + + as->inst = NULL; + llist_del(&as->list); + talloc_free(as); +} + +/*! \brief Determine if given AS contains ASP + * \param[in] as Application Server in which to look for \ref asp + * \param[in] asp Application Server Process to look for in \ref as + * \returns true in case \ref asp is part of \ref as; false otherwise */ +bool osmo_ss7_as_has_asp(struct osmo_ss7_as *as, + struct osmo_ss7_asp *asp) +{ + unsigned int i; + + OSMO_ASSERT(ss7_initialized); + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + if (as->cfg.asps[i] == asp) + return true; + } + return false; +} + +/*********************************************************************** + * SS7 Application Server Process + ***********************************************************************/ + +struct osmo_ss7_asp * +osmo_ss7_asp_find_by_name(struct osmo_ss7_instance *inst, const char *name) +{ + struct osmo_ss7_asp *asp; + + OSMO_ASSERT(ss7_initialized); + llist_for_each_entry(asp, &inst->asp_list, list) { + if (!strcmp(name, asp->cfg.name)) + return asp; + } + return NULL; +} + +static uint16_t get_in_port(struct sockaddr *sa) +{ + switch (sa->sa_family) { + case AF_INET: + return (((struct sockaddr_in*)sa)->sin_port); + case AF_INET6: + return (((struct sockaddr_in6*)sa)->sin6_port); + default: + return 0; + } +} + +/*! \brief Find an ASP definition matching the local+remote IP/PORT of given fd + * \param[in] fd socket descriptor of given socket + * \returns SS7 ASP in case a matching one is found; NULL otherwise */ +static struct osmo_ss7_asp * +osmo_ss7_asp_find_by_socket_addr(int fd) +{ + struct osmo_ss7_instance *inst; + struct sockaddr sa_l, sa_r; + socklen_t sa_len_l = sizeof(sa_l); + socklen_t sa_len_r = sizeof(sa_r); + char hostbuf_l[64], hostbuf_r[64]; + uint16_t local_port, remote_port; + int rc; + + OSMO_ASSERT(ss7_initialized); + /* convert local and remote IP to string */ + rc = getsockname(fd, &sa_l, &sa_len_l); + if (rc < 0) + return NULL; + rc = getnameinfo(&sa_l, sa_len_l, hostbuf_l, sizeof(hostbuf_l), + NULL, 0, NI_NUMERICHOST); + if (rc < 0) + return NULL; + local_port = ntohs(get_in_port(&sa_l)); + + rc = getpeername(fd, &sa_r, &sa_len_r); + if (rc < 0) + return NULL; + rc = getnameinfo(&sa_r, sa_len_r, hostbuf_r, sizeof(hostbuf_r), + NULL, 0, NI_NUMERICHOST); + if (rc < 0) + return NULL; + remote_port = ntohs(get_in_port(&sa_r)); + + /* check all instances for any ASP definition matching the + * address combination of local/remote ip/port */ + llist_for_each_entry(inst, &osmo_ss7_instances, list) { + struct osmo_ss7_asp *asp; + llist_for_each_entry(asp, &inst->asp_list, list) { + if (asp->cfg.local.port == local_port && + (!asp->cfg.remote.port ||asp->cfg.remote.port == remote_port) && + (!asp->cfg.local.host || !strcmp(asp->cfg.local.host, hostbuf_l)) && + (!asp->cfg.remote.host || !strcmp(asp->cfg.remote.host, hostbuf_r))) + return asp; + } + } + + return NULL; +} + +/*! \brief Find an ASP that matches the given protocol. + * \param[in] as Application Server in which to look for \ref asp + * \returns SS7 ASP in case a matching one is found; NULL otherwise */ +struct osmo_ss7_asp +*osmo_ss7_asp_find_by_proto(struct osmo_ss7_as *as, + enum osmo_ss7_asp_protocol proto) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + if (as->cfg.asps[i] && as->cfg.asps[i]->cfg.proto == proto) + return as->cfg.asps[i]; + } + + return NULL; +} + +struct osmo_ss7_asp * +osmo_ss7_asp_find_or_create(struct osmo_ss7_instance *inst, const char *name, + uint16_t remote_port, uint16_t local_port, + enum osmo_ss7_asp_protocol proto) +{ + struct osmo_ss7_asp *asp; + + OSMO_ASSERT(ss7_initialized); + asp = osmo_ss7_asp_find_by_name(inst, name); + + if (asp && (asp->cfg.remote.port != remote_port || + asp->cfg.local.port != local_port || + asp->cfg.proto != proto)) + return NULL; + + if (!asp) { + /* FIXME: check if local port has SCTP? */ + asp = talloc_zero(inst, struct osmo_ss7_asp); + asp->inst = inst; + asp->cfg.remote.port = remote_port; + asp->cfg.local.port = local_port; + asp->cfg.proto = proto; + asp->cfg.name = talloc_strdup(asp, name); + llist_add_tail(&asp->list, &inst->asp_list); + + /* The SUA code internally needs SCCP to work */ + if (proto == OSMO_SS7_ASP_PROT_SUA && !inst->sccp) + inst->sccp = osmo_sccp_instance_create(inst, NULL); + + } + return asp; +} + +void osmo_ss7_asp_destroy(struct osmo_ss7_asp *asp) +{ + struct osmo_ss7_as *as; + + OSMO_ASSERT(ss7_initialized); + LOGSS7(asp->inst, LOGL_INFO, "Destroying ASP %s\n", asp->cfg.name); + + if (asp->server) + osmo_stream_srv_destroy(asp->server); + if (asp->client) + osmo_stream_cli_destroy(asp->client); + if (asp->fi) + osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); + if (asp->xua_server) + llist_del(&asp->siblings); + + /* unlink from all ASs we are part of */ + llist_for_each_entry(as, &asp->inst->as_list, list) { + unsigned int i; + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + if (as->cfg.asps[i] == asp) { + as->cfg.asps[i] = NULL; + } + } + } + /* unlink from ss7_instance */ + asp->inst = NULL; + llist_del(&asp->list); + /* release memory */ + talloc_free(asp); +} + +static int xua_cli_read_cb(struct osmo_stream_cli *conn); +static int ipa_cli_read_cb(struct osmo_stream_cli *conn); +static int xua_cli_connect_cb(struct osmo_stream_cli *cli); + +int osmo_ss7_asp_restart(struct osmo_ss7_asp *asp) +{ + int rc; + enum xua_asp_role role; + + OSMO_ASSERT(ss7_initialized); + LOGSS7(asp->inst, LOGL_INFO, "Restarting ASP %s\n", asp->cfg.name); + + if (!asp->cfg.is_server) { + /* We are in client mode now */ + if (asp->server) { + /* if we previously were in server mode, + * destroy it */ + osmo_stream_srv_destroy(asp->server); + asp->server = NULL; + } + if (!asp->client) + asp->client = osmo_stream_cli_create(asp); + if (!asp->client) { + LOGSS7(asp->inst, LOGL_ERROR, "Unable to create stream" + " client for ASP %s\n", asp->cfg.name); + return -1; + } + osmo_stream_cli_set_nodelay(asp->client, true); + osmo_stream_cli_set_addr(asp->client, asp->cfg.remote.host); + osmo_stream_cli_set_port(asp->client, asp->cfg.remote.port); + osmo_stream_cli_set_local_addr(asp->client, asp->cfg.local.host); + osmo_stream_cli_set_local_port(asp->client, asp->cfg.local.port); + osmo_stream_cli_set_proto(asp->client, asp_proto_to_ip_proto(asp->cfg.proto)); + osmo_stream_cli_set_reconnect_timeout(asp->client, 5); + osmo_stream_cli_set_connect_cb(asp->client, xua_cli_connect_cb); + if (asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA) + osmo_stream_cli_set_read_cb(asp->client, ipa_cli_read_cb); + else + osmo_stream_cli_set_read_cb(asp->client, xua_cli_read_cb); + osmo_stream_cli_set_data(asp->client, asp); + rc = osmo_stream_cli_open2(asp->client, 1); + if (rc < 0) { + LOGSS7(asp->inst, LOGL_ERROR, "Unable to open stream" + " client for ASP %s\n", asp->cfg.name); + } + /* TODO: make this configurable and not implicit */ + role = XUA_ASPFSM_ROLE_ASP; + } else { + /* We are in server mode now */ + if (asp->client) { + /* if we previously were in client mode, + * destroy it */ + osmo_stream_cli_destroy(asp->client); + asp->client = NULL; + } + /* FIXME: ensure we have a SCTP server */ + LOGSS7(asp->inst, LOGL_NOTICE, "ASP Restart for server " + "not implemented yet!\n"); + /* TODO: make this configurable and not implicit */ + role = XUA_ASPFSM_ROLE_SG; + } + + /* (re)start the ASP FSM */ + if (asp->fi) + osmo_fsm_inst_term(asp->fi, OSMO_FSM_TERM_REQUEST, NULL); + asp->fi = xua_asp_fsm_start(asp, role, LOGL_DEBUG); + + return 0; +} + +/*********************************************************************** + * libosmo-netif integration for SCTP stream server/client + ***********************************************************************/ + +static const struct value_string sctp_assoc_chg_vals[] = { + { SCTP_COMM_UP, "COMM_UP" }, + { SCTP_COMM_LOST, "COMM_LOST" }, + { SCTP_RESTART, "RESTART" }, + { SCTP_SHUTDOWN_COMP, "SHUTDOWN_COMP" }, + { SCTP_CANT_STR_ASSOC, "CANT_STR_ASSOC" }, + { 0, NULL } +}; + +static const struct value_string sctp_sn_type_vals[] = { + { SCTP_ASSOC_CHANGE, "ASSOC_CHANGE" }, + { SCTP_PEER_ADDR_CHANGE, "PEER_ADDR_CHANGE" }, + { SCTP_SHUTDOWN_EVENT, "SHUTDOWN_EVENT" }, + { SCTP_SEND_FAILED, "SEND_FAILED" }, + { SCTP_REMOTE_ERROR, "REMOTE_ERROR" }, + { SCTP_PARTIAL_DELIVERY_EVENT, "PARTIAL_DELIVERY_EVENT" }, + { SCTP_ADAPTATION_INDICATION, "ADAPTATION_INDICATION" }, +#ifdef SCTP_AUTHENTICATION_INDICATION + { SCTP_AUTHENTICATION_INDICATION, "UTHENTICATION_INDICATION" }, +#endif +#ifdef SCTP_SENDER_DRY_EVENT + { SCTP_SENDER_DRY_EVENT, "SENDER_DRY_EVENT" }, +#endif + { 0, NULL } +}; + +static int get_logevel_by_sn_type(int sn_type) +{ + switch (sn_type) { + case SCTP_ADAPTATION_INDICATION: + case SCTP_PEER_ADDR_CHANGE: +#ifdef SCTP_AUTHENTICATION_INDICATION + case SCTP_AUTHENTICATION_INDICATION: +#endif +#ifdef SCTP_SENDER_DRY_EVENT + case SCTP_SENDER_DRY_EVENT: +#endif + return LOGL_INFO; + case SCTP_ASSOC_CHANGE: + return LOGL_NOTICE; + case SCTP_SHUTDOWN_EVENT: + case SCTP_PARTIAL_DELIVERY_EVENT: + return LOGL_NOTICE; + case SCTP_SEND_FAILED: + case SCTP_REMOTE_ERROR: + return LOGL_ERROR; + default: + return LOGL_NOTICE; + } +} + +static void log_sctp_notification(struct osmo_ss7_asp *asp, const char *pfx, + union sctp_notification *notif) +{ + int log_level; + + LOGPASP(asp, DLSS7, LOGL_INFO, "%s SCTP NOTIFICATION %u flags=0x%0x\n", + pfx, notif->sn_header.sn_type, + notif->sn_header.sn_flags); + + log_level = get_logevel_by_sn_type(notif->sn_header.sn_type); + + switch (notif->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + LOGPASP(asp, DLSS7, log_level, "%s SCTP_ASSOC_CHANGE: %s\n", + pfx, get_value_string(sctp_assoc_chg_vals, + notif->sn_assoc_change.sac_state)); + break; + default: + LOGPASP(asp, DLSS7, log_level, "%s %s\n", + pfx, get_value_string(sctp_sn_type_vals, + notif->sn_header.sn_type)); + break; + } +} + +/* netif code tells us we can read something from the socket */ +static int ipa_srv_conn_cb(struct osmo_stream_srv *conn) +{ + struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); + struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); + struct msgb *msg = NULL; + int rc; + + /* read IPA message from socket and process it */ + rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", + __func__, rc); + if (rc <= 0) { + if (rc == -EAGAIN) { + /* more data needed */ + return 0; + } + osmo_stream_srv_destroy(conn); + return rc; + } + if (osmo_ipa_process_msg(msg) < 0) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); + osmo_stream_srv_destroy(conn); + msgb_free(msg); + return -1; + } + msg->dst = asp; + + return ipa_rx_msg(asp, msg); +} + +/* netif code tells us we can read something from the socket */ +static int xua_srv_conn_cb(struct osmo_stream_srv *conn) +{ + struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); + struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(conn); + struct msgb *msg = m3ua_msgb_alloc("xUA Server Rx"); + struct sctp_sndrcvinfo sinfo; + unsigned int ppid; + int flags = 0; + int rc; + + if (!msg) + return -ENOMEM; + + /* read xUA message from socket and process it */ + rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg), + NULL, NULL, &sinfo, &flags); + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", + __func__, rc, flags); + if (rc < 0) { + osmo_stream_srv_destroy(conn); + goto out; + } else if (rc == 0) { + osmo_stream_srv_destroy(conn); + goto out; + } else { + msgb_put(msg, rc); + } + + if (flags & MSG_NOTIFICATION) { + union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); + + log_sctp_notification(asp, "xUA SRV", notif); + + switch (notif->sn_header.sn_type) { + case SCTP_SHUTDOWN_EVENT: + osmo_stream_srv_destroy(conn); + break; + case SCTP_ASSOC_CHANGE: + if (notif->sn_assoc_change.sac_state == SCTP_RESTART) + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, + PRIM_OP_INDICATION); + break; + default: + break; + } + rc = 0; + goto out; + } + + ppid = ntohl(sinfo.sinfo_ppid); + msgb_sctp_ppid(msg) = ppid; + msgb_sctp_stream(msg) = sinfo.sinfo_stream; + msg->dst = asp; + + if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) + rc = sua_rx_msg(asp, msg); + else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) + rc = m3ua_rx_msg(asp, msg); + else { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "SCTP chunk for unknown PPID %u " + "received\n", ppid); + rc = 0; + } + +out: + msgb_free(msg); + return rc; +} + +/* client has established SCTP connection to server */ +static int xua_cli_connect_cb(struct osmo_stream_cli *cli) +{ + struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); + + /* update the socket name */ + if (asp->sock_name) + talloc_free(asp->sock_name); + asp->sock_name = osmo_sock_get_name(asp, ofd->fd); + + LOGPASP(asp, DLSS7, LOGL_INFO, "Client connected %s\n", asp->sock_name); + + if (asp->lm && asp->lm->prim_cb) { + /* Notify layer manager that a connection has been + * established */ + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); + } else { + /* directly as the ASP FSM to start by sending an ASP-UP ... */ + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_M_ASP_UP_REQ, NULL); + } + + return 0; +} + +static void xua_cli_close(struct osmo_stream_cli *cli) +{ + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(cli); + + osmo_stream_cli_close(cli); + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, asp); + /* send M-SCTP_RELEASE.ind to XUA Layer Manager */ + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); +} + +static void xua_cli_close_and_reconnect(struct osmo_stream_cli *cli) +{ + xua_cli_close(cli); + osmo_stream_cli_reconnect(cli); +} + +/* read call-back for IPA/SCCPlite socket */ +static int ipa_cli_read_cb(struct osmo_stream_cli *conn) +{ + struct osmo_fd *ofd = osmo_stream_cli_get_ofd(conn); + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); + struct msgb *msg = NULL; + int rc; + + /* read IPA message from socket and process it */ + rc = ipa_msg_recv_buffered(ofd->fd, &msg, &asp->pending_msg); + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): ipa_msg_recv_buffered() returned %d\n", + __func__, rc); + if (rc <= 0) { + if (rc == -EAGAIN) { + /* more data needed */ + return 0; + } + xua_cli_close_and_reconnect(conn); + return rc; + } + if (osmo_ipa_process_msg(msg) < 0) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Bad IPA message\n"); + xua_cli_close_and_reconnect(conn); + msgb_free(msg); + return -1; + } + msg->dst = asp; + return ipa_rx_msg(asp, msg); +} + +static int xua_cli_read_cb(struct osmo_stream_cli *conn) +{ + struct osmo_fd *ofd = osmo_stream_cli_get_ofd(conn); + struct osmo_ss7_asp *asp = osmo_stream_cli_get_data(conn); + struct msgb *msg = m3ua_msgb_alloc("xUA Client Rx"); + struct sctp_sndrcvinfo sinfo; + unsigned int ppid; + int flags = 0; + int rc; + + if (!msg) + return -ENOMEM; + + /* read xUA message from socket and process it */ + rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg), + NULL, NULL, &sinfo, &flags); + LOGPASP(asp, DLSS7, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", + __func__, rc, flags); + if (rc < 0) { + xua_cli_close_and_reconnect(conn); + goto out; + } else if (rc == 0) { + xua_cli_close_and_reconnect(conn); + } else { + msgb_put(msg, rc); + } + + if (flags & MSG_NOTIFICATION) { + union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); + + log_sctp_notification(asp, "xUA CLNT", notif); + + switch (notif->sn_header.sn_type) { + case SCTP_SHUTDOWN_EVENT: + xua_cli_close_and_reconnect(conn); + break; + case SCTP_ASSOC_CHANGE: + if (notif->sn_assoc_change.sac_state == SCTP_RESTART) + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RESTART, + PRIM_OP_INDICATION); + default: + break; + } + rc = 0; + goto out; + } + + if (rc == 0) + goto out; + + ppid = ntohl(sinfo.sinfo_ppid); + msgb_sctp_ppid(msg) = ppid; + msgb_sctp_stream(msg) = sinfo.sinfo_stream; + msg->dst = asp; + + if (ppid == SUA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA) + rc = sua_rx_msg(asp, msg); + else if (ppid == M3UA_PPID && asp->cfg.proto == OSMO_SS7_ASP_PROT_M3UA) + rc = m3ua_rx_msg(asp, msg); + else { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "SCTP chunk for unknown PPID %u " + "received\n", ppid); + rc = 0; + } + +out: + msgb_free(msg); + return rc; +} + +static int xua_srv_conn_closed_cb(struct osmo_stream_srv *srv) +{ + struct osmo_ss7_asp *asp = osmo_stream_srv_get_data(srv); + + LOGP(DLSS7, LOGL_INFO, "%s: SCTP connection closed\n", + asp ? asp->cfg.name : "?"); + + if (!asp) + return 0; + + /* notify ASP FSM and everyone else */ + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_COMM_DOWN_IND, NULL); + + /* delete any RKM-dynamically allocated ASs for this ASP */ + xua_rkm_cleanup_dyn_as_for_asp(asp); + + /* send M-SCTP_RELEASE.ind to Layer Manager */ + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION); + + /* if we were dynamically allocated at accept_cb() time, let's + * self-destruct now. A new connection will re-create the ASP. */ + if (asp->dyn_allocated) { + /* avoid re-entrance via osmo_stream_srv_destroy() which + * called us */ + asp->server = NULL; + osmo_ss7_asp_destroy(asp); + } + + return 0; +} + + +/* server has accept()ed a new SCTP association, let's find the ASP for + * it (if any) */ +static int xua_accept_cb(struct osmo_stream_srv_link *link, int fd) +{ + struct osmo_xua_server *oxs = osmo_stream_srv_link_get_data(link); + struct osmo_stream_srv *srv; + struct osmo_ss7_asp *asp; + char *sock_name = osmo_sock_get_name(link, fd); + + LOGP(DLSS7, LOGL_INFO, "%s: New %s connection accepted\n", + sock_name, get_value_string(osmo_ss7_asp_protocol_vals, oxs->cfg.proto)); + + if (oxs->cfg.proto == OSMO_SS7_ASP_PROT_IPA) { + srv = osmo_stream_srv_create(oxs, link, fd, + ipa_srv_conn_cb, + xua_srv_conn_closed_cb, NULL); + } else { + srv = osmo_stream_srv_create(oxs, link, fd, + xua_srv_conn_cb, + xua_srv_conn_closed_cb, NULL); + } + if (!srv) { + LOGP(DLSS7, LOGL_ERROR, "%s: Unable to create stream server " + "for connection\n", sock_name); + close(fd); + talloc_free(sock_name); + return -1; + } + + asp = osmo_ss7_asp_find_by_socket_addr(fd); + if (asp) { + LOGP(DLSS7, LOGL_INFO, "%s: matched connection to ASP %s\n", + sock_name, asp->cfg.name); + } else { + if (!oxs->cfg.accept_dyn_reg) { + LOGP(DLSS7, LOGL_NOTICE, "%s: SCTP connection without matching " + "ASP definition and no dynamic registration enabled, terminating\n", + sock_name); + } else { + char namebuf[32]; + static uint32_t dyn_asp_num = 0; + snprintf(namebuf, sizeof(namebuf), "asp-dyn-%u", dyn_asp_num++); + asp = osmo_ss7_asp_find_or_create(oxs->inst, namebuf, 0, 0, + oxs->cfg.proto); + if (asp) { + LOGP(DLSS7, LOGL_INFO, "%s: created dynamicASP %s\n", + sock_name, asp->cfg.name); + asp->cfg.is_server = true; + asp->dyn_allocated = true; + asp->server = srv; + osmo_ss7_asp_restart(asp); + } + } + if (!asp) { + osmo_stream_srv_destroy(srv); + talloc_free(sock_name); + return -1; + } + } + + /* update the ASP reference back to the server over which the + * connection came in */ + asp->server = srv; + asp->xua_server = oxs; + llist_add_tail(&asp->siblings, &oxs->asp_list); + /* update the ASP socket name */ + if (asp->sock_name) + talloc_free(asp->sock_name); + asp->sock_name = talloc_reparent(link, asp, sock_name); + /* make sure the conn_cb() is called with the asp as private + * data */ + osmo_stream_srv_set_data(srv, asp); + + /* send M-SCTP_ESTABLISH.ind to Layer Manager */ + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_SCTP_EST_IND, 0); + xua_asp_send_xlm_prim_simple(asp, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION); + + return 0; +} + +/*! \brief send a fully encoded msgb via a given ASP + * \param[in] asp Application Server Process through which to send + * \param[in] msg message buffer to transmit. Ownership transferred. + * \returns 0 on success; negative in case of error */ +int osmo_ss7_asp_send(struct osmo_ss7_asp *asp, struct msgb *msg) +{ + OSMO_ASSERT(ss7_initialized); + + switch (asp->cfg.proto) { + case OSMO_SS7_ASP_PROT_SUA: + msgb_sctp_ppid(msg) = SUA_PPID; + break; + case OSMO_SS7_ASP_PROT_M3UA: + msgb_sctp_ppid(msg) = M3UA_PPID; + break; + case OSMO_SS7_ASP_PROT_IPA: + break; + default: + OSMO_ASSERT(0); + } + + if (asp->cfg.is_server) { + if (!asp->server) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->server\n"); + /* FIXME: what to do here? delete the route? send DUNA? */ + msgb_free(msg); + return -EIO; + } + osmo_stream_srv_send(asp->server, msg); + } else { + if (!asp->client) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "Cannot transmit, no asp->client\n"); + /* FIXME: what to do here? delete the route? send DUNA? */ + msgb_free(msg); + return -EIO; + } + osmo_stream_cli_send(asp->client, msg); + } + + return 0; +} + +void osmo_ss7_asp_disconnect(struct osmo_ss7_asp *asp) +{ + if (asp->server) + osmo_stream_srv_destroy(asp->server); + /* the close_cb() will handle the remaining cleanup here */ + else if (asp->client) + xua_cli_close_and_reconnect(asp->client); +} + +/*********************************************************************** + * SS7 xUA Server + ***********************************************************************/ + +struct osmo_xua_server * +osmo_ss7_xua_server_find(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, + uint16_t local_port) +{ + struct osmo_xua_server *xs; + + OSMO_ASSERT(ss7_initialized); + llist_for_each_entry(xs, &inst->xua_servers, list) { + if (proto == xs->cfg.proto && + local_port == xs->cfg.local.port) + return xs; + } + return NULL; +} + +/*! \brief create a new xUA server listening to given ip/port + * \param[in] ctx talloc allocation context + * \param[in] proto protocol (xUA variant) to use + * \param[in] local_port local SCTP port to bind/listen to + * \param[in] local_host local IP address to bind/listen to (optional) + * \returns callee-allocated \ref osmo_xua_server in case of success + */ +struct osmo_xua_server * +osmo_ss7_xua_server_create(struct osmo_ss7_instance *inst, enum osmo_ss7_asp_protocol proto, + uint16_t local_port, const char *local_host) +{ + struct osmo_xua_server *oxs = talloc_zero(inst, struct osmo_xua_server); + int rc; + + OSMO_ASSERT(ss7_initialized); + if (!oxs) + return NULL; + + LOGP(DLSS7, LOGL_INFO, "Creating %s Server %s:%u\n", + get_value_string(osmo_ss7_asp_protocol_vals, proto), local_host, local_port); + + INIT_LLIST_HEAD(&oxs->asp_list); + + oxs->cfg.proto = proto; + oxs->cfg.local.port = local_port; + oxs->cfg.local.host = talloc_strdup(oxs, local_host); + + oxs->server = osmo_stream_srv_link_create(oxs); + osmo_stream_srv_link_set_data(oxs->server, oxs); + osmo_stream_srv_link_set_accept_cb(oxs->server, xua_accept_cb); + + osmo_stream_srv_link_set_nodelay(oxs->server, true); + osmo_stream_srv_link_set_addr(oxs->server, oxs->cfg.local.host); + osmo_stream_srv_link_set_port(oxs->server, oxs->cfg.local.port); + osmo_stream_srv_link_set_proto(oxs->server, asp_proto_to_ip_proto(proto)); + + rc = osmo_stream_srv_link_open(oxs->server); + if (rc < 0) { + osmo_stream_srv_link_destroy(oxs->server); + oxs->server = NULL; + talloc_free(oxs); + } + + oxs->inst = inst; + llist_add_tail(&oxs->list, &inst->xua_servers); + + /* The SUA code internally needs SCCP to work */ + if (proto == OSMO_SS7_ASP_PROT_SUA && !inst->sccp) + inst->sccp = osmo_sccp_instance_create(inst, NULL); + + return oxs; +} + +int +osmo_ss7_xua_server_set_local_host(struct osmo_xua_server *xs, const char *local_host) +{ + OSMO_ASSERT(ss7_initialized); + osmo_talloc_replace_string(xs, &xs->cfg.local.host, local_host); + + osmo_stream_srv_link_set_addr(xs->server, xs->cfg.local.host); + + return 0; +} + +void osmo_ss7_xua_server_destroy(struct osmo_xua_server *xs) +{ + struct osmo_ss7_asp *asp, *asp2; + + if (xs->server) { + osmo_stream_srv_link_close(xs->server); + osmo_stream_srv_link_destroy(xs->server); + } + /* iterate and close all connections established in relation + * with this server */ + llist_for_each_entry_safe(asp, asp2, &xs->asp_list, siblings) + osmo_ss7_asp_destroy(asp); + + llist_del(&xs->list); + talloc_free(xs); +} + +bool osmo_ss7_pc_is_local(struct osmo_ss7_instance *inst, uint32_t pc) +{ + OSMO_ASSERT(ss7_initialized); + if (osmo_ss7_pc_is_valid(inst->cfg.primary_pc) && pc == inst->cfg.primary_pc) + return true; + /* FIXME: Secondary and Capability Point Codes */ + return false; +} + +int osmo_ss7_init(void) +{ + if (ss7_initialized) + return 1; + osmo_fsm_register(&sccp_scoc_fsm); + osmo_fsm_register(&xua_as_fsm); + osmo_fsm_register(&xua_asp_fsm); + osmo_fsm_register(&ipa_asp_fsm); + osmo_fsm_register(&xua_default_lm_fsm); + ss7_initialized = true; + return 0; +} + +int osmo_ss7_tmode_to_xua(enum osmo_ss7_as_traffic_mode tmod) +{ + switch (tmod) { + case OSMO_SS7_AS_TMOD_OVERRIDE: + return M3UA_TMOD_OVERRIDE; + case OSMO_SS7_AS_TMOD_LOADSHARE: + return M3UA_TMOD_LOADSHARE; + case OSMO_SS7_AS_TMOD_BCAST: + return M3UA_TMOD_BCAST; + default: + return -1; + } +} + +enum osmo_ss7_as_traffic_mode osmo_ss7_tmode_from_xua(uint32_t in) +{ + switch (in) { + case M3UA_TMOD_OVERRIDE: + default: + return OSMO_SS7_AS_TMOD_OVERRIDE; + case M3UA_TMOD_LOADSHARE: + return OSMO_SS7_AS_TMOD_LOADSHARE; + case M3UA_TMOD_BCAST: + return OSMO_SS7_AS_TMOD_BCAST; + } +} diff --git a/src/osmo_ss7_hmrt.c b/src/osmo_ss7_hmrt.c new file mode 100644 index 0000000..8165a36 --- /dev/null +++ b/src/osmo_ss7_hmrt.c @@ -0,0 +1,229 @@ +/*********************************************************************** + * MTP Level 3 - Signalling message handling (SMH) Figure 23/Q.704 + ***********************************************************************/ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "xua_internal.h" + +/* convert from M3UA message to MTP-TRANSFER.ind osmo_mtp_prim */ +struct osmo_mtp_prim *m3ua_to_xfer_ind(struct xua_msg *xua) +{ + struct osmo_mtp_prim *prim; + struct osmo_mtp_transfer_param *param; + struct xua_msg_part *data_ie = xua_msg_find_tag(xua, M3UA_IEI_PROT_DATA); + struct m3ua_data_hdr *data_hdr; + struct msgb *upmsg = m3ua_msgb_alloc("M3UA MTP-TRANSFER.ind"); + + if (!data_ie || data_ie->len < sizeof(*data_hdr)) { + /* FIXME: ERROR message */ + msgb_free(upmsg); + return NULL; + } + data_hdr = (struct m3ua_data_hdr *) data_ie->dat; + + /* fill primitive */ + prim = (struct osmo_mtp_prim *) msgb_put(upmsg, sizeof(*prim)); + param = &prim->u.transfer; + osmo_prim_init(&prim->oph, MTP_SAP_USER, + OSMO_MTP_PRIM_TRANSFER, + PRIM_OP_INDICATION, upmsg); + + m3ua_dh_to_xfer_param(param, data_hdr); + /* copy data */ + upmsg->l2h = msgb_put(upmsg, data_ie->len - sizeof(*data_hdr)); + memcpy(upmsg->l2h, data_ie->dat+sizeof(*data_hdr), data_ie->len - sizeof(*data_hdr)); + + return prim; +} + +/* convert from MTP-TRANSFER.req to osmo_mtp_prim */ +static struct xua_msg *mtp_prim_to_m3ua(struct osmo_mtp_prim *prim) +{ + struct msgb *msg = prim->oph.msg; + struct osmo_mtp_transfer_param *param = &prim->u.transfer; + struct m3ua_data_hdr data_hdr; + + mtp_xfer_param_to_m3ua_dh(&data_hdr, param); + + return m3ua_xfer_from_data(&data_hdr, msgb_l2(msg), msgb_l2len(msg)); +} + +/* delivery given XUA message to given SS7 user */ +static int deliver_to_mtp_user(const struct osmo_ss7_user *osu, + struct xua_msg *xua) +{ + struct osmo_mtp_prim *prim; + + /* Create MTP-TRANSFER.ind and feed to user */ + prim = m3ua_to_xfer_ind(xua); + if (!prim) + return -1; + prim->u.transfer = xua->mtp; + + return osu->prim_cb(&prim->oph, (void *) osu->priv); +} + +/* HMDC -> HMDT: Message for distribution; Figure 25/Q.704 */ +/* This means it is a message we received from remote/L2, and it is to + * be routed to a local user part */ +static int hmdt_message_for_distribution(struct osmo_ss7_instance *inst, struct xua_msg *xua) +{ + struct m3ua_data_hdr *mdh; + const struct osmo_ss7_user *osu; + uint32_t service_ind; + + switch (xua->hdr.msg_class) { + case M3UA_MSGC_XFER: + switch (xua->hdr.msg_type) { + case M3UA_XFER_DATA: + mdh = data_hdr_from_m3ua(xua); + service_ind = mdh->si & 0xf; + break; + default: + LOGP(DLSS7, LOGL_ERROR, "Unknown M3UA XFER Message " + "Type %u\n", xua->hdr.msg_type); + return -1; + } + break; + case M3UA_MSGC_SNM: + /* FIXME */ + /* FIXME: SI = Signalling Network Management -> SRM/SLM/STM */ + /* FIXME: SI = Signalling Network Testing and Maintenance -> SLTC */ + default: + /* Discard Message */ + LOGP(DLSS7, LOGL_ERROR, "Unknown M3UA Message Class %u\n", + xua->hdr.msg_class); + return -1; + } + + /* Check for local SSN registered for this DPC/SSN */ + osu = inst->user[service_ind]; + if (osu) { + return deliver_to_mtp_user(osu, xua); + } else { + LOGP(DLSS7, LOGL_NOTICE, "No MTP-User for SI %u\n", service_ind); + /* Discard Message */ + /* FIXME: User Part Unavailable HMDT -> HMRT */ + return -1; + } +} + +/* HMDC->HMRT Msg For Routing; Figure 26/Q.704 */ +/* local message was receive d from L4, SRM, SLM, STM or SLTC, or + * remote message received from L2 and HMDC determined msg for routing */ +static int hmrt_message_for_routing(struct osmo_ss7_instance *inst, + struct xua_msg *xua) +{ + uint32_t dpc = xua->mtp.dpc; + struct osmo_ss7_route *rt; + + /* find route for DPC */ + /* FIXME: unify with gen_mtp_transfer_req_xua() */ + rt = osmo_ss7_route_lookup(inst, dpc); + if (rt) { + /* FIXME: DPC SP restart? */ + /* FIXME: DPC Congested? */ + /* FIXME: Select link based on SLS */ + /* FIXME: Transmit over respective Link */ + if (rt->dest.as) { + struct osmo_ss7_as *as = rt->dest.as; + DEBUGP(DLSS7, + "Found route for dpc=%u=%s: pc=%u=%s mask=0x%x" + " via AS %s proto=%s\n", + dpc, osmo_ss7_pointcode_print(inst, dpc), + rt->cfg.pc, osmo_ss7_pointcode_print2(inst, rt->cfg.pc), rt->cfg.mask, + as->cfg.name, osmo_ss7_asp_protocol_name(as->cfg.proto)); + + switch (as->cfg.proto) { + case OSMO_SS7_ASP_PROT_M3UA: + DEBUGP(DLSS7, "rt->dest.as proto is M3UA for dpc=%u=%s\n", + dpc, osmo_ss7_pointcode_print(inst, dpc)); + return m3ua_tx_xua_as(as,xua); + case OSMO_SS7_ASP_PROT_IPA: + return ipa_tx_xua_as(as, xua); + default: + LOGP(DLSS7, LOGL_ERROR, "MTP message " + "for ASP of unknown protocol %u\n", + as->cfg.proto); + break; + } + } else if (rt->dest.linkset) { + LOGP(DLSS7, LOGL_ERROR, + "Found route for dpc=%u=%s: pc=%u=%s mask=0x%x" + " via linkset %s, but MTP-TRANSFER.req unsupported for linkset.\n", + dpc, osmo_ss7_pointcode_print(inst, dpc), + rt->cfg.pc, osmo_ss7_pointcode_print2(inst, rt->cfg.pc), rt->cfg.mask, + rt->dest.linkset->cfg.name); + } else + OSMO_ASSERT(0); + } else { + LOGP(DLSS7, LOGL_ERROR, "MTP-TRANSFER.req for DPC %u: " + "no route!\n", dpc); + /* DPC unknown HMRT -> MGMT */ + /* Message Received for inaccesible SP HMRT ->RTPC */ + /* Discard Message */ + } + return -1; +} + +/* HMDC: Received Message L2 -> L3; Figure 24/Q.704 */ +/* This means a message was received from L2 and we have to decide if it + * is for the local stack (HMDT) or for routng (HMRT) */ +int m3ua_hmdc_rx_from_l2(struct osmo_ss7_instance *inst, struct xua_msg *xua) +{ + uint32_t dpc = xua->mtp.dpc; + if (osmo_ss7_pc_is_local(inst, dpc)) { + DEBUGP(DLSS7, "%s(): found dpc=%u=%s as local\n", __func__, + dpc, osmo_ss7_pointcode_print(inst, dpc)); + return hmdt_message_for_distribution(inst, xua); + } else { + DEBUGP(DLSS7, "%s(): dpc=%u=%s not local, message is for routing\n", __func__, + dpc, osmo_ss7_pointcode_print(inst, dpc)); + return hmrt_message_for_routing(inst, xua); + } +} + +/* MTP-User requests to send a MTP-TRANSFER.req via the stack */ +int osmo_ss7_user_mtp_xfer_req(struct osmo_ss7_instance *inst, + struct osmo_mtp_prim *omp) +{ + struct xua_msg *xua; + int rc; + + OSMO_ASSERT(omp->oph.sap == MTP_SAP_USER); + + switch (OSMO_PRIM_HDR(&omp->oph)) { + case OSMO_PRIM(OSMO_MTP_PRIM_TRANSFER, PRIM_OP_REQUEST): + xua = mtp_prim_to_m3ua(omp); + xua->mtp = omp->u.transfer; + /* normally we would call hmrt_message_for_routing() + * here, if we were to follow the state diagrams of the + * ITU-T Q.70x specifications. However, what if a local + * MTP user sends a MTP-TRANSFER.req to a local SSN? + * This wouldn't work as per the spec, but I believe it + * is a very useful feature (aka "loopback device" in + * IPv4). So we call m3ua_hmdc_rx_from_l2() just like + * the MTP-TRANSFER had been received from L2. */ + rc = m3ua_hmdc_rx_from_l2(inst, xua); + xua_msg_free(xua); + break; + default: + LOGP(DLSS7, LOGL_ERROR, "Ignoring unknown primitive %u:%u\n", + omp->oph.primitive, omp->oph.operation); + rc = -1; + } + + msgb_free(omp->oph.msg); + return rc; +} diff --git a/src/osmo_ss7_vty.c b/src/osmo_ss7_vty.c new file mode 100644 index 0000000..8d13865 --- /dev/null +++ b/src/osmo_ss7_vty.c @@ -0,0 +1,1793 @@ +/* Core SS7 Instance/Linkset/Link/AS/ASP VTY Interface */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "xua_internal.h" +#include + +#define XUA_VAR_STR "(sua|m3ua|ipa)" + +#define XUA_VAR_HELP_STR \ + "SCCP User Adaptation\n" \ + "MTP3 User Adaptation\n" \ + "IPA Multiplex (SCCP Lite)\n" + + +/*********************************************************************** + * Core CS7 Configuration + ***********************************************************************/ + +enum cs7_role_t {CS7_ROLE_SG, CS7_ROLE_ASP}; +static enum cs7_role_t cs7_role; +static void *g_ctx; + +static struct cmd_node cs7_node = { + L_CS7_NODE, + "%s(config-cs7)# ", + 1, +}; + +DEFUN(cs7_instance, cs7_instance_cmd, + "cs7 instance <0-15>", + CS7_STR "Configure a SS7 Instance\n" INST_STR + "Number of the instance\n") +{ + int id = atoi(argv[0]); + struct osmo_ss7_instance *inst; + + inst = osmo_ss7_instance_find_or_create(g_ctx, id); + if (!inst) { + vty_out(vty, "Unable to create SS7 Instance %d%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->node = L_CS7_NODE; + vty->index = inst; + vty->index_sub = &inst->cfg.description; + + return CMD_SUCCESS; +} + +static const struct value_string ss7_network_indicator_vals[] = { + { 0, "international" }, + { 1, "spare" }, + { 2, "national" }, + { 3, "reserved" }, + { 0, NULL } +}; + +/* cs7 network-indicator */ +DEFUN(cs7_net_ind, cs7_net_ind_cmd, + "network-indicator (international | national | reserved | spare)", + "Configure the Network Indicator\n" + "International Network\n" + "National Network\n" + "Reserved Network\n" + "Spare Network\n") +{ + struct osmo_ss7_instance *inst = vty->index; + int ni = get_string_value(ss7_network_indicator_vals, argv[0]); + + inst->cfg.network_indicator = ni; + return CMD_SUCCESS; +} + +/* TODO: cs7 point-code format */ +DEFUN(cs7_pc_format, cs7_pc_format_cmd, + "point-code format <1-24> [<1-23>] [<1-22>]", + PC_STR "Configure Point Code Format\n" + "Length of first PC component\n" + "Length of second PC component\n" + "Length of third PC component\n") +{ + struct osmo_ss7_instance *inst = vty->index; + int argind = 0; + + inst->cfg.pc_fmt.component_len[0] = atoi(argv[argind++]); + + if (argc >= 2) + inst->cfg.pc_fmt.component_len[1] = atoi(argv[argind++]); + else + inst->cfg.pc_fmt.component_len[1] = 0; + + if (argc >= 3) + inst->cfg.pc_fmt.component_len[2] = atoi(argv[argind++]); + else + inst->cfg.pc_fmt.component_len[2] = 0; + + return CMD_SUCCESS; +} + +DEFUN(cs7_pc_format_def, cs7_pc_format_def_cmd, + "point-code format default", + PC_STR "Configure Point Code Format\n" + "Default Point Code Format (3.8.3)\n") +{ + struct osmo_ss7_instance *inst = vty->index; + inst->cfg.pc_fmt.component_len[0] = 3; + inst->cfg.pc_fmt.component_len[1] = 8; + inst->cfg.pc_fmt.component_len[2] = 3; + return CMD_SUCCESS; +} + + +/* cs7 point-code delimiter */ +DEFUN(cs7_pc_delimiter, cs7_pc_delimiter_cmd, + "point-code delimiter (default|dash)", + PC_STR "Configure Point Code Delimiter\n" + "Use dot as delimiter\n" + "User dash as delimiter\n") +{ + struct osmo_ss7_instance *inst = vty->index; + + if (!strcmp(argv[0], "dash")) + inst->cfg.pc_fmt.delimiter = '-'; + else + inst->cfg.pc_fmt.delimiter = '.'; + + return CMD_SUCCESS; +} + +DEFUN(cs7_point_code, cs7_point_code_cmd, + "point-code POINT_CODE", + "Configure the local Point Code\n" + "Point Code\n") +{ + struct osmo_ss7_instance *inst = vty->index; + int pc = osmo_ss7_pointcode_parse(inst, argv[0]); + if (pc < 0 || !osmo_ss7_pc_is_valid((uint32_t)pc)) { + vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + inst->cfg.primary_pc = pc; + return CMD_SUCCESS; +} + +/* TODO: cs7 secondary-pc */ +/* TODO: cs7 capability-pc */ + +DEFUN(cs7_permit_dyn_rkm, cs7_permit_dyn_rkm_cmd, + "xua rkm routing-key-allocation (static-only|dynamic-permitted)", + "SIGTRAN xxxUA related\n" "Routing Key Management\n" + "Routing Key Management Allocation Policy\n" + "Only static (pre-confgured) Routing Keys permitted\n" + "Dynamically allocate Routing Keys for what ASPs request\n") +{ + struct osmo_ss7_instance *inst = vty->index; + + if (!strcmp(argv[0], "dynamic-permitted")) + inst->cfg.permit_dyn_rkm_alloc = true; + else + inst->cfg.permit_dyn_rkm_alloc = false; + + return CMD_SUCCESS; +} + +static void write_one_cs7(struct vty *vty, struct osmo_ss7_instance *inst); + +static int config_write_cs7(struct vty *vty) +{ + struct osmo_ss7_instance *inst; + + llist_for_each_entry(inst, &osmo_ss7_instances, list) + write_one_cs7(vty, inst); + + return 0; +} + +DEFUN(show_cs7_user, show_cs7_user_cmd, + "show cs7 instance <0-15> users", + SHOW_STR CS7_STR INST_STR INST_STR "User Table\n") +{ + int id = atoi(argv[0]); + struct osmo_ss7_instance *inst; + unsigned int i; + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + for (i = 0; i < ARRAY_SIZE(inst->user); i++) { + const struct osmo_ss7_user *user = inst->user[i]; + if (!user) + continue; + vty_out(vty, "SI %u: %s%s", i, user->name, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +/* TODO: Links + Linksets */ + +/*********************************************************************** + * Routing Table Configuration + ***********************************************************************/ + +static struct cmd_node rtable_node = { + L_CS7_RTABLE_NODE, + "%s(config-cs7-rt)# ", + 1, +}; + +DEFUN(cs7_route_table, cs7_route_table_cmd, + "route-table system", + "Specify the name of the route table\n" + "Name of the route table\n") +{ + struct osmo_ss7_instance *inst = vty->index; + struct osmo_ss7_route_table *rtable; + + rtable = inst->rtable_system; + vty->node = L_CS7_RTABLE_NODE; + vty->index = rtable; + vty->index_sub = &rtable->cfg.description; + + return CMD_SUCCESS; +} + +DEFUN(cs7_rt_upd, cs7_rt_upd_cmd, + "update route POINT_CODE MASK linkset LS_NAME [priority PRIO] [qos-class (CLASS|default)]", + "Update the Route\n" + "Update the Route\n" + "Destination Point Code\n" + "Point Code Mask\n" + "Point Code Length\n" + "Specify Destination Linkset\n" + "Linkset Name\n" + "Specify Priority\n" + "Priority\n" + "Specify QoS Class\n" + "QoS Class\n" + "Default QoS Class\n") +{ + struct osmo_ss7_route_table *rtable = vty->index; + struct osmo_ss7_route *rt; + int dpc = osmo_ss7_pointcode_parse(rtable->inst, argv[0]); + int mask = osmo_ss7_pointcode_parse_mask_or_len(rtable->inst, argv[1]); + const char *ls_name = argv[2]; + unsigned int argind; + + if (dpc < 0) { + vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (mask < 0) { + vty_out(vty, "Invalid point code (%s)%s", argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + rt = osmo_ss7_route_create(rtable, dpc, mask, ls_name); + if (!rt) { + vty_out(vty, "cannot create route %s/%s to %s%s", + argv[0], argv[1], argv[2], VTY_NEWLINE); + return CMD_WARNING; + } + + argind = 3; + if (argc > argind && !strcmp(argv[argind], "priority")) { + argind++; + rt->cfg.priority = atoi(argv[argind++]); + } + + if (argc > argind && !strcmp(argv[argind], "qos-class")) { + argind++; + rt->cfg.qos_class = atoi(argv[argind++]); + } + + return CMD_SUCCESS; +} + +DEFUN(cs7_rt_rem, cs7_rt_rem_cmd, + "remove route POINT_CODE MASK", + "Remove a Route\n" + "Remove a Route\n" + "Destination Point Code\n" + "Point Code Mask\n" + "Point Code Length\n") +{ + struct osmo_ss7_route_table *rtable = vty->index; + struct osmo_ss7_route *rt; + int dpc = osmo_ss7_pointcode_parse(rtable->inst, argv[0]); + int mask = osmo_ss7_pointcode_parse_mask_or_len(rtable->inst, argv[1]); + + if (dpc < 0) { + vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (mask < 0) { + vty_out(vty, "Invalid point code (%s)%s", argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + rt = osmo_ss7_route_find_dpc_mask(rtable, dpc, mask); + if (!rt) { + vty_out(vty, "cannot find route to be deleted%s", VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_ss7_route_destroy(rt); + return CMD_SUCCESS; +} + +static void write_one_rtable(struct vty *vty, struct osmo_ss7_route_table *rtable) +{ + struct osmo_ss7_route *rt; + + vty_out(vty, " route-table %s%s", rtable->cfg.name, VTY_NEWLINE); + if (rtable->cfg.description) + vty_out(vty, " description %s%s", rtable->cfg.description, VTY_NEWLINE); + llist_for_each_entry(rt, &rtable->routes, list) { + vty_out(vty, " update route %s %s linkset %s", + osmo_ss7_pointcode_print(rtable->inst, rt->cfg.pc), + osmo_ss7_pointcode_print(rtable->inst, rt->cfg.mask), + rt->cfg.linkset_name); + if (rt->cfg.priority) + vty_out(vty, " priority %u", rt->cfg.priority); + if (rt->cfg.qos_class) + vty_out(vty, " qos-class %u", rt->cfg.qos_class); + vty_out(vty, "%s", VTY_NEWLINE); + } +} + +static void vty_dump_rtable(struct vty *vty, struct osmo_ss7_route_table *rtbl) +{ + struct osmo_ss7_route *rt; + + vty_out(vty, "Routing table = %s%s", rtbl->cfg.name, VTY_NEWLINE); + vty_out(vty, "C=Cong Q=QoS P=Prio%s", VTY_NEWLINE); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "Destination C Q P Linkset Name Linkset Non-adj Route%s", VTY_NEWLINE); + vty_out(vty, "---------------------- - - - ------------------- ------- ------- -------%s", VTY_NEWLINE); + + llist_for_each_entry(rt, &rtbl->routes, list) { + vty_out(vty, "%-22s %c %c %u %-19s %-7s %-7s %-7s%s", + osmo_ss7_pointcode_print(rtbl->inst, rt->cfg.mask), + ' ', ' ', rt->cfg.priority, rt->cfg.linkset_name, "?", "?", "?", VTY_NEWLINE); + } +} + +DEFUN(show_cs7_route, show_cs7_route_cmd, + "show cs7 instance <0-15> route", + SHOW_STR CS7_STR INST_STR INST_STR "Routing Table\n") +{ + int id = atoi(argv[0]); + struct osmo_ss7_instance *inst; + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + vty_dump_rtable(vty, inst->rtable_system); + return CMD_SUCCESS; +} + +/*********************************************************************** + * xUA Listener Configuration (SG) + ***********************************************************************/ + +static enum osmo_ss7_asp_protocol parse_asp_proto(const char *protocol) +{ + return get_string_value(osmo_ss7_asp_protocol_vals, protocol); +} + +static struct cmd_node xua_node = { + L_CS7_XUA_NODE, + "%s(config-cs7-listen)# ", + 1, +}; + +DEFUN(cs7_xua, cs7_xua_cmd, + "listen " XUA_VAR_STR " <0-65534>", + "Configure/Enable xUA Listener\n" + XUA_VAR_HELP_STR "SCTP Port number\n") +{ + struct osmo_ss7_instance *inst = vty->index; + struct osmo_xua_server *xs; + enum osmo_ss7_asp_protocol proto = parse_asp_proto(argv[0]); + uint16_t port = atoi(argv[1]); + + xs = osmo_ss7_xua_server_find(inst, proto, port); + if (!xs) { + xs = osmo_ss7_xua_server_create(inst, proto, port, NULL); + if (!xs) + return CMD_SUCCESS; + } + + vty->node = L_CS7_XUA_NODE; + vty->index = xs; + return CMD_SUCCESS; +} + +DEFUN(no_cs7_xua, no_cs7_xua_cmd, + "no listen " XUA_VAR_STR " <0-65534>", + NO_STR "Disable xUA Listener on given SCTP Port\n" + XUA_VAR_HELP_STR "SCTP Port number\n") +{ + struct osmo_ss7_instance *inst = vty->index; + struct osmo_xua_server *xs; + enum osmo_ss7_asp_protocol proto = parse_asp_proto(argv[0]); + uint16_t port = atoi(argv[1]); + + xs = osmo_ss7_xua_server_find(inst, proto, port); + if (!xs) { + vty_out(vty, "No xUA server for port %u found%s", port, VTY_NEWLINE); + return CMD_WARNING; + } + osmo_ss7_xua_server_destroy(xs); + return CMD_SUCCESS; +} + +DEFUN(xua_local_ip, xua_local_ip_cmd, + "local-ip A.B.C.D", + "Configure the Local IP Address for xUA\n" + "IP Address to use for XUA\n") +{ + struct osmo_xua_server *xs = vty->index; + + osmo_ss7_xua_server_set_local_host(xs, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(xua_accept_dyn_asp, xua_accept_dyn_asp_cmd, + "accept-asp-connections (pre-configured|dynamic-permitted)", + "Define what kind of ASP connections to accept\n" + "Accept only pre-confiugred ASPs (source IP/prt)\n" + "Accept any connection and dynamically create an ASP definition\n") +{ + struct osmo_xua_server *xs = vty->index; + + if (!strcmp(argv[0], "dynamic-permitted")) + xs->cfg.accept_dyn_reg = true; + else + xs->cfg.accept_dyn_reg = false; + + return CMD_SUCCESS; +} + +static void write_one_xua(struct vty *vty, struct osmo_xua_server *xs) +{ + vty_out(vty, " listen %s %u%s", + get_value_string(osmo_ss7_asp_protocol_vals, xs->cfg.proto), + xs->cfg.local.port, VTY_NEWLINE); + vty_out(vty, " local-ip %s%s", xs->cfg.local.host, VTY_NEWLINE); + if (xs->cfg.accept_dyn_reg) + vty_out(vty, " accept-asp-connections dynamic-permitted%s", VTY_NEWLINE); +} + + +/*********************************************************************** + * Application Server Process + ***********************************************************************/ + +static struct cmd_node asp_node = { + L_CS7_ASP_NODE, + "%s(config-cs7-asp)# ", + 1, +}; + +DEFUN(cs7_asp, cs7_asp_cmd, + "asp NAME <0-65535> <0-65535> " XUA_VAR_STR, + "Configure Application Server Process\n" + "Name of ASP\n" + "Remote SCTP port number\n" + "Local SCTP port number\n" + XUA_VAR_HELP_STR) +{ + struct osmo_ss7_instance *inst = vty->index; + const char *name = argv[0]; + uint16_t remote_port = atoi(argv[1]); + uint16_t local_port = atoi(argv[2]); + enum osmo_ss7_asp_protocol protocol = parse_asp_proto(argv[3]); + struct osmo_ss7_asp *asp; + + if (protocol == OSMO_SS7_ASP_PROT_NONE) { + vty_out(vty, "invalid protocol '%s'%s", argv[3], VTY_NEWLINE); + return CMD_WARNING; + } + + asp = osmo_ss7_asp_find_or_create(inst, name, remote_port, local_port, protocol); + if (!asp) { + vty_out(vty, "cannot create ASP '%s'%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + asp->cfg.is_server = true; + + vty->node = L_CS7_ASP_NODE; + vty->index = asp; + vty->index_sub = &asp->cfg.description; + return CMD_SUCCESS; +} + +DEFUN(no_cs7_asp, no_cs7_asp_cmd, + "no asp NAME", + NO_STR "Disable Application Server Process\n" + "Name of ASP\n") +{ + struct osmo_ss7_instance *inst = vty->index; + const char *name = argv[0]; + struct osmo_ss7_asp *asp; + + asp = osmo_ss7_asp_find_by_name(inst, name); + if (!asp) { + vty_out(vty, "No ASP named '%s' found%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + osmo_ss7_asp_destroy(asp); + return CMD_SUCCESS; +} + +DEFUN(asp_local_ip, asp_local_ip_cmd, + "local-ip A.B.C.D", + "Specify Local IP Address from which to contact ASP\n" + "Local IP Address from which to contact of ASP\n") +{ + struct osmo_ss7_asp *asp = vty->index; + osmo_talloc_replace_string(asp, &asp->cfg.local.host, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(asp_remote_ip, asp_remote_ip_cmd, + "remote-ip A.B.C.D", + "Specify Remote IP Address of ASP\n" + "Remote IP Address of ASP\n") +{ + struct osmo_ss7_asp *asp = vty->index; + osmo_talloc_replace_string(asp, &asp->cfg.remote.host, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(asp_qos_clas, asp_qos_class_cmd, + "qos-class <0-255>", + "Specify QoS Class of ASP\n" + "QoS Class of ASP\n") +{ + struct osmo_ss7_asp *asp = vty->index; + asp->cfg.qos_class = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(asp_block, asp_block_cmd, + "block", + "Allows a SCTP Association with ASP, but doesn't let it become active\n") +{ + /* TODO */ + vty_out(vty, "Not supported yet%s", VTY_NEWLINE); + return CMD_WARNING; +} + +DEFUN(asp_shutdown, asp_shutdown_cmd, + "shutdown", + "Terminates SCTP association; New associations will be rejected\n") +{ + /* TODO */ + vty_out(vty, "Not supported yet%s", VTY_NEWLINE); + return CMD_WARNING; +} + +DEFUN(show_cs7_asp, show_cs7_asp_cmd, + "show cs7 instance <0-15> asp", + SHOW_STR CS7_STR INST_STR INST_STR "Application Server Process (ASP)\n") +{ + struct osmo_ss7_instance *inst; + struct osmo_ss7_asp *asp; + int id = atoi(argv[0]); + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, " Effect Primary%s", VTY_NEWLINE); + vty_out(vty, "ASP Name AS Name State Type Rmt Port Remote IP Addr SCTP%s", VTY_NEWLINE); + vty_out(vty, "------------ ------------ ------------- ---- -------- --------------- ----------%s", VTY_NEWLINE); + + llist_for_each_entry(asp, &inst->asp_list, list) { + vty_out(vty, "%-12s %-12s %-13s %-4s %-8u %-15s %-10s%s", + asp->cfg.name, "?", osmo_fsm_inst_state_name(asp->fi), + get_value_string(osmo_ss7_asp_protocol_vals, asp->cfg.proto), + asp->cfg.remote.port, asp->cfg.remote.host, "", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +static void write_one_asp(struct vty *vty, struct osmo_ss7_asp *asp) +{ + /* skip any dynamically created ASPs (auto-created at connect + * time) */ + if (asp->dyn_allocated) + return; + + vty_out(vty, " asp %s %u %u %s%s", + asp->cfg.name, asp->cfg.remote.port, asp->cfg.local.port, + osmo_ss7_asp_protocol_name(asp->cfg.proto), VTY_NEWLINE); + if (asp->cfg.description) + vty_out(vty, " description %s%s", asp->cfg.description, VTY_NEWLINE); + if (asp->cfg.local.host) + vty_out(vty, " local-ip %s%s", asp->cfg.local.host, VTY_NEWLINE); + if (asp->cfg.remote.host) + vty_out(vty, " remote-ip %s%s", asp->cfg.remote.host, VTY_NEWLINE); + if (asp->cfg.qos_class) + vty_out(vty, " qos-class %u%s", asp->cfg.qos_class, VTY_NEWLINE); +} + + +/*********************************************************************** + * Application Server + ***********************************************************************/ + +static struct cmd_node as_node = { + L_CS7_AS_NODE, + "%s(config-cs7-as)# ", + 1, +}; + +DEFUN(cs7_as, cs7_as_cmd, + "as NAME " XUA_VAR_STR, + "Configure an Application Server\n" + "Name of the Application Server\n" + XUA_VAR_HELP_STR) +{ + struct osmo_ss7_instance *inst = vty->index; + struct osmo_ss7_as *as; + const char *name = argv[0]; + enum osmo_ss7_asp_protocol protocol = parse_asp_proto(argv[1]); + + if (protocol == OSMO_SS7_ASP_PROT_NONE) { + vty_out(vty, "invalid protocol '%s'%s", argv[3], VTY_NEWLINE); + return CMD_WARNING; + } + + as = osmo_ss7_as_find_or_create(inst, name, protocol); + if (!as) { + vty_out(vty, "cannot create AS '%s'%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + + as->cfg.name = talloc_strdup(as, name); + + vty->node = L_CS7_AS_NODE; + vty->index = as; + vty->index_sub = &as->cfg.description; + + return CMD_SUCCESS; +} + +DEFUN(no_cs7_as, no_cs7_as_cmd, + "no as NAME", + NO_STR "Disable Application Server\n" + "Name of AS\n") +{ + struct osmo_ss7_instance *inst = vty->index; + const char *name = argv[0]; + struct osmo_ss7_as *as; + + as = osmo_ss7_as_find_by_name(inst, name); + if (!as) { + vty_out(vty, "No AS named '%s' found%s", name, VTY_NEWLINE); + return CMD_WARNING; + } + osmo_ss7_as_destroy(as); + return CMD_SUCCESS; +} + +/* TODO: routing-key */ +DEFUN(as_asp, as_asp_cmd, + "asp NAME", + "Specify that a given ASP is part of this AS\n" + "Name of ASP to be added to AS\n") +{ + struct osmo_ss7_as *as = vty->index; + + if (osmo_ss7_as_add_asp(as, argv[0])) { + vty_out(vty, "cannot find ASP '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(as_no_asp, as_no_asp_cmd, + "no asp NAME", + NO_STR "Specify ASP to be removed from this AS\n" + "Name of ASP to be removed\n") +{ + struct osmo_ss7_as *as = vty->index; + + if (osmo_ss7_as_del_asp(as, argv[0])) { + vty_out(vty, "cannot find ASP '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(as_traf_mode, as_traf_mode_cmd, + "traffic-mode (broadcast | loadshare | roundrobin | override)", + "Specifies traffic mode of operation of the ASP within the AS\n" + "Broadcast to all ASP within AS\n" + "Share Load among all ASP within AS\n" + "Round-Robin between all ASP within AS\n" + "Override\n") +{ + struct osmo_ss7_as *as = vty->index; + + as->cfg.mode = get_string_value(osmo_ss7_as_traffic_mode_vals, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(as_recov_tout, as_recov_tout_cmd, + "recovery-timeout <1-2000>", + "Specifies the recovery timeout value in milliseconds\n" + "Recovery Timeout in Milliseconds\n") +{ + struct osmo_ss7_as *as = vty->index; + as->cfg.recovery_timeout_msec = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(as_qos_clas, as_qos_class_cmd, + "qos-class <0-255>", + "Specity QoS Class of AS\n" + "QoS Class of AS\n") +{ + struct osmo_ss7_as *as = vty->index; + as->cfg.qos_class = atoi(argv[0]); + return CMD_SUCCESS; +} + +const struct value_string mtp_si_vals[] = { + { MTP_SI_SCCP, "sccp" }, + { MTP_SI_TUP, "tup" }, + { MTP_SI_ISUP, "isup" }, + { MTP_SI_DUP, "dup" }, + { MTP_SI_TESTING, "testing" }, + { MTP_SI_B_ISUP, "b-isup" }, + { MTP_SI_SAT_ISUP, "sat-isup" }, + { MTP_SI_AAL2_SIG, "aal2" }, + { MTP_SI_BICC, "bicc" }, + { MTP_SI_GCP, "h248" }, + { 0, NULL } +}; + +#define ROUTING_KEY_CMD "routing-key RCONTEXT DPC" +#define ROUTING_KEY_CMD_STRS \ + "Define a routing key\n" \ + "Routing context number\n" \ + "Destination Point Code\n" +#define ROUTING_KEY_SI_ARG " si (aal2|bicc|b-isup|h248|isup|sat-isup|sccp|tup)" +#define ROUTING_KEY_SI_ARG_STRS \ + "Match on Service Indicator\n" \ + "ATM Adaption Layer 2\n" \ + "Bearer Independent Call Control\n" \ + "Broadband ISDN User Part\n" \ + "H.248\n" \ + "ISDN User Part\n" \ + "Sattelite ISDN User Part\n" \ + "Signalling Connection Control Part\n" \ + "Telephony User Part\n" +#define ROUTING_KEY_SSN_ARG " ssn SSN" +#define ROUTING_KEY_SSN_ARG_STRS \ + "Match on Sub-System Number\n" \ + "Sub-System Number to match on\n" + +static int _rout_key(struct vty *vty, + const char *rcontext, const char *dpc, + const char *si, const char *ssn) +{ + struct osmo_ss7_as *as = vty->index; + struct osmo_ss7_routing_key *rkey = &as->cfg.routing_key; + int pc; + + pc = osmo_ss7_pointcode_parse(as->inst, dpc); + if (pc < 0) { + vty_out(vty, "Invalid point code (%s)%s", dpc, VTY_NEWLINE); + return CMD_WARNING; + } + rkey->pc = pc; + + rkey->context = atoi(rcontext); /* FIXME: input validation */ + rkey->si = si ? get_string_value(mtp_si_vals, si) : 0; /* FIXME: input validation */ + rkey->ssn = ssn ? atoi(ssn) : 0; /* FIXME: input validation */ + + return CMD_SUCCESS; +} + +DEFUN(as_rout_key, as_rout_key_cmd, + ROUTING_KEY_CMD, + ROUTING_KEY_CMD_STRS) +{ + return _rout_key(vty, argv[0], argv[1], NULL, NULL); +} + +DEFUN(as_rout_key_si, as_rout_key_si_cmd, + ROUTING_KEY_CMD ROUTING_KEY_SI_ARG, + ROUTING_KEY_CMD_STRS ROUTING_KEY_SI_ARG_STRS) +{ + return _rout_key(vty, argv[0], argv[1], argv[2], NULL); +} + +DEFUN(as_rout_key_ssn, as_rout_key_ssn_cmd, + ROUTING_KEY_CMD ROUTING_KEY_SSN_ARG, + ROUTING_KEY_CMD_STRS ROUTING_KEY_SSN_ARG_STRS) +{ + return _rout_key(vty, argv[0], argv[1], NULL, argv[2]); +} + +DEFUN(as_rout_key_si_ssn, as_rout_key_si_ssn_cmd, + ROUTING_KEY_CMD ROUTING_KEY_SI_ARG ROUTING_KEY_SSN_ARG, + ROUTING_KEY_CMD_STRS ROUTING_KEY_SI_ARG_STRS ROUTING_KEY_SSN_ARG_STRS) +{ + return _rout_key(vty, argv[0], argv[1], argv[2], argv[3]); +} + +DEFUN(as_pc_override, as_pc_override_cmd, + "point-code override dpc PC", + "Point Code Specific Features\n" + "Override (force) a point-code to hard-coded value\n" + "Override Source Point Code\n" + "Override Destination Point Code\n" + "New Point Code\n") +{ + struct osmo_ss7_as *as = vty->index; + int pc = osmo_ss7_pointcode_parse(as->inst, argv[0]); + if (pc < 0) { + vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (as->cfg.proto != OSMO_SS7_ASP_PROT_IPA) { + vty_out(vty, "Only IPA type AS support point-code override. " + "Be happy that you don't need it!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + as->cfg.pc_override.dpc = pc; + + return CMD_SUCCESS; +} + +static void write_one_as(struct vty *vty, struct osmo_ss7_as *as) +{ + struct osmo_ss7_routing_key *rkey; + unsigned int i; + + /* skip any dynamically allocated AS definitions */ + if (as->rkm_dyn_allocated) + return; + + vty_out(vty, " as %s %s%s", as->cfg.name, + osmo_ss7_asp_protocol_name(as->cfg.proto), VTY_NEWLINE); + if (as->cfg.description) + vty_out(vty, " description %s%s", as->cfg.description, VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + struct osmo_ss7_asp *asp = as->cfg.asps[i]; + if (!asp) + continue; + vty_out(vty, " asp %s%s", asp->cfg.name, VTY_NEWLINE); + } + if (as->cfg.mode != OSMO_SS7_AS_TMOD_LOADSHARE) + vty_out(vty, " traffic-mode %s%s", + osmo_ss7_as_traffic_mode_name(as->cfg.mode), VTY_NEWLINE); + if (as->cfg.recovery_timeout_msec != 2000) { + vty_out(vty, " recovery-timeout %u%s", + as->cfg.recovery_timeout_msec, VTY_NEWLINE); + } + if (as->cfg.qos_class) + vty_out(vty, " qos-class %u%s", as->cfg.qos_class, VTY_NEWLINE); + rkey = &as->cfg.routing_key; + vty_out(vty, " routing-key %u %s", rkey->context, + osmo_ss7_pointcode_print(as->inst, rkey->pc)); + if (rkey->si) + vty_out(vty, " si %s", + get_value_string(mtp_si_vals, rkey->si)); + if (rkey->ssn) + vty_out(vty, " ssn %u", rkey->ssn); + vty_out(vty, "%s", VTY_NEWLINE); + + if (as->cfg.pc_override.dpc) + vty_out(vty, " point-code override dpc %s%s", + osmo_ss7_pointcode_print(as->inst, as->cfg.pc_override.dpc), VTY_NEWLINE); +} + +DEFUN(show_cs7_as, show_cs7_as_cmd, + "show cs7 instance <0-15> as (active|all|m3ua|sua)", + SHOW_STR CS7_STR INST_STR INST_STR "Application Server (AS)\n" + "Display all active ASs\n" + "Display all ASs (default)\n" + "Display all m3ua ASs\n" + "Display all SUA ASs\n") +{ + struct osmo_ss7_instance *inst; + struct osmo_ss7_as *as; + const char *filter = argv[1]; + int id = atoi(argv[0]); + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, " Routing Routing Key Cic Cic%s", VTY_NEWLINE); + vty_out(vty, "AS Name State Context Dpc Si Opc Ssn Min Max%s", VTY_NEWLINE); + vty_out(vty, "------------ ------------ ---------- ------------- ---- ------------- --- ----- -----%s", VTY_NEWLINE); + + llist_for_each_entry(as, &inst->as_list, list) { + if (filter && !strcmp(filter, "m3ua") && as->cfg.proto != OSMO_SS7_ASP_PROT_M3UA) + continue; + if (filter && !strcmp(filter, "sua") && as->cfg.proto != OSMO_SS7_ASP_PROT_SUA) + continue; + /* FIXME: active filter */ + vty_out(vty, "%-12s %-12s %-10u %-13s %4s %13s %3s %5s %4s%s", + as->cfg.name, osmo_fsm_inst_state_name(as->fi), as->cfg.routing_key.context, + osmo_ss7_pointcode_print(as->inst, as->cfg.routing_key.pc), + "", "", "", "", "", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +/*********************************************************************** + * SCCP addressbook handling + ***********************************************************************/ + +/* SCCP addressbook */ +struct osmo_sccp_addr_entry { + struct llist_head list; + struct llist_head list_global; + struct osmo_ss7_instance *inst; + char name[512]; + struct osmo_sccp_addr addr; +}; + +static struct cmd_node sccpaddr_node = { + L_CS7_SCCPADDR_NODE, + "%s(config-cs7-sccpaddr)# ", + 1, +}; + +static struct cmd_node sccpaddr_gt_node = { + L_CS7_SCCPADDR_GT_NODE, + "%s(config-cs7-sccpaddr-gt)# ", + 1, +}; + +/* A global list that holds all addressbook entries at once + * (see also .cfg in struct osmo_ss7_instance) */ +LLIST_HEAD(sccp_address_book_global); + +/* Pick an SCCP address entry from the addressbook list by its name */ +static struct osmo_sccp_addr_entry +*addr_entry_by_name_local(const char *name, + const struct osmo_ss7_instance *inst) +{ + struct osmo_sccp_addr_entry *entry; + + llist_for_each_entry(entry, &inst->cfg.sccp_address_book, list) { + if (strcmp(entry->name, name) == 0) { + OSMO_ASSERT(entry->inst == inst); + return entry; + } + } + + return NULL; +} + +/* Pick an SCCP address entry from the global addressbook + * list by its name */ +static struct osmo_sccp_addr_entry +*addr_entry_by_name_global(const char *name) +{ + struct osmo_sccp_addr_entry *entry; + + llist_for_each_entry(entry, &sccp_address_book_global, + list_global) { + if (strcmp(entry->name, name) == 0) + return entry; + } + + return NULL; +} + +/*! \brief Lookup an SCCP address from the addressbook by its name. + * \param[out] dest_addr pointer to output the resulting sccp-address; + * (set to NULL if not interested) + * \param[in] name of the address to lookup + * \returns SS7 instance; NULL on error */ +struct osmo_ss7_instance * +osmo_sccp_addr_by_name(struct osmo_sccp_addr *dest_addr, + const char *name) +{ + struct osmo_sccp_addr_entry *entry; + + entry = addr_entry_by_name_global(name); + if (!entry) + return NULL; + + if (dest_addr) + *dest_addr = entry->addr; + + return entry->inst; +} + +/*! \brief Reverse lookup the lookup-name of a specified SCCP address. + * \param[in] name of the address to lookup + * \returns char pointer to the lookup-name; NULL on error */ +const char *osmo_sccp_name_by_addr(const struct osmo_sccp_addr *addr) +{ + struct osmo_sccp_addr_entry *entry; + + llist_for_each_entry(entry, &sccp_address_book_global, list_global) { + if (memcmp(&entry->addr, addr, sizeof(*addr)) == 0) + return entry->name; + } + + return NULL; +} + +/* Generate VTY configuration file snippet */ +static void write_sccp_addressbook(struct vty *vty, + const struct osmo_ss7_instance *inst) +{ + struct osmo_sccp_addr_entry *entry; + + if (llist_empty(&inst->cfg.sccp_address_book)) + return; + + /* FIXME: Add code to write IP-Addresses */ + + llist_for_each_entry(entry, &inst->cfg.sccp_address_book, list) { + vty_out(vty, " sccp-address %s%s", entry->name, VTY_NEWLINE); + switch (entry->addr.ri) { + case OSMO_SCCP_RI_GT: + vty_out(vty, " routing-indicator GT%s", VTY_NEWLINE); + break; + case OSMO_SCCP_RI_SSN_PC: + vty_out(vty, " routing-indicator PC%s", VTY_NEWLINE); + break; + case OSMO_SCCP_RI_SSN_IP: + vty_out(vty, " routing-indicator IP%s", VTY_NEWLINE); + break; + case OSMO_SCCP_RI_NONE: + break; + default: + vty_out(vty, " ! invalid routing-indicator value: %u%s", entry->addr.ri, VTY_NEWLINE); + break; + } + if (entry->addr.presence & OSMO_SCCP_ADDR_T_PC) + vty_out(vty, " point-code %s%s", + osmo_ss7_pointcode_print(entry->inst, + entry->addr.pc), + VTY_NEWLINE); + if (entry->addr.presence & OSMO_SCCP_ADDR_T_SSN) + vty_out(vty, " subsystem-number %u%s", entry->addr.ssn, + VTY_NEWLINE); + if (entry->addr.presence & OSMO_SCCP_ADDR_T_GT) { + vty_out(vty, " global-title%s", VTY_NEWLINE); + vty_out(vty, " global-title-indicator %u%s", + entry->addr.gt.gti, VTY_NEWLINE); + vty_out(vty, " translation-type %u%s", + entry->addr.gt.tt, VTY_NEWLINE); + vty_out(vty, " numbering-plan-indicator %u%s", + entry->addr.gt.npi, VTY_NEWLINE); + vty_out(vty, " nature-of-address-indicator %u%s", + entry->addr.gt.nai, VTY_NEWLINE); + if (strlen(entry->addr.gt.digits)) + vty_out(vty, " digits %s%s", + entry->addr.gt.digits, VTY_NEWLINE); + } + } +} + +/* List all addressbook entries */ +DEFUN(cs7_show_sccpaddr, cs7_show_sccpaddr_cmd, + "show cs7 instance <0-15> sccp-addressbook", + SHOW_STR CS7_STR INST_STR INST_STR "List all SCCP addressbook entries\n") +{ + struct osmo_ss7_instance *inst; + struct osmo_sccp_addr_entry *entry; + int id = atoi(argv[0]); +#if 0 + /* FIXME: IP-Address based SCCP-Routing is currently not supported, + * so we leave the related VTY options out for now */ + char ip_addr_str[INET6_ADDRSTRLEN]; +#endif + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + if (inst->cfg.description) + vty_out(vty, " description %s%s", inst->cfg.description, + VTY_NEWLINE); + + if (llist_empty(&inst->cfg.sccp_address_book)) { + vty_out(vty, "SCCP addressbook empty!%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + + vty_out(vty, "%s", VTY_NEWLINE); + + vty_out(vty, "Name "); + vty_out(vty, "RI: "); + vty_out(vty, "PC: "); + vty_out(vty, "SSN: "); +#if 0 + /* FIXME: IP-Address based SCCP-Routing is currently not supported, + * so we leave the related VTY options out for now */ + vty_out(vty, "IP-Address: "); +#endif + vty_out(vty, "GT:"); + vty_out(vty, "%s", VTY_NEWLINE); + + vty_out(vty, "------------ "); + vty_out(vty, "--- "); + vty_out(vty, "--------- "); + vty_out(vty, "---------- "); +#if 0 + /* FIXME: IP-Address based SCCP-Routing is currently not supported, + * so we leave the related VTY options out for now */ + vty_out(vty, "--------------------------------------- "); +#endif + vty_out(vty, "--------------------------------------- "); + vty_out(vty, "%s", VTY_NEWLINE); + + llist_for_each_entry(entry, &inst->cfg.sccp_address_book, list) { + vty_out(vty, "%-12s ", entry->name); + + /* RI */ + switch (entry->addr.ri) { + case OSMO_SCCP_RI_GT: + vty_out(vty, "GT "); + break; + case OSMO_SCCP_RI_SSN_PC: + vty_out(vty, "PC "); + break; + case OSMO_SCCP_RI_SSN_IP: + vty_out(vty, "IP "); + break; + default: + vty_out(vty, "ERR "); + break; + } + + /* PC */ + if (entry->addr.presence & OSMO_SCCP_ADDR_T_PC) + vty_out(vty, "%-9s ", + osmo_ss7_pointcode_print(entry->inst, + entry->addr.pc)); + else + vty_out(vty, "(none) "); + + /* SSN */ + if (entry->addr.presence & OSMO_SCCP_ADDR_T_SSN) + vty_out(vty, "%-10u ", entry->addr.ssn); + else + vty_out(vty, "(none) "); +#if 0 + /* FIXME: IP-Address based SCCP-Routing is currently not + * supported, so we leave the related VTY options out for now */ + /* IP-Address */ + if (entry->addr.presence & OSMO_SCCP_ADDR_T_IPv4) { + inet_ntop(AF_INET, &entry->addr.ip.v4, ip_addr_str, + INET6_ADDRSTRLEN); + vty_out(vty, "%-39s ", ip_addr_str); + } else if (entry->addr.presence & OSMO_SCCP_ADDR_T_IPv6) { + inet_ntop(AF_INET6, &entry->addr.ip.v6, ip_addr_str, + INET6_ADDRSTRLEN); + vty_out(vty, "%-39s ", ip_addr_str); + } else + vty_out(vty, "(none) "); +#endif + /* GT */ + if (entry->addr.presence & OSMO_SCCP_ADDR_T_GT) { + vty_out(vty, "GTI:%u ", entry->addr.gt.gti); + vty_out(vty, "TT:%u ", entry->addr.gt.tt); + vty_out(vty, "NPI:%u ", entry->addr.gt.npi); + vty_out(vty, "NAI:%u ", entry->addr.gt.nai); + if (strlen(entry->addr.gt.digits)) + vty_out(vty, "%s ", entry->addr.gt.digits); + } else + vty_out(vty, "(none)"); + vty_out(vty, "%s", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +/* Create a new addressbook entry and switch nodes */ +DEFUN(cs7_sccpaddr, cs7_sccpaddr_cmd, + "sccp-address NAME", + "Create/Modify an SCCP addressbook entry\n" "Name of the SCCP Address\n") +{ + struct osmo_ss7_instance *inst = (struct osmo_ss7_instance *)vty->index; + struct osmo_sccp_addr_entry *entry; + const char *name = argv[0]; + + if (strlen(name) >= sizeof(entry->name)) { + vty_out(vty, "sccp address name to long!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Ensure that we do not use address names that + * are already used in other ss7 instances. */ + entry = addr_entry_by_name_global(name); + if (entry != NULL) { + vty_out(vty, + "address name (%s) already used in ss7 instance %u%s", + entry->name, entry->inst->cfg.id, VTY_NEWLINE); + return CMD_WARNING; + } + + entry = addr_entry_by_name_local(name, inst); + + /* Create a new addressbook entry if we can not find an + * already existing entry */ + if (!entry) { + entry = talloc_zero(inst, struct osmo_sccp_addr_entry); + osmo_strlcpy(entry->name, name, sizeof(entry->name)); + llist_add_tail(&entry->list, &inst->cfg.sccp_address_book); + llist_add_tail(&entry->list_global, &sccp_address_book_global); + entry->addr.ri = OSMO_SCCP_RI_SSN_PC; + } + + entry->inst = (struct osmo_ss7_instance *)vty->index; + vty->node = L_CS7_SCCPADDR_NODE; + vty->index = entry; + + return CMD_SUCCESS; +} + +DEFUN(cs7_sccpaddr_del, cs7_sccpaddr_del_cmd, + "no sccp-address NAME", + NO_STR "Delete an SCCP addressbook entry\n" "Name of the SCCP Address\n") +{ + struct osmo_ss7_instance *inst = (struct osmo_ss7_instance *)vty->index; + struct osmo_sccp_addr_entry *entry; + const char *name = argv[0]; + + entry = addr_entry_by_name_local(name, inst); + if (entry) { + llist_del(&entry->list); + llist_del(&entry->list_global); + talloc_free(entry); + } else { + vty_out(vty, "Addressbook entry not found!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* Set routing indicator of sccp address */ +DEFUN(cs7_sccpaddr_ri, cs7_sccpaddr_ri_cmd, + "routing-indicator (GT|PC|IP)", + "Add Routing Indicator\n" + "by global-title\n" "by point-code\n" "by ip-address\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + switch (argv[0][0]) { + case 'G': + entry->addr.ri = OSMO_SCCP_RI_GT; + break; + case 'P': + entry->addr.ri = OSMO_SCCP_RI_SSN_PC; + break; + case 'I': + entry->addr.ri = OSMO_SCCP_RI_SSN_IP; + break; + } + return CMD_SUCCESS; +} + +/* Set point-code number of sccp address */ +DEFUN(cs7_sccpaddr_pc, cs7_sccpaddr_pc_cmd, + "point-code POINT_CODE", "Add point-code Number\n" "PC\n") +{ + int pc; + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + + pc = osmo_ss7_pointcode_parse(entry->inst, argv[0]); + if (pc < 0) { + vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + entry->addr.presence |= OSMO_SCCP_ADDR_T_PC; + entry->addr.pc = pc; + if (entry->addr.ri == OSMO_SCCP_RI_NONE) + entry->addr.ri = OSMO_SCCP_RI_SSN_PC; + return CMD_SUCCESS; +} + +/* Remove point-code number from sccp address */ +DEFUN(cs7_sccpaddr_pc_del, cs7_sccpaddr_pc_del_cmd, + "no point-code", NO_STR "Remove point-code Number\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_PC; + entry->addr.pc = 0; + return CMD_SUCCESS; +} + +/* Set subsystem number of sccp address */ +DEFUN(cs7_sccpaddr_ssn, cs7_sccpaddr_ssn_cmd, + "subsystem-number <0-4294967295>", "Add Subsystem Number\n" "SSN\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.presence |= OSMO_SCCP_ADDR_T_SSN; + entry->addr.ssn = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* Remove subsystem number from sccp address */ +DEFUN(cs7_sccpaddr_ssn_del, cs7_sccpaddr_ssn_del_cmd, + "no subsystem-number", NO_STR "Remove Subsystem Number\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_SSN; + entry->addr.ssn = 0; + return CMD_SUCCESS; +} + +#if 0 +/* FIXME: IP-Address based SCCP-Routing is currently not supported, + * so we leave the related VTY options out for now */ + +/* Set IP Address (V4) of sccp address */ +DEFUN(cs7_sccpaddr_ipv4, cs7_sccpaddr_ipv4_cmd, + "ip-address V4 A.B.C.D", + "Add IP-Address\n" "Protocol version 4\n" "IP-Address digits\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + unsigned int rc; + uint8_t ip_addr_backup[sizeof(entry->addr.ip)]; + OSMO_ASSERT(entry); + + /* Create a backup of the existing IP-Address setting */ + memcpy(ip_addr_backup, &entry->addr.ip, sizeof(entry->addr.ip)); + + entry->addr.presence |= OSMO_SCCP_ADDR_T_IPv4; + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv6; + rc = inet_pton(AF_INET, argv[1], &entry->addr.ip.v4); + if (rc <= 0) { + vty_out(vty, "Invalid IP-Address format!%s", VTY_NEWLINE); + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv4; + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv6; + + /* In case of failure, make sure the previous IP-Address + * configuration is restored */ + memcpy(&entry->addr.ip, ip_addr_backup, sizeof(entry->addr.ip)); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +/* Set IP Address (V6) of sccp address */ +DEFUN(cs7_sccpaddr_ipv6, cs7_sccpaddr_ipv6_cmd, + "ip-address V6 A:B:C:D:E:F:G:H", + "Add IP-Address\n" "Protocol version 6\n" "IP-Address digits\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + unsigned int rc; + uint8_t ip_addr_backup[sizeof(entry->addr.ip)]; + OSMO_ASSERT(entry); + + /* Create a backup of the existing IP-Address setting */ + memcpy(ip_addr_backup, &entry->addr.ip, sizeof(entry->addr.ip)); + + entry->addr.presence |= OSMO_SCCP_ADDR_T_IPv6; + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv4; + rc = inet_pton(AF_INET6, argv[1], &entry->addr.ip.v4); + if (rc <= 0) { + vty_out(vty, "Invalid IP-Address format!%s", VTY_NEWLINE); + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv4; + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv6; + + /* In case of failure, make sure the previous IP-Address + * configuration is restored */ + memcpy(&entry->addr.ip, ip_addr_backup, sizeof(entry->addr.ip)); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +/* Remove IP Address from sccp address */ +DEFUN(cs7_sccpaddr_ip_del, cs7_sccpaddr_ip_del_cmd, + "no ip-address", NO_STR "Remove IP-Address\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv4; + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_IPv6; + memset(&entry->addr.ip, 0, sizeof(entry->addr.ip)); + return CMD_SUCCESS; +} +#endif + +/* Configure global title and switch nodes */ +DEFUN(cs7_sccpaddr_gt, cs7_sccpaddr_gt_cmd, + "global-title", "Add/Modify Global Title\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + entry->addr.presence |= OSMO_SCCP_ADDR_T_GT; + vty->node = L_CS7_SCCPADDR_GT_NODE; + return CMD_SUCCESS; +} + +/* Remove global title from sccp address */ +DEFUN(cs7_sccpaddr_gt_del, cs7_sccpaddr_gt_del_cmd, + "no global-title", NO_STR "Remove Global Title\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.presence &= ~OSMO_SCCP_ADDR_T_GT; + entry->addr.gt = (struct osmo_sccp_gt) {}; + return CMD_SUCCESS; +} + +/* Set global title inicator of the sccp address gt */ +DEFUN(cs7_sccpaddr_gt_gti, cs7_sccpaddr_gt_gti_cmd, + "global-title-indicator <0-15>", "Set Global Title Indicator\n" "GTI\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.gt.gti = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* Set global title translation type of the sccp address gt */ +DEFUN(cs7_sccpaddr_gt_tt, cs7_sccpaddr_gt_tt_cmd, + "translation-type <0-255>", "Set Global Title Translation Type\n" "TT\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.gt.tt = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* Set global title numbering plan indicator of the sccp address gt */ +DEFUN(cs7_sccpaddr_gt_npi, cs7_sccpaddr_gt_npi_cmd, + "numbering-plan-indicator <0-15>", + "Set Global Title Numbering Plan Indicator\n" "NPI\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.gt.npi = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* Set global title nature of address indicator of the sccp address gt */ +DEFUN(cs7_sccpaddr_gt_nai, cs7_sccpaddr_gt_nai_cmd, + "nature-of-address-indicator <0-127>", + "Set Global Title Nature of Address Indicator\n" "NAI\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + entry->addr.gt.nai = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* Set global title digits of the sccp address gt */ +DEFUN(cs7_sccpaddr_gt_digits, cs7_sccpaddr_gt_digits_cmd, + "digits DIGITS", "Set Global Title Digits\n" "Number digits\n") +{ + struct osmo_sccp_addr_entry *entry = + (struct osmo_sccp_addr_entry *)vty->index; + OSMO_ASSERT(entry); + + if (strlen(argv[0]) > sizeof(entry->addr.gt.digits)) { + vty_out(vty, "Number too long!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + memset(entry->addr.gt.digits, 0, sizeof(entry->addr.gt.digits)); + + osmo_strlcpy(entry->addr.gt.digits, argv[0], + sizeof(entry->addr.gt.digits)); + return CMD_SUCCESS; +} + +/*********************************************************************** + * Common + ***********************************************************************/ + +static void write_one_cs7(struct vty *vty, struct osmo_ss7_instance *inst) +{ + struct osmo_ss7_asp *asp; + struct osmo_ss7_as *as; + struct osmo_ss7_route_table *rtable; + struct osmo_xua_server *oxs; + + vty_out(vty, "cs7 instance %u%s", inst->cfg.id, VTY_NEWLINE); + if (inst->cfg.description) + vty_out(vty, " description %s%s", inst->cfg.description, VTY_NEWLINE); + if (inst->cfg.network_indicator) + vty_out(vty, " network-indicator %s%s", + get_value_string(ss7_network_indicator_vals, + inst->cfg.network_indicator), + VTY_NEWLINE); + + if (inst->cfg.pc_fmt.component_len[0] != 3 || + inst->cfg.pc_fmt.component_len[1] != 8 || + inst->cfg.pc_fmt.component_len[2] != 3) { + vty_out(vty, " point-code format %u", + inst->cfg.pc_fmt.component_len[0]); + if (inst->cfg.pc_fmt.component_len[1]) + vty_out(vty, " %u", inst->cfg.pc_fmt.component_len[1]); + if (inst->cfg.pc_fmt.component_len[2]) + vty_out(vty, " %u", inst->cfg.pc_fmt.component_len[2]); + vty_out(vty, "%s", VTY_NEWLINE); + } + + if (inst->cfg.pc_fmt.delimiter != '.') + vty_out(vty, " point-code delimiter dash%s", VTY_NEWLINE); + + if (osmo_ss7_pc_is_valid(inst->cfg.primary_pc)) + vty_out(vty, " point-code %s%s", + osmo_ss7_pointcode_print(inst, inst->cfg.primary_pc), + VTY_NEWLINE); + + if (inst->cfg.permit_dyn_rkm_alloc) + vty_out(vty, " xua rkm routing-key-allocation dynamic-permitted%s", VTY_NEWLINE); + + /* first dump ASPs, as ASs reference them */ + llist_for_each_entry(asp, &inst->asp_list, list) + write_one_asp(vty, asp); + + /* then dump ASPs, as routes reference them */ + llist_for_each_entry(as, &inst->as_list, list) + write_one_as(vty, as); + + /* now dump everything that is relevent for the SG role */ + if (cs7_role == CS7_ROLE_SG) { + + /* dump routes, as their target ASs exist */ + llist_for_each_entry(rtable, &inst->rtable_list, list) + write_one_rtable(vty, rtable); + + llist_for_each_entry(oxs, &inst->xua_servers, list) + write_one_xua(vty, oxs); + } + + /* Append SCCP Addressbook */ + write_sccp_addressbook(vty, inst); +} + + +int osmo_ss7_vty_go_parent(struct vty *vty) +{ + struct osmo_ss7_as *as; + struct osmo_ss7_asp *asp; + struct osmo_ss7_route_table *rtbl; + struct osmo_xua_server *oxs; + struct osmo_sccp_addr_entry *entry; + + switch (vty->node) { + case L_CS7_ASP_NODE: + asp = vty->index; + osmo_ss7_asp_restart(asp); + vty->node = L_CS7_NODE; + vty->index = asp->inst; + break; + case L_CS7_RTABLE_NODE: + rtbl = vty->index; + vty->node = L_CS7_NODE; + vty->index = rtbl->inst; + break; + case L_CS7_AS_NODE: + as = vty->index; + vty->node = L_CS7_NODE; + vty->index = as->inst; + break; + case L_CS7_XUA_NODE: + oxs = vty->index; + vty->node = L_CS7_NODE; + vty->index = oxs->inst; + break; + case L_CS7_SCCPADDR_NODE: + entry = vty->index; + vty->node = L_CS7_NODE; + vty->index = entry->inst; + break; + case L_CS7_SCCPADDR_GT_NODE: + vty->node = L_CS7_SCCPADDR_NODE; + vty->index = NULL; + break; + case L_CS7_NODE: + default: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + } + return 0; +} + +int osmo_ss7_is_config_node(struct vty *vty, int node) +{ + switch (node) { + case L_CS7_NODE: + case L_CS7_ASP_NODE: + case L_CS7_RTABLE_NODE: + case L_CS7_XUA_NODE: + case L_CS7_AS_NODE: + case L_CS7_SCCPADDR_NODE: + case L_CS7_SCCPADDR_GT_NODE: + return 1; + default: + return 0; + } +} + +/* Commands for SCCP-Addressbook */ +static void vty_init_addr(void) +{ + install_node(&sccpaddr_node, NULL); + vty_install_default(L_CS7_SCCPADDR_NODE); + install_element(L_CS7_NODE, &cs7_show_sccpaddr_cmd); + install_element(L_CS7_NODE, &cs7_sccpaddr_cmd); + install_element(L_CS7_NODE, &cs7_sccpaddr_del_cmd); + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_pc_del_cmd); + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_ssn_del_cmd); +#if 0 + /* FIXME: IP-Address based SCCP-Routing is currently not supported, + * so we leave the related VTY options out for now */ + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_ip_del_cmd); +#endif + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_gt_del_cmd); + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_ri_cmd); + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_pc_cmd); + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_ssn_cmd); +#if 0 + /* FIXME: IP-Address based SCCP-Routing is currently not supported, + * so we leave the related VTY options out for now */ + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_ipv4_cmd); + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_ipv6_cmd); +#endif + install_element(L_CS7_SCCPADDR_NODE, &cs7_sccpaddr_gt_cmd); + install_node(&sccpaddr_gt_node, NULL); + vty_install_default(L_CS7_SCCPADDR_GT_NODE); + install_element(L_CS7_SCCPADDR_GT_NODE, &cs7_sccpaddr_gt_gti_cmd); + install_element(L_CS7_SCCPADDR_GT_NODE, &cs7_sccpaddr_gt_tt_cmd); + install_element(L_CS7_SCCPADDR_GT_NODE, &cs7_sccpaddr_gt_npi_cmd); + install_element(L_CS7_SCCPADDR_GT_NODE, &cs7_sccpaddr_gt_nai_cmd); + install_element(L_CS7_SCCPADDR_GT_NODE, &cs7_sccpaddr_gt_digits_cmd); +} + +static void vty_init_shared(void *ctx) +{ + g_ctx = ctx; + + install_element_ve(&show_cs7_user_cmd); + + /* the mother of all VTY config nodes */ + install_element(CONFIG_NODE, &cs7_instance_cmd); + + install_node(&cs7_node, config_write_cs7); + vty_install_default(L_CS7_NODE); + install_element(L_CS7_NODE, &cfg_description_cmd); + install_element(L_CS7_NODE, &cs7_net_ind_cmd); + install_element(L_CS7_NODE, &cs7_point_code_cmd); + install_element(L_CS7_NODE, &cs7_pc_format_cmd); + install_element(L_CS7_NODE, &cs7_pc_format_def_cmd); + install_element(L_CS7_NODE, &cs7_pc_delimiter_cmd); + install_element(L_CS7_NODE, &cs7_permit_dyn_rkm_cmd); + + install_node(&asp_node, NULL); + vty_install_default(L_CS7_ASP_NODE); + install_element_ve(&show_cs7_asp_cmd); + install_element(L_CS7_NODE, &cs7_asp_cmd); + install_element(L_CS7_NODE, &no_cs7_asp_cmd); + install_element(L_CS7_ASP_NODE, &cfg_description_cmd); + install_element(L_CS7_ASP_NODE, &asp_remote_ip_cmd); + install_element(L_CS7_ASP_NODE, &asp_local_ip_cmd); + install_element(L_CS7_ASP_NODE, &asp_qos_class_cmd); + install_element(L_CS7_ASP_NODE, &asp_block_cmd); + install_element(L_CS7_ASP_NODE, &asp_shutdown_cmd); + + install_node(&as_node, NULL); + vty_install_default(L_CS7_AS_NODE); + install_element_ve(&show_cs7_as_cmd); + install_element(L_CS7_NODE, &cs7_as_cmd); + install_element(L_CS7_NODE, &no_cs7_as_cmd); + install_element(L_CS7_AS_NODE, &cfg_description_cmd); + install_element(L_CS7_AS_NODE, &as_asp_cmd); + install_element(L_CS7_AS_NODE, &as_no_asp_cmd); + install_element(L_CS7_AS_NODE, &as_traf_mode_cmd); + install_element(L_CS7_AS_NODE, &as_recov_tout_cmd); + install_element(L_CS7_AS_NODE, &as_qos_class_cmd); + install_element(L_CS7_AS_NODE, &as_rout_key_cmd); + install_element(L_CS7_AS_NODE, &as_rout_key_si_cmd); + install_element(L_CS7_AS_NODE, &as_rout_key_ssn_cmd); + install_element(L_CS7_AS_NODE, &as_rout_key_si_ssn_cmd); + install_element(L_CS7_AS_NODE, &as_pc_override_cmd); + + vty_init_addr(); +} + +void osmo_ss7_vty_init_asp(void *ctx) +{ + cs7_role = CS7_ROLE_ASP; + vty_init_shared(ctx); +} + +void osmo_ss7_vty_init_sg(void *ctx) +{ + cs7_role = CS7_ROLE_SG; + vty_init_shared(ctx); + + install_node(&rtable_node, NULL); + vty_install_default(L_CS7_RTABLE_NODE); + install_element_ve(&show_cs7_route_cmd); + install_element(L_CS7_NODE, &cs7_route_table_cmd); + install_element(L_CS7_RTABLE_NODE, &cfg_description_cmd); + install_element(L_CS7_RTABLE_NODE, &cs7_rt_upd_cmd); + install_element(L_CS7_RTABLE_NODE, &cs7_rt_rem_cmd); + + install_node(&xua_node, NULL); + vty_install_default(L_CS7_XUA_NODE); + install_element(L_CS7_NODE, &cs7_xua_cmd); + install_element(L_CS7_NODE, &no_cs7_xua_cmd); + install_element(L_CS7_XUA_NODE, &xua_local_ip_cmd); + install_element(L_CS7_XUA_NODE, &xua_accept_dyn_asp_cmd); +} diff --git a/src/sccp.c b/src/sccp.c new file mode 100644 index 0000000..6c77cc4 --- /dev/null +++ b/src/sccp.c @@ -0,0 +1,1483 @@ +/* + * SCCP management code + * + * (C) 2009, 2010, 2013 by Holger Hans Peter Freyther + * (C) 2009, 2010, 2013 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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include + +#include +#include +#include +#include + +#include + +// Unassigned debug area +static int DSCCP = 0; + +static void *tall_sccp_ctx; +static LLIST_HEAD(sccp_connections); + +#define SCCP_MSG_SIZE 4096 +#define SCCP_MSG_HEADROOM 128 + +/* global data */ +const struct sockaddr_sccp sccp_ssn_bssap = { + .sccp_family = 0, + .sccp_ssn = SCCP_SSN_BSSAP, +}; + +struct sccp_system { + /* layer3 -> layer2 */ + void (*write_data)(struct sccp_connection *conn, struct msgb *data, + void *gctx, void *ctx); + void *write_context; +}; + + +static struct sccp_system sccp_system = { + .write_data = NULL, +}; + +struct sccp_data_callback { + /* connection based */ + int (*accept_cb)(struct sccp_connection *, void *); + void *accept_context; + + /* connection less */ + int (*read_cb)(struct msgb *, unsigned int, void *); + void *read_context; + + uint8_t ssn; + struct llist_head callback; +}; + +static LLIST_HEAD(sccp_callbacks); + +static struct sccp_data_callback *_find_ssn(uint8_t ssn) +{ + struct sccp_data_callback *cb; + + llist_for_each_entry(cb, &sccp_callbacks, callback) { + if (cb->ssn == ssn) + return cb; + } + + /* need to add one */ + cb = talloc_zero(tall_sccp_ctx, struct sccp_data_callback); + if (!cb) { + LOGP(DSCCP, LOGL_ERROR, "Failed to allocate sccp callback.\n"); + return NULL; + } + + cb->ssn = ssn; + llist_add_tail(&cb->callback, &sccp_callbacks); + return cb; +} + + +static void _send_msg(struct sccp_connection *conn, struct msgb *msg, void *ctx) +{ + sccp_system.write_data(conn, msg, sccp_system.write_context, ctx); +} + +/* + * parsing routines + */ +static int copy_address(struct sccp_address *addr, uint8_t offset, struct msgb *msgb) +{ + struct sccp_called_party_address *party; + + int room = msgb_l2len(msgb) - offset; + uint8_t read = 0; + uint8_t length; + + if (room <= 0) { + LOGP(DSCCP, LOGL_ERROR, "Not enough room for an address: %u\n", room); + return -1; + } + + length = msgb->l2h[offset]; + if (room <= length) { + LOGP(DSCCP, LOGL_ERROR, "Not enough room for optional data %u %u\n", room, length); + return -1; + } + + + party = (struct sccp_called_party_address *)(msgb->l2h + offset + 1); + if (party->point_code_indicator) { + if (length <= read + 2) { + LOGP(DSCCP, LOGL_ERROR, "POI does not fit %u\n", length); + return -1; + } + + + memcpy(&addr->poi, &party->data[read], 2); + read += 2; + } + + if (party->ssn_indicator) { + if (length <= read + 1) { + LOGP(DSCCP, LOGL_ERROR, "SSN does not fit %u\n", length); + return -1; + } + + addr->ssn = party->data[read]; + read += 1; + } + + /* copy the GTI over */ + if (party->global_title_indicator) { + addr->gti_len = length - read - 1; + addr->gti_data = &party->data[read]; + } + + addr->address = *party; + return 0; +} + +static int _sccp_parse_optional_data(const int offset, + struct msgb *msgb, struct sccp_optional_data *data) +{ + uint16_t room = msgb_l2len(msgb) - offset; + uint16_t read = 0; + + while (room > read) { + uint8_t type = msgb->l2h[offset + read]; + if (type == SCCP_PNC_END_OF_OPTIONAL) + return 0; + + if (read + 1 >= room) { + LOGP(DSCCP, LOGL_ERROR, "no place for length\n"); + return 0; + } + + uint8_t length = msgb->l2h[offset + read + 1]; + read += 2 + length; + + + if (room <= read) { + LOGP(DSCCP, LOGL_ERROR, + "no space for the data: type: %d read: %d room: %d l2: %d\n", + type, read, room, msgb_l2len(msgb)); + return 0; + } + + if (type == SCCP_PNC_DATA) { + data->data_len = length; + data->data_start = offset + read - length; + } + + } + + return -1; +} + +int _sccp_parse_connection_request(struct msgb *msgb, struct sccp_parse_result *result) +{ + static const uint32_t header_size = + sizeof(struct sccp_connection_request); + static const uint32_t optional_offset = + offsetof(struct sccp_connection_request, optional_start); + static const uint32_t called_offset = + offsetof(struct sccp_connection_request, variable_called); + + struct sccp_connection_request *req = (struct sccp_connection_request *)msgb->l2h; + struct sccp_optional_data optional_data; + + /* header check */ + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n", + msgb_l2len(msgb), header_size); + return -1; + } + + /* copy out the calling and called address. Add the offset */ + if (copy_address(&result->called, called_offset + req->variable_called, msgb) != 0) + return -1; + + result->source_local_reference = &req->source_local_reference; + + /* + * parse optional data. + */ + memset(&optional_data, 0, sizeof(optional_data)); + if (_sccp_parse_optional_data(optional_offset + req->optional_start, msgb, &optional_data) != 0) { + LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n"); + return -1; + } + + if (optional_data.data_len != 0) { + msgb->l3h = &msgb->l2h[optional_data.data_start]; + result->data_len = optional_data.data_len; + } else { + result->data_len = 0; + } + + return 0; +} + +int _sccp_parse_connection_released(struct msgb *msgb, struct sccp_parse_result *result) +{ + static size_t header_size = sizeof(struct sccp_connection_released); + static size_t optional_offset = offsetof(struct sccp_connection_released, optional_start); + + struct sccp_optional_data optional_data; + struct sccp_connection_released *rls = (struct sccp_connection_released *) msgb->l2h; + + /* we don't have enough size for the struct */ + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb > header_size %u %zu\n", + msgb_l2len(msgb), header_size); + return -1; + } + + memset(&optional_data, 0, sizeof(optional_data)); + if (_sccp_parse_optional_data(optional_offset + rls->optional_start, msgb, &optional_data) != 0) { + LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n"); + return -1; + } + + result->source_local_reference = &rls->source_local_reference; + result->destination_local_reference = &rls->destination_local_reference; + + if (optional_data.data_len != 0) { + msgb->l3h = &msgb->l2h[optional_data.data_start]; + result->data_len = optional_data.data_len; + } else { + result->data_len = 0; + } + + return 0; +} + +int _sccp_parse_connection_refused(struct msgb *msgb, struct sccp_parse_result *result) +{ + static const uint32_t header_size = + sizeof(struct sccp_connection_refused); + static int optional_offset = offsetof(struct sccp_connection_refused, optional_start); + + struct sccp_optional_data optional_data; + struct sccp_connection_refused *ref; + + /* header check */ + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n", + msgb_l2len(msgb), header_size); + return -1; + } + + ref = (struct sccp_connection_refused *) msgb->l2h; + + result->destination_local_reference = &ref->destination_local_reference; + + memset(&optional_data, 0, sizeof(optional_data)); + if (_sccp_parse_optional_data(optional_offset + ref->optional_start, msgb, &optional_data) != 0) { + LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n"); + return -1; + } + + /* optional data */ + if (optional_data.data_len != 0) { + msgb->l3h = &msgb->l2h[optional_data.data_start]; + result->data_len = optional_data.data_len; + } else { + result->data_len = 0; + } + + return 0; +} + +int _sccp_parse_connection_confirm(struct msgb *msgb, struct sccp_parse_result *result) +{ + static uint32_t header_size = + sizeof(struct sccp_connection_confirm); + static const uint32_t optional_offset = + offsetof(struct sccp_connection_confirm, optional_start); + + struct sccp_optional_data optional_data; + struct sccp_connection_confirm *con; + + /* header check */ + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n", + msgb_l2len(msgb), header_size); + return -1; + } + + con = (struct sccp_connection_confirm *) msgb->l2h; + result->destination_local_reference = &con->destination_local_reference; + result->source_local_reference = &con->source_local_reference; + + memset(&optional_data, 0, sizeof(optional_data)); + if (_sccp_parse_optional_data(optional_offset + con->optional_start, msgb, &optional_data) != 0) { + LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n"); + return -1; + } + + if (optional_data.data_len != 0) { + msgb->l3h = &msgb->l2h[optional_data.data_start]; + result->data_len = optional_data.data_len; + } else { + result->data_len = 0; + } + + return 0; +} + +int _sccp_parse_connection_release_complete(struct msgb *msgb, struct sccp_parse_result *result) +{ + static size_t header_size = sizeof(struct sccp_connection_release_complete); + + struct sccp_connection_release_complete *cmpl; + + /* header check */ + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %zu\n", + msgb_l2len(msgb), header_size); + return -1; + } + + cmpl = (struct sccp_connection_release_complete *) msgb->l2h; + result->source_local_reference = &cmpl->source_local_reference; + result->destination_local_reference = &cmpl->destination_local_reference; + + return 0; +} + +int _sccp_parse_connection_dt1(struct msgb *msgb, struct sccp_parse_result *result) +{ + static size_t header_size = sizeof(struct sccp_data_form1); + static size_t variable_offset = offsetof(struct sccp_data_form1, variable_start); + + struct sccp_data_form1 *dt1 = (struct sccp_data_form1 *)msgb->l2h; + + /* we don't have enough size for the struct */ + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb > header_size %u %zu\n", + msgb_l2len(msgb), header_size); + return -1; + } + + if (dt1->segmenting != 0) { + LOGP(DSCCP, LOGL_ERROR, "This packet has segmenting, not supported: %d\n", dt1->segmenting); + return -1; + } + + result->destination_local_reference = &dt1->destination_local_reference; + + /* some more size checks in here */ + if (msgb_l2len(msgb) < variable_offset + dt1->variable_start + 1) { + LOGP(DSCCP, LOGL_ERROR, "Not enough space for variable start: %u %u\n", + msgb_l2len(msgb), dt1->variable_start); + return -1; + } + + result->data_len = msgb->l2h[variable_offset + dt1->variable_start]; + msgb->l3h = &msgb->l2h[dt1->variable_start + variable_offset + 1]; + + if (msgb_l3len(msgb) < result->data_len) { + LOGP(DSCCP, LOGL_ERROR, "Not enough room for the payload: %u %u\n", + msgb_l3len(msgb), result->data_len); + return -1; + } + + return 0; +} + +int _sccp_parse_udt(struct msgb *msgb, struct sccp_parse_result *result) +{ + static const uint32_t header_size = sizeof(struct sccp_data_unitdata); + static const uint32_t called_offset = offsetof(struct sccp_data_unitdata, variable_called); + static const uint32_t calling_offset = offsetof(struct sccp_data_unitdata, variable_calling); + static const uint32_t data_offset = offsetof(struct sccp_data_unitdata, variable_data); + + struct sccp_data_unitdata *udt = (struct sccp_data_unitdata *)msgb->l2h; + + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n", + msgb_l2len(msgb), header_size); + return -1; + } + + /* copy out the calling and called address. Add the off */ + if (copy_address(&result->called, called_offset + udt->variable_called, msgb) != 0) + return -1; + + if (copy_address(&result->calling, calling_offset + udt->variable_calling, msgb) != 0) + return -1; + + /* we don't have enough size for the data */ + if (msgb_l2len(msgb) < data_offset + udt->variable_data + 1) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header + offset %u %u %u\n", + msgb_l2len(msgb), header_size, udt->variable_data); + return -1; + } + + + msgb->l3h = &udt->data[udt->variable_data]; + result->data_len = msgb_l3len(msgb); + + if (msgb_l3len(msgb) < msgb->l3h[-1]) { + LOGP(DSCCP, LOGL_ERROR, "msgb is truncated is: %u should: %u\n", + msgb_l3len(msgb), msgb->l3h[-1]); + return -1; + } + + return 0; +} + +static int _sccp_parse_it(struct msgb *msgb, struct sccp_parse_result *result) +{ + static const uint32_t header_size = sizeof(struct sccp_data_it); + + struct sccp_data_it *it; + + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n", + msgb_l2len(msgb), header_size); + return -1; + } + + it = (struct sccp_data_it *) msgb->l2h; + result->data_len = 0; + result->source_local_reference = &it->source_local_reference; + result->destination_local_reference = &it->destination_local_reference; + return 0; +} + +static int _sccp_parse_err(struct msgb *msgb, struct sccp_parse_result *result) +{ + static const uint32_t header_size = sizeof(struct sccp_proto_err); + + struct sccp_proto_err *err; + + if (msgb_l2len(msgb) < header_size) { + LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n", + msgb_l2len(msgb), header_size); + return -1; + } + + err = (struct sccp_proto_err *) msgb->l2h; + result->data_len = 0; + result->destination_local_reference = &err->destination_local_reference; + return 0; +} + +int sccp_create_sccp_addr(struct msgb *msg, const struct sockaddr_sccp *sock) +{ + uint8_t *len, *ai, *gti; + + len = msgb_put(msg, 1); + ai = msgb_put(msg, 1); + + + if (sock->gti) + ai[0] = 0 << 6 | (sock->gti_ind & 0x0f) << 2 | 1 << 1; + else + ai[0] = 1 << 6 | 1 << 1; + + /* store a point code */ + if (sock->use_poi) { + uint8_t *poi; + + ai[0] |= 0x01; + poi = msgb_put(msg, 2); + poi[0] = sock->poi[0]; + poi[1] = sock->poi[1]; + } + + /* copy the SSN */ + msgb_v_put(msg, sock->sccp_ssn); + + /* copy the gti if it is present */ + if (sock->gti) { + gti = msgb_put(msg, sock->gti_len); + memcpy(gti, sock->gti, sock->gti_len); + } + + /* update the length now */ + len[0] = msg->tail - len - 1; + return len[0] + 1; +} + +/* + * Send UDT. Currently we have a fixed address... + */ +struct msgb *sccp_create_udt(int class, const struct sockaddr_sccp *in, + const struct sockaddr_sccp *out, uint8_t *in_data, int len) +{ + struct sccp_data_unitdata *udt; + uint8_t *data; + int out_len, inp_len; + + if (len > 256) { + LOGP(DSCCP, LOGL_ERROR, "The payload is too big for one udt\n"); + return NULL; + } + + struct msgb *msg = msgb_alloc_headroom(SCCP_MSG_SIZE, + SCCP_MSG_HEADROOM, "sccp: udt"); + if (!msg) + return NULL; + + msg->l2h = &msg->data[0]; + udt = (struct sccp_data_unitdata *)msgb_put(msg, sizeof(*udt)); + + udt->type = SCCP_MSG_TYPE_UDT; + udt->proto_class = class; + + /* for variable data we start with a size and the data */ + out_len = sccp_create_sccp_addr(msg, out); + inp_len = sccp_create_sccp_addr(msg, in); + + /* update the offsets now */ + udt->variable_called = 3; + udt->variable_calling = 2 + out_len; + udt->variable_data = 1 + out_len + inp_len; + + /* copy the payload */ + data = msgb_put(msg, 1 + len); + data[0] = len; + memcpy(&data[1], in_data, len); + + return msg; +} + +static int _sccp_send_data(int class, const struct sockaddr_sccp *in, + const struct sockaddr_sccp *out, + struct msgb *payload, void *ctx) +{ + struct msgb *msg; + + msg = sccp_create_udt(class, in, out, payload->l3h, msgb_l3len(payload)); + if (!msg) + return -1; + + _send_msg(NULL, msg, ctx); + return 0; +} + +static int _sccp_handle_read(struct msgb *msgb) +{ + struct sccp_data_callback *cb; + struct sccp_parse_result result; + + if (_sccp_parse_udt(msgb, &result) != 0) + return -1; + + cb = _find_ssn(result.called.ssn); + if (!cb || !cb->read_cb) { + LOGP(DSCCP, LOGL_ERROR, "No routing for UDT for called SSN: %u\n", result.called.ssn); + return -1; + } + + /* sanity check */ + return cb->read_cb(msgb, msgb_l3len(msgb), cb->read_context); +} + +/* + * handle connection orientated methods + */ +static int source_local_reference_is_free(struct sccp_source_reference *reference) +{ + struct sccp_connection *connection; + + llist_for_each_entry(connection, &sccp_connections, list) { + if (memcmp(reference, &connection->source_local_reference, sizeof(*reference)) == 0) + return -1; + } + + return 0; +} + +static int destination_local_reference_is_free(struct sccp_source_reference *reference) +{ + struct sccp_connection *connection; + + llist_for_each_entry(connection, &sccp_connections, list) { + if (memcmp(reference, &connection->destination_local_reference, sizeof(*reference)) == 0) + return -1; + } + + return 0; +} + +static int assign_source_local_reference(struct sccp_connection *connection) +{ + static uint32_t last_ref = 0x30000; + int wrapped = 0; + + do { + struct sccp_source_reference reference; + reference.octet1 = (last_ref >> 0) & 0xff; + reference.octet2 = (last_ref >> 8) & 0xff; + reference.octet3 = (last_ref >> 16) & 0xff; + + ++last_ref; + /* do not use the reversed word and wrap around */ + if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) { + LOGP(DSCCP, LOGL_DEBUG, "Wrapped searching for a free code\n"); + last_ref = 0; + ++wrapped; + } + + if (source_local_reference_is_free(&reference) == 0) { + connection->source_local_reference = reference; + return 0; + } + } while (wrapped != 2); + + LOGP(DSCCP, LOGL_ERROR, "Finding a free reference failed\n"); + return -1; +} + +static void _sccp_set_connection_state(struct sccp_connection *connection, int new_state) +{ + int old_state = connection->connection_state; + + connection->connection_state = new_state; + if (connection->state_cb) + connection->state_cb(connection, old_state); +} + +struct msgb *sccp_create_refuse(struct sccp_source_reference *src_ref, int cause, uint8_t *inp, int length) +{ + struct msgb *msgb; + struct sccp_connection_refused *ref; + uint8_t *data; + + msgb = msgb_alloc_headroom(SCCP_MSG_SIZE, + SCCP_MSG_HEADROOM, "sccp ref"); + if (!msgb) { + LOGP(DSCCP, LOGL_ERROR, "Failed to allocate refusal msg.\n"); + return NULL; + } + + msgb->l2h = &msgb->data[0]; + + ref = (struct sccp_connection_refused *) msgb_put(msgb, sizeof(*ref)); + ref->type = SCCP_MSG_TYPE_CREF; + memcpy(&ref->destination_local_reference, src_ref, + sizeof(struct sccp_source_reference)); + ref->cause = cause; + ref->optional_start = 1; + + if (inp) { + data = msgb_put(msgb, 1 + 1 + length); + data[0] = SCCP_PNC_DATA; + data[1] = length; + memcpy(&data[2], inp, length); + } + + data = msgb_put(msgb, 1); + data[0] = SCCP_PNC_END_OF_OPTIONAL; + return msgb; +} + +static int _sccp_send_refuse(struct sccp_source_reference *src_ref, int cause, void *ctx) +{ + struct msgb *msgb = sccp_create_refuse(src_ref, cause, NULL, 0); + if (!msgb) + return -1; + + _send_msg(NULL, msgb, ctx); + return 0; +} + +struct msgb *sccp_create_cc(struct sccp_source_reference *src_ref, + struct sccp_source_reference *dst_ref) +{ + struct msgb *response; + struct sccp_connection_confirm *confirm; + uint8_t *optional_data; + + response = msgb_alloc_headroom(SCCP_MSG_SIZE, + SCCP_MSG_HEADROOM, "sccp confirm"); + if (!response) { + LOGP(DSCCP, LOGL_ERROR, "Failed to create SCCP Confirm.\n"); + return NULL; + } + + response->l2h = &response->data[0]; + + confirm = (struct sccp_connection_confirm *) msgb_put(response, sizeof(*confirm)); + + confirm->type = SCCP_MSG_TYPE_CC; + memcpy(&confirm->destination_local_reference, + dst_ref, sizeof(*dst_ref)); + memcpy(&confirm->source_local_reference, + src_ref, sizeof(*src_ref)); + confirm->proto_class = 2; + confirm->optional_start = 1; + + optional_data = (uint8_t *) msgb_put(response, 1); + optional_data[0] = SCCP_PNC_END_OF_OPTIONAL; + return response; +} + +static int _sccp_send_connection_confirm(struct sccp_connection *connection) +{ + struct msgb *response; + + if (assign_source_local_reference(connection) != 0) + return -1; + + response = sccp_create_cc(&connection->source_local_reference, + &connection->destination_local_reference); + if (!response) + return -1; + + _send_msg(connection, response, NULL); + _sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_ESTABLISHED); + return 0; +} + +struct msgb *sccp_create_cr(const struct sccp_source_reference *src_ref, + const struct sockaddr_sccp *called, + const uint8_t *l3_data, size_t l3_length) +{ + struct msgb *request; + struct sccp_connection_request *req; + uint8_t *data; + uint8_t extra_size = 3 + 1; + int called_len; + + if (l3_data && (l3_length < 3 || l3_length > 130)) { + LOGP(DSCCP, LOGL_ERROR, "Invalid amount of data... %zu\n", l3_length); + return NULL; + } + + if (l3_data) + extra_size += 2 + l3_length; + request = msgb_alloc_headroom(SCCP_MSG_SIZE, + SCCP_MSG_HEADROOM, "sccp connection request"); + request->l2h = &request->data[0]; + req = (struct sccp_connection_request *) msgb_put(request, sizeof(*req)); + + req->type = SCCP_MSG_TYPE_CR; + memcpy(&req->source_local_reference, src_ref, sizeof(*src_ref)); + req->proto_class = 2; + + /* write the called party address */ + called_len = sccp_create_sccp_addr(request, called); + + /* update the offsets */ + req->variable_called = 2; + req->optional_start = 1 + called_len; + + /* write the payload */ + if (l3_data) { + data = msgb_put(request, 2 + l3_length); + data[0] = SCCP_PNC_DATA; + data[1] = l3_length; + memcpy(&data[2], l3_data, l3_length); + } + + data = msgb_put(request, 1); + data[0] = SCCP_PNC_END_OF_OPTIONAL; + + return request; +} + +static int _sccp_send_connection_request(struct sccp_connection *connection, + const struct sockaddr_sccp *called, struct msgb *msg) +{ + struct msgb *request; + + /* try to find an id */ + if (assign_source_local_reference(connection) != 0) { + LOGP(DSCCP, LOGL_ERROR, "Assigning a local reference failed.\n"); + _sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_SETUP_ERROR); + return -1; + } + + request = sccp_create_cr(&connection->source_local_reference, called, + msg ? msg->l3h : NULL, + msg ? msgb_l3len(msg) : 0); + if (!request) { + _sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_SETUP_ERROR); + return -1; + } + + llist_add_tail(&connection->list, &sccp_connections); + _sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_REQUEST); + + _send_msg(connection, request, NULL); + return 0; +} + +struct msgb *sccp_create_dt1(struct sccp_source_reference *dst_ref, uint8_t *inp_data, uint8_t len) +{ + struct msgb *msgb; + struct sccp_data_form1 *dt1; + uint8_t *data; + + msgb = msgb_alloc_headroom(SCCP_MSG_SIZE, + SCCP_MSG_HEADROOM, "sccp dt1"); + if (!msgb) { + LOGP(DSCCP, LOGL_ERROR, "Failed to create DT1 msg.\n"); + return NULL; + } + + msgb->l2h = &msgb->data[0]; + + dt1 = (struct sccp_data_form1 *) msgb_put(msgb, sizeof(*dt1)); + dt1->type = SCCP_MSG_TYPE_DT1; + memcpy(&dt1->destination_local_reference, dst_ref, + sizeof(struct sccp_source_reference)); + dt1->segmenting = 0; + + /* copy the data */ + dt1->variable_start = 1; + data = msgb_put(msgb, 1 + len); + data[0] = len; + memcpy(&data[1], inp_data, len); + + return msgb; +} + +static int _sccp_send_connection_data(struct sccp_connection *conn, struct msgb *_data) +{ + struct msgb *msgb; + + if (msgb_l3len(_data) < 2 || msgb_l3len(_data) > 256) { + LOGP(DSCCP, LOGL_ERROR, "data size too big, segmenting unimplemented.\n"); + return -1; + } + + msgb = sccp_create_dt1(&conn->destination_local_reference, + _data->l3h, msgb_l3len(_data)); + if (!msgb) + return -1; + + _send_msg(conn, msgb, NULL); + return 0; +} + +static int _sccp_send_connection_it(struct sccp_connection *conn) +{ + struct msgb *msgb; + struct sccp_data_it *it; + + msgb = msgb_alloc_headroom(SCCP_MSG_SIZE, + SCCP_MSG_HEADROOM, "sccp it"); + msgb->l2h = &msgb->data[0]; + it = (struct sccp_data_it *) msgb_put(msgb, sizeof(*it)); + it->type = SCCP_MSG_TYPE_IT; + memcpy(&it->destination_local_reference, &conn->destination_local_reference, + sizeof(struct sccp_source_reference)); + memcpy(&it->source_local_reference, &conn->source_local_reference, + sizeof(struct sccp_source_reference)); + + it->proto_class = 0x2; + it->sequencing[0] = it->sequencing[1] = 0; + it->credit = 0; + + _send_msg(conn, msgb, NULL); + return 0; +} + +struct msgb *sccp_create_rlsd(struct sccp_source_reference *src_ref, + struct sccp_source_reference *dst_ref, int cause) +{ + struct msgb *msg; + struct sccp_connection_released *rel; + uint8_t *data; + + msg = msgb_alloc_headroom(SCCP_MSG_SIZE, SCCP_MSG_HEADROOM, + "sccp: connection released"); + if (!msg) { + LOGP(DSCCP, LOGL_ERROR, "Failed to allocate RLSD.\n"); + return NULL; + } + + msg->l2h = &msg->data[0]; + rel = (struct sccp_connection_released *) msgb_put(msg, sizeof(*rel)); + rel->type = SCCP_MSG_TYPE_RLSD; + rel->release_cause = cause; + + /* copy the source references */ + memcpy(&rel->destination_local_reference, dst_ref, + sizeof(struct sccp_source_reference)); + memcpy(&rel->source_local_reference, src_ref, + sizeof(struct sccp_source_reference)); + + data = msgb_put(msg, 1); + data[0] = SCCP_PNC_END_OF_OPTIONAL; + return msg; +} + +static int _sccp_send_connection_released(struct sccp_connection *conn, int cause) +{ + struct msgb *msg; + + msg = sccp_create_rlsd(&conn->source_local_reference, + &conn->destination_local_reference, + cause); + if (!msg) + return -1; + + _sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_RELEASE); + _send_msg(conn, msg, NULL); + return 0; +} + +/* + * Open a connection. The following is going to happen: + * + * - Verify the packet, e.g. that we have no other connection + * that id. + * - Ask the user if he wants to accept the connection + * - Try to open the connection by assigning a source local reference + * and sending the packet + */ +static int _sccp_handle_connection_request(struct msgb *msgb, void *ctx) +{ + struct sccp_parse_result result; + + struct sccp_data_callback *cb; + struct sccp_connection *connection; + + if (_sccp_parse_connection_request(msgb, &result) != 0) + return -1; + + cb = _find_ssn(result.called.ssn); + if (!cb || !cb->accept_cb) { + LOGP(DSCCP, LOGL_ERROR, "No routing for CR for called SSN: %u\n", result.called.ssn); + return -1; + } + + /* check if the system wants this connection */ + connection = talloc_zero(tall_sccp_ctx, struct sccp_connection); + if (!connection) { + LOGP(DSCCP, LOGL_ERROR, "Allocation failed\n"); + return -1; + } + + /* + * sanity checks: + * - Is the source_local_reference in any other connection? + * then will call accept, assign a "destination" local reference + * and send a connection confirm, otherwise we will send a refuseed + * one.... + */ + if (destination_local_reference_is_free(result.source_local_reference) != 0) { + LOGP(DSCCP, LOGL_ERROR, "Need to reject connection with existing reference\n"); + _sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_SCCP_FAILURE, ctx); + talloc_free(connection); + return -1; + } + + connection->incoming = 1; + connection->destination_local_reference = *result.source_local_reference; + + if (cb->accept_cb(connection, cb->accept_context) != 0) { + _sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_END_USER_ORIGINATED, ctx); + _sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_REFUSED); + talloc_free(connection); + return 0; + } + + + llist_add_tail(&connection->list, &sccp_connections); + + if (_sccp_send_connection_confirm(connection) != 0) { + LOGP(DSCCP, LOGL_ERROR, "Sending confirm failed... no available source reference?\n"); + + _sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_SCCP_FAILURE, ctx); + _sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_REFUSED); + llist_del(&connection->list); + talloc_free(connection); + + return -1; + } + + /* + * If we have data let us forward things. + */ + if (result.data_len != 0 && connection->data_cb) { + connection->data_cb(connection, msgb, result.data_len); + } + + return 0; +} + +/* Handle the release confirmed */ +static int _sccp_handle_connection_release_complete(struct msgb *msgb) +{ + struct sccp_parse_result result; + struct sccp_connection *conn; + + if (_sccp_parse_connection_release_complete(msgb, &result) != 0) + return -1; + + /* find the connection */ + llist_for_each_entry(conn, &sccp_connections, list) { + if (conn->data_cb + && memcmp(&conn->source_local_reference, + result.destination_local_reference, + sizeof(conn->source_local_reference)) == 0 + && memcmp(&conn->destination_local_reference, + result.source_local_reference, + sizeof(conn->destination_local_reference)) == 0) { + goto found; + } + } + + + LOGP(DSCCP, LOGL_ERROR, "Release complete of unknown connection\n"); + return -1; + +found: + llist_del(&conn->list); + _sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_RELEASE_COMPLETE); + return 0; +} + +/* Handle the Data Form 1 message */ +static int _sccp_handle_connection_dt1(struct msgb *msgb) +{ + struct sccp_parse_result result; + struct sccp_connection *conn; + + if (_sccp_parse_connection_dt1(msgb, &result) != 0) + return -1; + + /* lookup if we have a connection with the given reference */ + llist_for_each_entry(conn, &sccp_connections, list) { + if (conn->data_cb + && memcmp(&conn->source_local_reference, + result.destination_local_reference, + sizeof(conn->source_local_reference)) == 0) { + goto found; + } + } + + LOGP(DSCCP, LOGL_ERROR, "No connection found for dt1 data\n"); + return -1; + +found: + conn->data_cb(conn, msgb, result.data_len); + return 0; +} + +/* confirm a connection release */ +static int _sccp_send_connection_release_complete(struct sccp_connection *connection) +{ + struct msgb *msgb; + struct sccp_connection_release_complete *rlc; + + msgb = msgb_alloc_headroom(SCCP_MSG_SIZE, + SCCP_MSG_HEADROOM, "sccp rlc"); + msgb->l2h = &msgb->data[0]; + + rlc = (struct sccp_connection_release_complete *) msgb_put(msgb, sizeof(*rlc)); + rlc->type = SCCP_MSG_TYPE_RLC; + memcpy(&rlc->destination_local_reference, + &connection->destination_local_reference, sizeof(struct sccp_source_reference)); + memcpy(&rlc->source_local_reference, + &connection->source_local_reference, sizeof(struct sccp_source_reference)); + + _send_msg(connection, msgb, NULL); + + /* + * Remove from the list of active connections and set the state. User code + * should now free the entry. + */ + llist_del(&connection->list); + _sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_RELEASE_COMPLETE); + return 0; +} + +/* connection released, send a released confirm */ +static int _sccp_handle_connection_released(struct msgb *msgb) +{ + struct sccp_parse_result result; + struct sccp_connection *conn; + + if (_sccp_parse_connection_released(msgb, &result) == -1) + return -1; + + /* lookup if we have a connection with the given reference */ + llist_for_each_entry(conn, &sccp_connections, list) { + if (conn->data_cb + && memcmp(&conn->source_local_reference, + result.destination_local_reference, + sizeof(conn->source_local_reference)) == 0 + && memcmp(&conn->destination_local_reference, + result.source_local_reference, + sizeof(conn->destination_local_reference)) == 0) { + goto found; + } + } + + + LOGP(DSCCP, LOGL_ERROR, "Unknown connection was released.\n"); + return -1; + + /* we have found a connection */ +found: + /* optional data */ + if (result.data_len != 0 && conn->data_cb) { + conn->data_cb(conn, msgb, result.data_len); + } + + /* generate a response */ + if (_sccp_send_connection_release_complete(conn) != 0) { + LOGP(DSCCP, LOGL_ERROR, "Sending release confirmed failed\n"); + return -1; + } + + return 0; +} + +static int _sccp_handle_connection_refused(struct msgb *msgb) +{ + struct sccp_parse_result result; + struct sccp_connection *conn; + + if (_sccp_parse_connection_refused(msgb, &result) != 0) + return -1; + + /* lookup if we have a connection with the given reference */ + llist_for_each_entry(conn, &sccp_connections, list) { + if (conn->incoming == 0 && conn->data_cb + && memcmp(&conn->source_local_reference, + result.destination_local_reference, + sizeof(conn->source_local_reference)) == 0) { + goto found; + } + } + + LOGP(DSCCP, LOGL_ERROR, "Refused but no connection found\n"); + return -1; + +found: + /* optional data */ + if (result.data_len != 0 && conn->data_cb) { + conn->data_cb(conn, msgb, result.data_len); + } + + + llist_del(&conn->list); + _sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_REFUSED); + return 0; +} + +static int _sccp_handle_connection_confirm(struct msgb *msgb) +{ + struct sccp_parse_result result; + struct sccp_connection *conn; + + if (_sccp_parse_connection_confirm(msgb, &result) != 0) + return -1; + + /* lookup if we have a connection with the given reference */ + llist_for_each_entry(conn, &sccp_connections, list) { + if (conn->incoming == 0 && conn->data_cb + && memcmp(&conn->source_local_reference, + result.destination_local_reference, + sizeof(conn->source_local_reference)) == 0) { + goto found; + } + } + + LOGP(DSCCP, LOGL_ERROR, "Confirmed but no connection found\n"); + return -1; + +found: + /* copy the addresses of the connection */ + conn->destination_local_reference = *result.source_local_reference; + _sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_ESTABLISHED); + + /* optional data */ + if (result.data_len != 0 && conn->data_cb) { + conn->data_cb(conn, msgb, result.data_len); + } + + return 0; +} + + +int sccp_system_init(void (*outgoing)(struct sccp_connection *conn, struct msgb *data, void *, void *), void *ctx) +{ + sccp_system.write_data = outgoing; + sccp_system.write_context = ctx; + + return 0; +} + +/* oh my god a real SCCP packet. need to dispatch it now */ +int sccp_system_incoming(struct msgb *msgb) +{ + return sccp_system_incoming_ctx(msgb, NULL); +} + +int sccp_system_incoming_ctx(struct msgb *msgb, void *ctx) +{ + if (msgb_l2len(msgb) < 1 ) { + LOGP(DSCCP, LOGL_ERROR, "Too short packet\n"); + return -1; + } + + int type = msgb->l2h[0]; + + switch(type) { + case SCCP_MSG_TYPE_CR: + return _sccp_handle_connection_request(msgb, ctx); + break; + case SCCP_MSG_TYPE_RLSD: + return _sccp_handle_connection_released(msgb); + break; + case SCCP_MSG_TYPE_CREF: + return _sccp_handle_connection_refused(msgb); + break; + case SCCP_MSG_TYPE_CC: + return _sccp_handle_connection_confirm(msgb); + break; + case SCCP_MSG_TYPE_RLC: + return _sccp_handle_connection_release_complete(msgb); + break; + case SCCP_MSG_TYPE_DT1: + return _sccp_handle_connection_dt1(msgb); + break; + case SCCP_MSG_TYPE_UDT: + return _sccp_handle_read(msgb); + break; + default: + LOGP(DSCCP, LOGL_ERROR, "unimplemented msg type: %d\n", type); + }; + + return -1; +} + +/* create a packet from the data */ +int sccp_connection_write(struct sccp_connection *connection, struct msgb *data) +{ + if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM + || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DSCCP, LOGL_ERROR, "sccp_connection_write: Wrong connection state: %p %d\n", + connection, connection->connection_state); + return -1; + } + + return _sccp_send_connection_data(connection, data); +} + +/* + * Send a Inactivity Test message. The owner of the connection + * should start a timer and call this method regularily. Calling + * this every 60 seconds should be good enough. + */ +int sccp_connection_send_it(struct sccp_connection *connection) +{ + if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM + || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DSCCP, LOGL_ERROR, "sccp_connection_write: Wrong connection state: %p %d\n", + connection, connection->connection_state); + return -1; + } + + return _sccp_send_connection_it(connection); +} + +/* send a connection release and wait for the connection released */ +int sccp_connection_close(struct sccp_connection *connection, int cause) +{ + if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM + || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DSCCP, LOGL_ERROR, "Can not close the connection. It was never opened: %p %d\n", + connection, connection->connection_state); + return -1; + } + + return _sccp_send_connection_released(connection, cause); +} + +int sccp_connection_free(struct sccp_connection *connection) +{ + if (connection->connection_state > SCCP_CONNECTION_STATE_NONE + && connection->connection_state < SCCP_CONNECTION_STATE_RELEASE_COMPLETE) { + LOGP(DSCCP, LOGL_ERROR, "The connection needs to be released before it is freed"); + return -1; + } + + talloc_free(connection); + return 0; +} + +int sccp_connection_force_free(struct sccp_connection *con) +{ + if (con->connection_state > SCCP_CONNECTION_STATE_NONE && + con->connection_state < SCCP_CONNECTION_STATE_RELEASE_COMPLETE) + llist_del(&con->list); + + con->connection_state = SCCP_CONNECTION_STATE_REFUSED; + sccp_connection_free(con); + return 0; +} + +struct sccp_connection *sccp_connection_socket(void) +{ + return talloc_zero(tall_sccp_ctx, struct sccp_connection); +} + +int sccp_connection_connect(struct sccp_connection *conn, + const struct sockaddr_sccp *local, + struct msgb *data) +{ + return _sccp_send_connection_request(conn, local, data); +} + +int sccp_connection_set_incoming(const struct sockaddr_sccp *sock, + int (*accept_cb)(struct sccp_connection *, void *), void *context) +{ + struct sccp_data_callback *cb; + + if (!sock) + return -2; + + cb = _find_ssn(sock->sccp_ssn); + if (!cb) + return -1; + + cb->accept_cb = accept_cb; + cb->accept_context = context; + return 0; +} + +int sccp_write(struct msgb *data, const struct sockaddr_sccp *in, + const struct sockaddr_sccp *out, int class, void *ctx) +{ + return _sccp_send_data(class, in, out, data, ctx); +} + +int sccp_set_read(const struct sockaddr_sccp *sock, + int (*read_cb)(struct msgb *, unsigned int, void *), void *context) +{ + struct sccp_data_callback *cb; + + if (!sock) + return -2; + + cb = _find_ssn(sock->sccp_ssn); + if (!cb) + return -1; + + cb->read_cb = read_cb; + cb->read_context = context; + return 0; +} + +osmo_static_assert(sizeof(struct sccp_source_reference) <= sizeof(uint32_t), enough_space); + +uint32_t sccp_src_ref_to_int(struct sccp_source_reference *ref) +{ + uint32_t src_ref = 0; +#if OSMO_IS_LITTLE_ENDIAN + memcpy(&src_ref, ref, sizeof(*ref)); +#elif OSMO_IS_BIG_ENDIAN + *(((uint8_t*)(&src_ref))+3) = ref->octet1; + *(((uint8_t*)(&src_ref))+2) = ref->octet2; + *(((uint8_t*)(&src_ref))+1) = ref->octet3; +#endif + return src_ref; +} + +struct sccp_source_reference sccp_src_ref_from_int(uint32_t int_ref) +{ + struct sccp_source_reference ref; +#if OSMO_IS_LITTLE_ENDIAN + memcpy(&ref, &int_ref, sizeof(ref)); +#elif OSMO_IS_BIG_ENDIAN + ref.octet1 = *(((uint8_t*)(&int_ref))+3); + ref.octet2 = *(((uint8_t*)(&int_ref))+2); + ref.octet3 = *(((uint8_t*)(&int_ref))+1); +#endif + return ref; +} + +int sccp_determine_msg_type(struct msgb *msg) +{ + if (msgb_l2len(msg) < 1) + return -1; + + return msg->l2h[0]; +} + +int sccp_parse_header(struct msgb *msg, struct sccp_parse_result *result) +{ + int type; + + if (msgb_l2len(msg) < 1) + return -1; + + type = msg->l2h[0]; + switch(type) { + case SCCP_MSG_TYPE_CR: + return _sccp_parse_connection_request(msg, result); + break; + case SCCP_MSG_TYPE_RLSD: + return _sccp_parse_connection_released(msg, result); + break; + case SCCP_MSG_TYPE_CREF: + return _sccp_parse_connection_refused(msg, result); + break; + case SCCP_MSG_TYPE_CC: + return _sccp_parse_connection_confirm(msg, result); + break; + case SCCP_MSG_TYPE_RLC: + return _sccp_parse_connection_release_complete(msg, result); + break; + case SCCP_MSG_TYPE_DT1: + return _sccp_parse_connection_dt1(msg, result); + break; + case SCCP_MSG_TYPE_UDT: + return _sccp_parse_udt(msg, result); + break; + case SCCP_MSG_TYPE_IT: + return _sccp_parse_it(msg, result); + break; + case SCCP_MSG_TYPE_ERR: + return _sccp_parse_err(msg, result); + break; + }; + + LOGP(DSCCP, LOGL_ERROR, "Unimplemented MSG Type: 0x%x\n", type); + return -1; +} + +static __attribute__((constructor)) void on_dso_load(void) +{ + tall_sccp_ctx = talloc_named_const(NULL, 1, "sccp"); +} + +static __attribute__((destructor)) void on_dso_unload(void) +{ + talloc_report_full(tall_sccp_ctx, stderr); +} + +void sccp_set_log_area(int log_area) +{ + DSCCP = log_area; +} diff --git a/src/sccp2sua.c b/src/sccp2sua.c new file mode 100644 index 0000000..d97906e --- /dev/null +++ b/src/sccp2sua.c @@ -0,0 +1,1421 @@ +/* SCCP <-> SUA transcoding routines */ + +/* (C) 2017 by Harald Welte + * All Rights Reserved + * + * based on my 2011 Erlang implementation osmo_ss7/src/sua_sccp_conv.erl + * + * References: ITU-T Q.713 and IETF RFC 3868 + * + * 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, see . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "xua_internal.h" +#include "sccp_internal.h" + +/* libosmocore candidates */ + +static void msgb_put_u24be(struct msgb *msg, uint32_t val) +{ + msgb_put_u8(msg, (val >> 16) & 0xff); + msgb_put_u8(msg, (val >> 8) & 0xff); + msgb_put_u8(msg, val & 0xff); +} + +static void msgb_put_u16le(struct msgb *msg, uint16_t val) +{ + msgb_put_u8(msg, val & 0xff); + msgb_put_u8(msg, (val >> 8) & 0xff); +} + +/*! \brief load a 24bit value as big-endian */ +static uint32_t load_24be(const void *ptr) +{ + const uint8_t *data = ptr; + return (data[0] << 16) | (data[1] << 8) | data[2]; +} + + + +/*! \brief Parse ISUP style address of BCD digets + * \param[out] out_digits user-allocated buffer for ASCII digits + * \param[in] in BCD-encoded digits + * \param[in] in_num_bytes Size of \ref in in bytes + * \param[in] odd Odd (true) or even (false) number of digits + * \returns number of digits generated + * */ +int osmo_isup_party_parse(char *out_digits, const uint8_t *in, + unsigned int in_num_bytes, bool odd) +{ + char *out = out_digits; + unsigned int i; + + for (i = 0; i < in_num_bytes; i++) { + *out_digits++ = osmo_bcd2char(in[i] & 0x0F); + if (i+1 == in_num_bytes && odd) + break; + *out_digits++ = osmo_bcd2char(in[i] >> 4); + } + *out_digits = '\0'; + return (out_digits - out); +} + +/*! \brief Encode an ISUP style address of BCD digits + * \param[out] msg Message to which the encoded address is appended + * \param[in] in_digits NUL-terminated ASCII string of digits + * \returns number of octets used for encoding \ref in_digits */ +int osmo_isup_party_encode(struct msgb *msg, const char *in_digits) +{ + unsigned int num_digits = strlen(in_digits); + unsigned int i, num_octets = num_digits/2; + const char *cur_digit = in_digits; + uint8_t *cur; + + if (num_digits & 1) + num_octets++; + + cur = msgb_put(msg, num_octets); + + for (i = 0; i < num_octets; i++) { + cur[i] = osmo_char2bcd(*cur_digit++); + if (cur_digit - in_digits < num_digits) + cur[i] |= osmo_char2bcd(*cur_digit++) << 4; + } + return num_octets; +} + +/*! \brief Parse wire-encoded SCCP address into omso_sccp_addr + * \param[out] out user-allocated output data structure + * \param[in] addr wire-encoded SCCP address + * \param[in] addrlen Size of \ref addr in bytes + * \returns 0 in case of success, negative on error + * According to Q.713/3.4 and RFC3868/3.10.2 */ +int osmo_sccp_addr_parse(struct osmo_sccp_addr *out, + const uint8_t *addr, unsigned int addrlen) +{ + struct sccp_called_party_address *sca; + uint8_t *cur; + uint8_t encoding; + bool odd; + int rc; + + memset(out, 0, sizeof(*out)); + + sca = (struct sccp_called_party_address *) addr; + cur = sca->data; + + if (sca->routing_indicator) + out->ri = OSMO_SCCP_RI_SSN_PC; + else + out->ri = OSMO_SCCP_RI_GT; + + if (sca->point_code_indicator) { + out->presence |= OSMO_SCCP_ADDR_T_PC; + out->pc = (uint16_t) (cur[1] & 0x3f) << 8; + out->pc |= cur[0]; + cur += 2; + } + + if (sca->ssn_indicator) { + out->presence |= OSMO_SCCP_ADDR_T_SSN; + out->ssn = *cur; + cur += 1; + } + + switch (sca->global_title_indicator) { + case SCCP_TITLE_IND_NONE: + out->gt.gti = OSMO_SCCP_GTI_NO_GT; + return 0; + case SCCP_TITLE_IND_NATURE_ONLY: + out->presence |= OSMO_SCCP_ADDR_T_GT; + out->gt.gti = OSMO_SCCP_GTI_NAI_ONLY; + out->gt.nai = *cur & 0x7f; + if (*cur++ & 0x80) + odd = true; + else + odd = false; + break; + case SCCP_TITLE_IND_TRANSLATION_ONLY: + out->presence |= OSMO_SCCP_ADDR_T_GT; + out->gt.gti = OSMO_SCCP_GTI_TT_ONLY; + out->gt.tt = *cur++; + /* abort, for national use only */ + LOGP(DLSUA, LOGL_ERROR, "Unsupported national GTI %u\n", sca->global_title_indicator); + return -EINVAL; + case SCCP_TITLE_IND_TRANS_NUM_ENC: + out->presence |= OSMO_SCCP_ADDR_T_GT; + out->gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC; + out->gt.tt = *cur++; + out->gt.npi = *cur >> 4; + encoding = *cur++ & 0xF; + switch (encoding) { + case 1: + odd = true; + break; + case 2: + odd = false; + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unknown GT encoding 0x%x\n", encoding); + return -1; + } + break; + case SCCP_TITLE_IND_TRANS_NUM_ENC_NATURE: + out->presence |= OSMO_SCCP_ADDR_T_GT; + out->gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC_NAI; + out->gt.tt = *cur++; + out->gt.npi = *cur >> 4; + encoding = *cur++ & 0xF; + switch (encoding) { + case 1: + odd = true; + break; + case 2: + odd = false; + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unknown GT encoding 0x%x\n", + encoding); + return -EINVAL; + } + out->gt.nai = *cur++ & 0x7f; + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unknown GTI %u in SCCP message\n", + sca->global_title_indicator); + return -EINVAL; + } + rc = osmo_isup_party_parse(out->gt.digits, cur, (addr+addrlen-cur), odd); + if (rc < 0) + return rc; + + return 0; +} + +/*! \brief encode a SCCP address from parsed format to wire format + * \param[out] msg message buffer to which address is to be appended + * \param[in] in data structure describing SCCP address + * \returns number of bytes written to \ref msg */ +int osmo_sccp_addr_encode(struct msgb *msg, const struct osmo_sccp_addr *in) +{ + struct sccp_called_party_address *sca; + bool odd; + + sca = (struct sccp_called_party_address *) msgb_put(msg, sizeof(*sca)); + switch (in->ri) { + case OSMO_SCCP_RI_SSN_PC: + sca->routing_indicator = 1; + break; + case OSMO_SCCP_RI_GT: + sca->routing_indicator = 0; + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unknown CCP Routing Indicator %u" + " requested\n", in->ri); + return -EINVAL; + } + + if (in->presence & OSMO_SCCP_ADDR_T_PC) { + sca->point_code_indicator = 1; + /* ITU-T Q.713 states that signalling point codes are 14bit */ + if (in->pc > 0x3fff) { + LOGP(DLSUA, LOGL_ERROR, "Invalid Point Code %u requested\n", in->pc); + return -EINVAL; + } + msgb_put_u16le(msg, in->pc & 0x3fff); + } + + if (in->presence & OSMO_SCCP_ADDR_T_SSN) { + sca->ssn_indicator = 1; + if (in->ssn > 0xff) { + LOGP(DLSUA, LOGL_ERROR, "Invalid SSN %u requested\n", in->ssn); + return -EINVAL; + } + msgb_put_u8(msg, in->ssn); + } + + if (!(in->presence & OSMO_SCCP_ADDR_T_GT)) { + sca->global_title_indicator = SCCP_TITLE_IND_NONE; + goto out; + } + + if (in->gt.npi && (in->gt.npi > 0xF)) { + LOGP(DLSUA, LOGL_ERROR, "Unsupported Numbering Plan %u", in->gt.npi); + return -EINVAL; + } + + if (in->gt.nai && (in->gt.nai > 0x7F)) { + LOGP(DLSUA, LOGL_ERROR, "Unsupported Nature of Address %u", in->gt.nai); + return -EINVAL; + } + + odd = strlen(in->gt.digits) & 1; + switch (in->gt.gti) { + case OSMO_SCCP_GTI_NO_GT: + sca->global_title_indicator = SCCP_TITLE_IND_NONE; + goto out; + case OSMO_SCCP_GTI_NAI_ONLY: + sca->global_title_indicator = SCCP_TITLE_IND_NATURE_ONLY; + msgb_put_u8(msg, (odd << 7) | (in->gt.nai & 0x7f)); + break; + case OSMO_SCCP_GTI_TT_ONLY: + sca->global_title_indicator = SCCP_TITLE_IND_TRANSLATION_ONLY; + msgb_put_u8(msg, in->gt.tt); + /* abort, for national use only */ + LOGP(DLSUA, LOGL_ERROR, "Unsupported Translation Type %u" + "requested\n", in->gt.gti); + return -EINVAL; + case OSMO_SCCP_GTI_TT_NPL_ENC: + sca->global_title_indicator = SCCP_TITLE_IND_TRANS_NUM_ENC; + msgb_put_u8(msg, in->gt.tt); + msgb_put_u8(msg, (in->gt.npi << 4) | (odd ? 1 : 2)); + break; + case OSMO_SCCP_GTI_TT_NPL_ENC_NAI: + sca->global_title_indicator = SCCP_TITLE_IND_TRANS_NUM_ENC_NATURE; + msgb_put_u8(msg, in->gt.tt); + msgb_put_u8(msg, (in->gt.npi << 4) | (odd ? 1 : 2)); + msgb_put_u8(msg, in->gt.nai & 0x7f); + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unsupported GTI %u requested\n", in->gt.gti); + return -EINVAL; + } + osmo_isup_party_encode(msg, in->gt.digits); + +out: + /* return number of bytes written */ + return msg->tail - (uint8_t *)sca; +} + +/*! \brief convert SCCP address to SUA address + * \param xua user-provided xUA message to which address shall be added + * \param[in] iei SUA Information Element Identifier for address + * \param[in] addr SCCP wire format binary address + * \param[in] addrlen Size of \ref addr in bytes + * \returns 0 in case of success; negative on error */ +static int sccp_addr_to_sua(struct xua_msg *xua, uint16_t iei, const uint8_t *addr, + unsigned int addrlen) +{ + struct osmo_sccp_addr osa; + int rc; + + /* First decode the address from SCCP wire format to + * osmo_sccp_addr */ + rc = osmo_sccp_addr_parse(&osa, addr, addrlen); + if (rc < 0) + return rc; + + LOGP(DLSUA, LOGL_DEBUG, "Parsed Addr: %s\n", osmo_sccp_addr_dump(&osa)); + + /* Then re-encode it as SUA address */ + return xua_msg_add_sccp_addr(xua, iei, &osa); +} + +/*! \brief convenience wrapper around sccp_addr_to_sua() for variable mandatory addresses */ +static int sccp_addr_to_sua_ptr(struct xua_msg *xua, uint16_t iei, struct msgb *msg, uint8_t *ptr_addr) +{ + uint8_t *addr = ptr_addr + *ptr_addr + 1; + unsigned int addrlen = *(ptr_addr + *ptr_addr); + + return sccp_addr_to_sua(xua, iei, addr, addrlen); +} + +/*! \brief convert SUA address to SCCP address + * \param msg user-provided message buffer to which address shall be * appended + * \param[in] part SUA wire format binary address + * \returns 0 in case of success; negative on error */ +static int sua_addr_to_sccp(struct msgb *msg, struct xua_msg_part *part) +{ + struct osmo_sccp_addr osa; + int rc; + + /* First decode the address from SUA wire format to + * osmo_sccp_addr */ + rc = sua_addr_parse_part(&osa, part); + if (rc < 0) + return rc; + + /* Then re-encode it as SCCP address */ + return osmo_sccp_addr_encode(msg, &osa); +} + +/*! \brief Add a "SCCP Variable Mandatory Part" (Address format) to the given msgb + * \param msg Message buffer to which part shall be added + * \param[out] var_ptr pointer to relative pointer in SCCP header + * \param[in] xua xUA message from which to use address + * \param[in] iei xUA information element identifier of address */ +static int sccp_add_var_addr(struct msgb *msg, uint8_t *var_ptr, struct xua_msg *xua, uint16_t iei) +{ + struct xua_msg_part *part = xua_msg_find_tag(xua, iei); + uint8_t *lenbyte; + int rc; + if (!part) { + LOGP(DLSUA, LOGL_ERROR, "Cannot find IEI %u in SUA message\n", iei); + return -ENODEV; + } + + /* first allocate one byte for the length */ + lenbyte = msgb_put(msg, 1); + /* update the relative pointer to the length byte */ + *var_ptr = lenbyte - var_ptr; + + /* then append the encoded SCCP address */ + rc = sua_addr_to_sccp(msg, part); + if (rc < 0) + return rc; + + /* store the encoded length of the address */ + *lenbyte = rc; + + return rc; +} + +/*! \brief Add a "SCCP Variable Mandatory Part" to the given msgb + * \param msg Message buffer to which part shall be added + * \param[out] var_ptr pointer to relative pointer in SCCP header + * \param[in] xua xUA message from which to use source data + * \param[in] iei xUA information element identifier of source data */ +static int sccp_add_variable_part(struct msgb *msg, uint8_t *var_ptr, struct xua_msg *xua, uint16_t iei) +{ + struct xua_msg_part *part = xua_msg_find_tag(xua, iei); + uint8_t *lenbyte; + uint8_t *cur; + if (!part) { + LOGP(DLSUA, LOGL_ERROR, "Cannot find IEI %u in SUA message\n", iei); + return -ENODEV; + } + + /* first allocate one byte for the length */ + lenbyte = msgb_put(msg, 1); + /* update the relative pointer to the length byte */ + *var_ptr = lenbyte - var_ptr; + + /* then append the encoded SCCP address */ + cur = msgb_put(msg, part->len); + memcpy(cur, part->dat, part->len); + + /* store the encoded length of the address */ + *lenbyte = part->len; + + return part->len; +} + + +/*! \brief validate that SCCP part with pointer + length doesn't exceed msg tail + * \param[in] msg Message containing SCCP address + * \param[in] ptr_addr pointer to byte with relative SCCP pointer + * \returns true if OK; false if message inconsistent */ +static bool sccp_ptr_part_consistent(struct msgb *msg, uint8_t *ptr_addr) +{ + uint8_t *ptr; + + /* check the address of the relative pointer is within msg */ + if (ptr_addr < msg->data || ptr_addr > msg->tail) { + LOGP(DLSUA, LOGL_ERROR, "ptr_addr outside msg boundary\n"); + return false; + } + + ptr = ptr_addr + *ptr_addr; + if (ptr > msg->tail) { + LOGP(DLSUA, LOGL_ERROR, "ptr points outside msg boundary\n"); + return false; + } + + /* at destination of relative pointer is the length */ + if (ptr + 1 + *ptr > msg->tail) { + LOGP(DLSUA, LOGL_ERROR, "ptr + len points outside msg boundary\n"); + return false; + } + return true; +} + +/*! \brief convenience wrapper around xua_msg_add_data() for variable mandatory data */ +static int sccp_data_to_sua_ptr(struct xua_msg *xua, uint16_t iei, struct msgb *msg, uint8_t *ptr_addr) +{ + uint8_t *addr = ptr_addr + *ptr_addr + 1; + unsigned int addrlen = *(ptr_addr + *ptr_addr); + + return xua_msg_add_data(xua, iei, addrlen, addr); +} + +/*! \brief Convert a given SCCP option to SUA and add it to given xua_msg + * \param xua caller-provided xUA message to which option is to be added + * \param[in] sccp_opt_type SCCP option type (PNC) + * \param[in] opt_len size of \ref opt in bytes + * \param[in] opt pointer to wire-format encoded SCCP option data + * \returns 0 in case of success; negative on error */ +static int xua_msg_add_sccp_opt(struct xua_msg *xua, uint8_t sccp_opt_type, + uint16_t opt_len, uint8_t *opt) +{ + switch (sccp_opt_type) { + case SCCP_PNC_DESTINATION_LOCAL_REFERENCE: + if (opt_len != 3) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(opt)); + break; + case SCCP_PNC_SOURCE_LOCAL_REFERENCE: + if (opt_len != 3) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(opt)); + break; + case SCCP_PNC_CALLED_PARTY_ADDRESS: + if (opt_len < 3) + return -EINVAL; + sccp_addr_to_sua(xua, SUA_IEI_DEST_ADDR, opt, opt_len); + break; + case SCCP_PNC_CALLING_PARTY_ADDRESS: + if (opt_len < 3) + return -EINVAL; + sccp_addr_to_sua(xua, SUA_IEI_SRC_ADDR, opt, opt_len); + break; + case SCCP_PNC_PROTOCOL_CLASS: + if (opt_len < 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, *opt); + break; + case SCCP_PNC_CREDIT: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_IMPORTANCE, *opt & 0x7); + break; + case SCCP_PNC_RELEASE_CAUSE: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | *opt); + break; + case SCCP_PNC_RETURN_CAUSE: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RETURN | *opt); + break; + case SCCP_PNC_RESET_CAUSE: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RESET | *opt); + break; + case SCCP_PNC_ERROR_CAUSE: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_ERROR | *opt); + break; + case SCCP_PNC_REFUSAL_CAUSE: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | *opt); + break; + case SCCP_PNC_DATA: + xua_msg_add_data(xua, SUA_IEI_DATA, opt_len, opt); + break; + case SCCP_PNC_HOP_COUNTER: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_S7_HOP_CTR, *opt); + break; + case SCCP_PNC_IMPORTANCE: + if (opt_len != 1) + return -EINVAL; + xua_msg_add_u32(xua, SUA_IEI_IMPORTANCE, *opt & 0x7); + break; + case SCCP_PNC_LONG_DATA: + xua_msg_add_data(xua, SUA_IEI_DATA, opt_len, opt); + break; + case SCCP_PNC_SEGMENTATION: + case SCCP_PNC_SEGMENTING: + case SCCP_PNC_RECEIVE_SEQ_NUMBER: + /* only in class 3 */ + case SCCP_PNC_SEQUENCING: + /* only in class 3 */ + default: + LOGP(DLSUA, LOGL_ERROR, "Unsupported SCCP option type %u\n", + sccp_opt_type); + return -1; + } + return 0; +} + +/*! \brief append a SCCP option header to the given message + * \param msg Message to which header is to be appended + * \param[in] pnc PNC of the option header + * \param[in] len length of the option, excluding the header */ +static void msgb_put_sccp_opt_hdr(struct msgb *msg, uint8_t pnc, uint8_t len) +{ + msgb_put_u8(msg, pnc); + msgb_put_u8(msg, len); +} + +/*! \brief append a SCCP option to the given message + * \param msg Message to which option is to be appended + * \param[in] pnc PNC of the option header + * \param[in] len length of the option, excluding the header + * \param[in] data actual data to be appended */ +static void msgb_put_sccp_opt(struct msgb *msg, uint8_t pnc, uint8_t len, const uint8_t *data) +{ + uint8_t *cur; + + msgb_put_sccp_opt_hdr(msg, pnc, len); + cur = msgb_put(msg, len); + memcpy(cur, data, len); +} + +/*! \brief Convert a given SUA option/IE to SCCP and add it to given * msgb + * \param msg caller-provided message buffer to which option is to be appended + * \param[in] opt xUA option/IE (messge part) to be converted+added + * \returns 0 in case of success; negative on error */ +static int sccp_msg_add_sua_opt(enum sccp_message_types type, struct msgb *msg, struct xua_msg_part *opt) +{ + uint32_t tmp32; + uint8_t pnc, *lenptr; + int rc; + + switch (opt->tag) { + case SUA_IEI_DEST_REF: + msgb_put_sccp_opt_hdr(msg, SCCP_PNC_DESTINATION_LOCAL_REFERENCE, 3); + msgb_put_u24be(msg, xua_msg_part_get_u32(opt)); + break; + case SUA_IEI_SRC_REF: + msgb_put_sccp_opt_hdr(msg, SCCP_PNC_SOURCE_LOCAL_REFERENCE, 3); + msgb_put_u24be(msg, xua_msg_part_get_u32(opt)); + break; + case SUA_IEI_DEST_ADDR: + switch (type) { + case SCCP_MSG_TYPE_CC: + case SCCP_MSG_TYPE_CREF: + /* The Destination of a CC message is the + * originator of the connection: Calling Party */ + msgb_put_u8(msg, SCCP_PNC_CALLING_PARTY_ADDRESS); + break; + default: + msgb_put_u8(msg, SCCP_PNC_CALLED_PARTY_ADDRESS); + break; + } + lenptr = msgb_put(msg, 1); + rc = sua_addr_to_sccp(msg, opt); + if (rc < 0) + return rc; + *lenptr = rc; + break; + case SUA_IEI_SRC_ADDR: + switch (type) { + case SCCP_MSG_TYPE_CC: + case SCCP_MSG_TYPE_CREF: + /* The Source of a CC message is the + * responder of the connection: Called Party */ + msgb_put_u8(msg, SCCP_PNC_CALLED_PARTY_ADDRESS); + break; + default: + msgb_put_u8(msg, SCCP_PNC_CALLING_PARTY_ADDRESS); + break; + } + lenptr = msgb_put(msg, 1); + rc = sua_addr_to_sccp(msg, opt); + if (rc < 0) + return rc; + *lenptr = rc; + break; + case SUA_IEI_PROTO_CLASS: + msgb_put_sccp_opt_hdr(msg, SCCP_PNC_PROTOCOL_CLASS, 1); + msgb_put_u8(msg, xua_msg_part_get_u32(opt)); + break; + case SUA_IEI_CREDIT: + msgb_put_sccp_opt_hdr(msg, SCCP_PNC_CREDIT, 1); + msgb_put_u8(msg, xua_msg_part_get_u32(opt) & 0x7); + break; + case SUA_IEI_CAUSE: + tmp32 = xua_msg_part_get_u32(opt); + switch (tmp32 & SUA_CAUSE_T_MASK) { + case SUA_CAUSE_T_RETURN: + pnc = SCCP_PNC_RETURN_CAUSE; + break; + case SUA_CAUSE_T_REFUSAL: + pnc = SCCP_PNC_REFUSAL_CAUSE; + break; + case SUA_CAUSE_T_RELEASE: + pnc = SCCP_PNC_RELEASE_CAUSE; + break; + case SUA_CAUSE_T_RESET: + pnc = SCCP_PNC_RESET_CAUSE; + break; + case SUA_CAUSE_T_ERROR: + pnc = SCCP_PNC_ERROR_CAUSE; + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unknown SUA Cause Class 0x%04x\n", tmp32); + return -EINVAL; + } + msgb_put_sccp_opt_hdr(msg, pnc, 1); + msgb_put_u8(msg, tmp32 & 0xff); + break; + case SUA_IEI_DATA: + msgb_put_sccp_opt(msg, SCCP_PNC_DATA, opt->len, opt->dat); + break; + case SUA_IEI_S7_HOP_CTR: + msgb_put_sccp_opt_hdr(msg, SCCP_PNC_HOP_COUNTER, 1); + msgb_put_u8(msg, xua_msg_part_get_u32(opt)); + break; + case SUA_IEI_IMPORTANCE: + msgb_put_sccp_opt_hdr(msg, SCCP_PNC_IMPORTANCE, 1); + msgb_put_u8(msg, xua_msg_part_get_u32(opt) & 0x7); + break; + case SUA_IEI_ROUTE_CTX: + break; + case SUA_IEI_SEQ_CTRL: + /* TODO */ + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unknown SUA IEI 0x%04x\n", opt->tag); + return -1; + } + return 0; +} + +/*! \brief convert SCCP optional part to list of SUA options + * \param[in] msg Message buffer holding SCCP message + * \param[in] ptr_opt address of relative pointer to optional part + * \param xua caller-provided xUA message to which options are added + * \returns \ref xua in case of success, NULL on error (xua not freed!) */ +static struct xua_msg *sccp_to_xua_opt(struct msgb *msg, uint8_t *ptr_opt, struct xua_msg *xua) +{ + uint8_t *opt_start, *oneopt; + + /* some bounds checking */ + if (ptr_opt < msg->data || ptr_opt > msg->tail) + return NULL; + opt_start = ptr_opt + *ptr_opt; + if (opt_start > msg->tail) + return NULL; + + oneopt = opt_start; + + while (oneopt < msg->tail) { + uint8_t opt_type = oneopt[0]; + + if (opt_type == SCCP_PNC_END_OF_OPTIONAL) + return xua; + + if (opt_type == SCCP_PNC_LONG_DATA) { + uint16_t opt_len16; + /* two byte length field */ + if (oneopt + 2 > msg->tail) + return NULL; + opt_len16 = oneopt[1] << 8 | oneopt[2]; + if (oneopt + 3 + opt_len16 > msg->tail) + return NULL; + xua_msg_add_sccp_opt(xua, opt_type, opt_len16, oneopt+3); + oneopt += 3 + opt_len16; + } else { + uint8_t opt_len; + /* one byte length field */ + if (oneopt + 1 > msg->tail) + return NULL; + + opt_len = oneopt[1]; + if (oneopt + 2 + opt_len > msg->tail) + return NULL; + xua_msg_add_sccp_opt(xua, opt_type, opt_len, oneopt+2); + oneopt += 2 + opt_len; + } + } + return NULL; +} + +#define MAX_IES 6 +#define NUM_SCCP_MSGT (SCCP_MSG_TYPE_LUDTS+1) + +/* This table indicates which information elements are mandatory and not + * optional in SCCP, per message type */ +static const uint16_t sccp_mandatory[NUM_SCCP_MSGT][MAX_IES] = { + /* Table 3/Q.713 */ + [SCCP_MSG_TYPE_CR] = { + SUA_IEI_SRC_REF, SUA_IEI_PROTO_CLASS, SUA_IEI_DEST_ADDR , 0 + }, + /* Table 4/Q.713 */ + [SCCP_MSG_TYPE_CC] = { + SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_PROTO_CLASS, 0 + }, + /* Table 5/Q.713 */ + [SCCP_MSG_TYPE_CREF] = { + SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0 + }, + /* Table 6/Q.713 */ + [SCCP_MSG_TYPE_RLSD] = { + SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_CAUSE, 0 + }, + /* Table 7/Q.713 */ + [SCCP_MSG_TYPE_RLC] = { + SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0 + }, + /* Table 8/Q.713 */ + [SCCP_MSG_TYPE_DT1] = { + SUA_IEI_DEST_REF, SUA_IEI_SEGMENTATION, 0 + }, + /* Table 9/Q.713 */ + [SCCP_MSG_TYPE_DT2] = { + SUA_IEI_DEST_REF, SUA_IEI_SEGMENTATION, 0 + }, + /* Table 10/Q.713 */ + [SCCP_MSG_TYPE_AK] = { + SUA_IEI_DEST_REF, SUA_IEI_RX_SEQ_NR, 0 + }, + /* Table 11/Q.713 */ + [SCCP_MSG_TYPE_UDT] = { + SUA_IEI_PROTO_CLASS, SUA_IEI_DEST_ADDR, + SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 + }, + /* Table 12/Q.713 */ + [SCCP_MSG_TYPE_UDTS] = { + SUA_IEI_CAUSE, SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 + }, + /* Table 13/Q.713 */ + [SCCP_MSG_TYPE_ED] = { + SUA_IEI_DEST_REF, 0 + }, + /* Table 14/Q.713 */ + [SCCP_MSG_TYPE_EA] = { + SUA_IEI_DEST_REF, 0 + }, + /* Table 15/Q.713 */ + [SCCP_MSG_TYPE_RSR] = { + SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_CAUSE, 0 + }, + /* Table 16/Q.713 */ + [SCCP_MSG_TYPE_RSC] = { + SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0 + }, + /* Table 17/Q.713 */ + [SCCP_MSG_TYPE_ERR] = { + SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0 + }, + /* Table 18/Q.713 */ + [SCCP_MSG_TYPE_IT] = { + SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, SUA_IEI_PROTO_CLASS, + SUA_IEI_SEGMENTATION, SUA_IEI_CREDIT, 0 + }, + /* Table 19/Q.713 */ + [SCCP_MSG_TYPE_XUDT] = { + SUA_IEI_PROTO_CLASS, SUA_IEI_S7_HOP_CTR, + SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 + }, + /* Table 20/Q.713 */ + [SCCP_MSG_TYPE_XUDTS] = { + SUA_IEI_CAUSE, SUA_IEI_S7_HOP_CTR, SUA_IEI_DEST_ADDR, + SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 + }, + /* Table 21/Q.713 */ + [SCCP_MSG_TYPE_LUDT] = { + SUA_IEI_PROTO_CLASS, SUA_IEI_S7_HOP_CTR, + SUA_IEI_DEST_ADDR, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 + }, + /* Table 22/Q.713 */ + [SCCP_MSG_TYPE_LUDTS] = { + SUA_IEI_CAUSE, SUA_IEI_S7_HOP_CTR, SUA_IEI_DEST_ADDR, + SUA_IEI_SRC_ADDR, SUA_IEI_DATA, 0 + }, +}; + +/* This table indicates which information elements are optionally + * permitted in the respective SCCP message type */ +static const uint16_t sccp_optional[NUM_SCCP_MSGT][MAX_IES] = { + /* Table 3/Q.713 */ + [SCCP_MSG_TYPE_CR] = { + SUA_IEI_CREDIT, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, + SUA_IEI_S7_HOP_CTR, SUA_IEI_IMPORTANCE, 0 + }, + /* Table 4/Q.713 */ + [SCCP_MSG_TYPE_CC] = { + SUA_IEI_CREDIT, SUA_IEI_SRC_ADDR, SUA_IEI_DATA, + SUA_IEI_IMPORTANCE, 0 + }, + /* Table 5/Q.713 */ + [SCCP_MSG_TYPE_CREF] = { + SUA_IEI_SRC_ADDR, SUA_IEI_DATA, SUA_IEI_IMPORTANCE, 0 + }, + /* Table 6/Q.713 */ + [SCCP_MSG_TYPE_RLSD] = { + SUA_IEI_DATA, SUA_IEI_IMPORTANCE, 0 + }, + /* Table 7/Q.713 */ + [SCCP_MSG_TYPE_RLC] = { + 0 + }, + /* Table 8/Q.713 */ + [SCCP_MSG_TYPE_DT1] = { + 0 + }, + /* Table 9/Q.713 */ + [SCCP_MSG_TYPE_DT2] = { + 0 + }, + /* Table 10/Q.713 */ + [SCCP_MSG_TYPE_AK] = { + 0 + }, + /* Table 11/Q.713 */ + [SCCP_MSG_TYPE_UDT] = { + 0 + }, + /* Table 12/Q.713 */ + [SCCP_MSG_TYPE_UDTS] = { + 0 + }, + /* Table 13/Q.713 */ + [SCCP_MSG_TYPE_ED] = { + 0 + }, + /* Table 14/Q.713 */ + [SCCP_MSG_TYPE_EA] = { + 0 + }, + /* Table 15/Q.713 */ + [SCCP_MSG_TYPE_RSR] = { + 0 + }, + /* Table 16/Q.713 */ + [SCCP_MSG_TYPE_RSC] = { + 0 + }, + /* Table 17/Q.713 */ + [SCCP_MSG_TYPE_ERR] = { + 0 + }, + /* Table 18/Q.713 */ + [SCCP_MSG_TYPE_IT] = { + 0 + }, + /* Table 19/Q.713 */ + [SCCP_MSG_TYPE_XUDT] = { + SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 + }, + /* Table 20/Q.713 */ + [SCCP_MSG_TYPE_XUDTS] = { + SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 + }, + /* Table 21/Q.713 */ + [SCCP_MSG_TYPE_LUDT] = { + SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 + }, + /* Table 22/Q.713 */ + [SCCP_MSG_TYPE_LUDTS] = { + SUA_IEI_SEGMENTATION, SUA_IEI_IMPORTANCE, 0 + }, +}; + + +static bool sccp_is_mandatory(enum sccp_message_types type, const struct xua_msg_part *part) +{ + unsigned int i; + + if (type >= ARRAY_SIZE(sccp_mandatory)) + return false; + + for (i = 0; i < MAX_IES; i++) { + uint16_t val = sccp_mandatory[type][i]; + if (val == 0) { + /* end of list, don't iterate further */ + return false; + } + if (val == part->tag) { + /* found in list, it's mandatory */ + return true; + } + } + /* not mandatory */ + return false; +} + +static bool sccp_option_permitted(enum sccp_message_types type, const struct xua_msg_part *part) +{ + unsigned int i; + + if (type >= ARRAY_SIZE(sccp_optional)) + return false; + + for (i = 0; i < MAX_IES; i++) { + uint16_t val = sccp_optional[type][i]; + if (val == 0) { + /* end of list, don't iterate further */ + return false; + } + if (val == part->tag) { + /* found in list, it's permitted */ + return true; + } + } + /* not permitted */ + return false; +} + +static int xua_ies_to_sccp_opts(struct msgb *msg, uint8_t *ptr_opt, + enum sccp_message_types type, struct xua_msg *xua) +{ + struct xua_msg_part *part; + + /* store relative pointer to start of optional part */ + *ptr_opt = msg->tail - ptr_opt; + + llist_for_each_entry(part, &xua->headers, entry) { + /* make sure we don't add a SCCP option for information + * that is already present in mandatory fixed or + * mandatory variable parts of the header */ + if (!sccp_is_mandatory(type, part) && sccp_option_permitted(type, part)) + sccp_msg_add_sua_opt(type, msg, part); + } + msgb_put_u8(msg, SCCP_PNC_END_OF_OPTIONAL); + + return 0; +} + +/* store a 'local reference' as big-eidian 24bit value at local_ref */ +static void store_local_ref(struct sccp_source_reference *local_ref, struct xua_msg *xua, uint16_t iei) +{ + uint32_t tmp32 = xua_msg_get_u32(xua, iei); + local_ref->octet1 = (tmp32 >> 16) & 0xff; + local_ref->octet2 = (tmp32 >> 8) & 0xff; + local_ref->octet3 = tmp32 & 0xff; +} + +static struct xua_msg *sccp_to_xua_cr(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_request *req = (struct sccp_connection_request *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, req->proto_class); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&req->source_local_reference)); + /* Variable Part */ + if (!sccp_ptr_part_consistent(msg, &req->variable_called)) + return NULL; + sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, msg, &req->variable_called); + /* Optional Part */ + return sccp_to_xua_opt(msg, &req->optional_start, xua); +} + +static int sua_to_sccp_cr(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_request *req; + req = (struct sccp_connection_request *) msgb_put(msg, sizeof(*req)); + + /* Fixed Part */ + req->type = SCCP_MSG_TYPE_CR; + req->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); + store_local_ref(&req->source_local_reference, xua, SUA_IEI_SRC_REF); + /* Variable Part */ + sccp_add_var_addr(msg, &req->variable_called, xua, SUA_IEI_DEST_ADDR); + + /* Optional Part */ + return xua_ies_to_sccp_opts(msg, &req->optional_start, req->type, xua); +} + +static struct xua_msg *sccp_to_xua_cc(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_confirm *cnf = (struct sccp_connection_confirm *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, cnf->proto_class); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&cnf->destination_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&cnf->source_local_reference)); + /* Optional Part */ + return sccp_to_xua_opt(msg, &cnf->optional_start, xua); +} + +static int sua_to_sccp_cc(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_confirm *cnf; + cnf = (struct sccp_connection_confirm *) msgb_put(msg, sizeof(*cnf)); + + /* Fixed Part */ + cnf->type = SCCP_MSG_TYPE_CC; + cnf->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); + store_local_ref(&cnf->destination_local_reference, xua, SUA_IEI_DEST_REF); + store_local_ref(&cnf->source_local_reference, xua, SUA_IEI_SRC_REF); + /* Optional Part */ + return xua_ies_to_sccp_opts(msg, &cnf->optional_start, cnf->type, xua); +} + +static struct xua_msg *sccp_to_xua_cref(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_refused *ref = (struct sccp_connection_refused *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&ref->destination_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | ref->cause); + /* Optional Part */ + return sccp_to_xua_opt(msg, &ref->optional_start, xua); +} + +static int sua_to_sccp_cref(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_refused *ref; + ref = (struct sccp_connection_refused *) msgb_put(msg, sizeof(*ref)); + + /* Fixed Part */ + ref->type = SCCP_MSG_TYPE_CREF; + store_local_ref(&ref->destination_local_reference, xua, SUA_IEI_DEST_REF); + ref->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; + /* Optional Part */ + return xua_ies_to_sccp_opts(msg, &ref->optional_start, ref->type, xua); +} + +static struct xua_msg *sccp_to_xua_rlsd(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_released *rlsd = (struct sccp_connection_released *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&rlsd->destination_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&rlsd->source_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | rlsd->release_cause); + /* Optional Part */ + return sccp_to_xua_opt(msg, &rlsd->optional_start, xua); +} + +static int sua_to_sccp_rlsd(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_released *rlsd; + rlsd =(struct sccp_connection_released *) msgb_put(msg, sizeof(*rlsd)); + + /* Fixed Part */ + rlsd->type = SCCP_MSG_TYPE_RLSD; + store_local_ref(&rlsd->destination_local_reference, xua, SUA_IEI_DEST_REF); + store_local_ref(&rlsd->source_local_reference, xua, SUA_IEI_SRC_REF); + rlsd->release_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; + + /* Optional Part */ + return xua_ies_to_sccp_opts(msg, &rlsd->optional_start, rlsd->type, xua); +} + +static struct xua_msg *sccp_to_xua_rlc(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_release_complete *rlc; + rlc = (struct sccp_connection_release_complete *) msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&rlc->destination_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&rlc->source_local_reference)); + return xua; +} + +static int sua_to_sccp_rlc(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_connection_release_complete *rlc; + rlc = (struct sccp_connection_release_complete *) msgb_put(msg, sizeof(*rlc)); + + /* Fixed Part */ + rlc->type = SCCP_MSG_TYPE_RLC; + store_local_ref(&rlc->destination_local_reference, xua, SUA_IEI_DEST_REF); + store_local_ref(&rlc->source_local_reference, xua, SUA_IEI_SRC_REF); + return 0; +} + +static struct xua_msg *sccp_to_xua_dt1(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_form1 *dt1 = (struct sccp_data_form1 *) msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&dt1->destination_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_SEGMENTATION, dt1->segmenting); + /* Variable Part */ + if (!sccp_ptr_part_consistent(msg, &dt1->variable_start)) + return NULL; + sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, msg, &dt1->variable_start); + return xua; +} + +static int sua_to_sccp_dt1(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_form1 *dt1; + dt1 = (struct sccp_data_form1 *) msgb_put(msg, sizeof(*dt1)); + + /* Fixed Part */ + dt1->type = SCCP_MSG_TYPE_DT1; + store_local_ref(&dt1->destination_local_reference, xua, SUA_IEI_DEST_REF); + dt1->segmenting = xua_msg_get_u32(xua, SUA_IEI_SEGMENTATION); + /* Variable Part */ + sccp_add_variable_part(msg, &dt1->variable_start, xua, SUA_IEI_DATA); + return 0; +} + +static struct xua_msg *sccp_to_xua_udt(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_unitdata *udt = (struct sccp_data_unitdata *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, udt->proto_class); + /* Variable Part */ + if (!sccp_ptr_part_consistent(msg, &udt->variable_called)) + return NULL; + sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, msg, &udt->variable_called); + if (!sccp_ptr_part_consistent(msg, &udt->variable_calling)) + return NULL; + sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, msg, &udt->variable_calling); + if (!sccp_ptr_part_consistent(msg, &udt->variable_data)) + return NULL; + sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, msg, &udt->variable_data); + return xua; + +} + +static int sua_to_sccp_udt(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_unitdata *udt; + udt = (struct sccp_data_unitdata *) msgb_put(msg, sizeof(*udt)); + + /* Fixed Part */ + udt->type = SCCP_MSG_TYPE_UDT; + udt->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); + /* Variable Part */ + sccp_add_var_addr(msg, &udt->variable_called, xua, SUA_IEI_DEST_ADDR); + sccp_add_var_addr(msg, &udt->variable_calling, xua, SUA_IEI_SRC_ADDR); + sccp_add_variable_part(msg, &udt->variable_data, xua, SUA_IEI_DATA); + return 0; +} + +static struct xua_msg *sccp_to_xua_udts(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_unitdata_service *udts; + udts =(struct sccp_data_unitdata_service *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RETURN | udts->return_cause); + /* Variable Part */ + if (!sccp_ptr_part_consistent(msg, &udts->variable_called)) + return NULL; + sccp_addr_to_sua_ptr(xua, SUA_IEI_DEST_ADDR, msg, &udts->variable_called); + if (!sccp_ptr_part_consistent(msg, &udts->variable_calling)) + return NULL; + sccp_addr_to_sua_ptr(xua, SUA_IEI_SRC_ADDR, msg, &udts->variable_calling); + if (!sccp_ptr_part_consistent(msg, &udts->variable_data)) + return NULL; + sccp_data_to_sua_ptr(xua, SUA_IEI_DATA, msg, &udts->variable_data); + return xua; + +} + +static int sua_to_sccp_udts(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_unitdata_service *udts; + udts = (struct sccp_data_unitdata_service *) msgb_put(msg, sizeof(*udts)); + + /* Fixed Part */ + udts->type = SCCP_MSG_TYPE_UDTS; + udts->return_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; + /* Variable Part */ + sccp_add_var_addr(msg, &udts->variable_called, xua, SUA_IEI_DEST_ADDR); + sccp_add_var_addr(msg, &udts->variable_calling, xua, SUA_IEI_SRC_ADDR); + sccp_add_variable_part(msg, &udts->variable_data, xua, SUA_IEI_DATA); + return 0; +} + +static struct xua_msg *sccp_to_xua_it(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_it *it = (struct sccp_data_it *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, it->proto_class); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, load_24be(&it->source_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&it->destination_local_reference)); + if ((it->proto_class & 0xF) == 3) { + //xua_msg_add_u32(xua, SUA_IEI_SEQUENCING, it->sequencing); + xua_msg_add_u32(xua, SUA_IEI_CREDIT, it->credit); + } + return xua; +} + +static int sua_to_sccp_it(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_data_it *it; + it = (struct sccp_data_it *) msgb_put(msg, sizeof(*it)); + + /* Fixed Part */ + it->type = SCCP_MSG_TYPE_IT; + it->proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); + store_local_ref(&it->destination_local_reference, xua, SUA_IEI_DEST_REF); + store_local_ref(&it->source_local_reference, xua, SUA_IEI_SRC_REF); + if ((it->proto_class & 0xF) == 3) { + //it->sequencing + it->credit = xua_msg_get_u32(xua, SUA_IEI_CREDIT); + } + + return 0; +} + +static struct xua_msg *sccp_to_xua_err(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_proto_err *err = (struct sccp_proto_err *)msg->l2h; + + /* Fixed Part */ + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, load_24be(&err->destination_local_reference)); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_ERROR | err->error_cause); + return xua; +} + +static int sua_to_sccp_err(struct msgb *msg, struct xua_msg *xua) +{ + struct sccp_proto_err *err; + err = (struct sccp_proto_err *) msgb_put(msg, sizeof(*err)); + + /* Fixed Part */ + err->type = SCCP_MSG_TYPE_ERR; + store_local_ref(&err->destination_local_reference, xua, SUA_IEI_DEST_REF); + err->error_cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE) & 0xff; + return 0; +} + +/*! \brief convert SCCP message to a SUA message + * \param[in] msg message buffer holding SCCP message at l2h + * \returns callee-allocated xUA message on success; NULL on error */ +struct xua_msg *osmo_sccp_to_xua(struct msgb *msg) +{ + struct xua_msg *xua; + + if (msgb_l2len(msg) < 1) { + LOGP(DLSUA, LOGL_ERROR, "Short SCCP Message, cannot transcode\n"); + return NULL; + } + + xua = xua_msg_alloc(); + if (!xua) + return NULL; + + switch (msg->l2h[0]) { + case SCCP_MSG_TYPE_CR: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CORE); + return sccp_to_xua_cr(msg, xua); + case SCCP_MSG_TYPE_CC: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK); + return sccp_to_xua_cc(msg, xua); + case SCCP_MSG_TYPE_CREF: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF); + return sccp_to_xua_cref(msg, xua); + case SCCP_MSG_TYPE_RLSD: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); + return sccp_to_xua_rlsd(msg, xua); + case SCCP_MSG_TYPE_RLC: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO); + return sccp_to_xua_rlc(msg, xua); + case SCCP_MSG_TYPE_DT1: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT); + return sccp_to_xua_dt1(msg, xua); + case SCCP_MSG_TYPE_UDT: + xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT); + return sccp_to_xua_udt(msg, xua); + case SCCP_MSG_TYPE_UDTS: + xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR); + return sccp_to_xua_udts(msg, xua); + case SCCP_MSG_TYPE_IT: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COIT); + return sccp_to_xua_it(msg, xua); + case SCCP_MSG_TYPE_ERR: + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COERR); + return sccp_to_xua_err(msg, xua); + /* Unsupported Message Types */ + case SCCP_MSG_TYPE_DT2: + case SCCP_MSG_TYPE_AK: + case SCCP_MSG_TYPE_ED: + case SCCP_MSG_TYPE_EA: + case SCCP_MSG_TYPE_RSR: + case SCCP_MSG_TYPE_RSC: + case SCCP_MSG_TYPE_XUDT: + case SCCP_MSG_TYPE_XUDTS: + case SCCP_MSG_TYPE_LUDT: + case SCCP_MSG_TYPE_LUDTS: + default: + LOGP(DLSUA, LOGL_ERROR, "Unsupported SCCP message type %u\n", + msg->l2h[0]); + xua_msg_free(xua); + return NULL; + } + + return NULL; +} + +/*! \brief convert parsed SUA message to SCCP message + * \param[in] xua parsed SUA message to be converted + * \returns callee-allocated msgb containing encoded SCCP message */ +struct msgb *osmo_sua_to_sccp(struct xua_msg *xua) +{ + struct msgb *msg = sccp_msgb_alloc("SCCP from SUA"); + int rc; + + switch (xua->hdr.msg_class) { + case SUA_MSGC_CL: + switch (xua->hdr.msg_type) { + case SUA_CL_CLDT: + rc = sua_to_sccp_udt(msg, xua); + break; + case SUA_CL_CLDR: + rc = sua_to_sccp_udts(msg, xua); + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unsupported SUA message %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + goto out_err; + } + break; + case SUA_MSGC_CO: + switch (xua->hdr.msg_type) { + case SUA_CO_CORE: + rc = sua_to_sccp_cr(msg, xua); + break; + case SUA_CO_COAK: + rc = sua_to_sccp_cc(msg, xua); + break; + case SUA_CO_COREF: + rc = sua_to_sccp_cref(msg, xua); + break; + case SUA_CO_RELRE: + rc = sua_to_sccp_rlsd(msg, xua); + break; + case SUA_CO_RELCO: + rc = sua_to_sccp_rlc(msg, xua); + break; + case SUA_CO_CODT: + rc = sua_to_sccp_dt1(msg, xua); + break; + case SUA_CO_COIT: + rc = sua_to_sccp_it(msg, xua); + break; + case SUA_CO_COERR: + rc = sua_to_sccp_err(msg, xua); + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unsupported SUA message %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + goto out_err; + } + break; + default: + LOGP(DLSUA, LOGL_ERROR, "Unsupported SUA message class %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + goto out_err; + } + + if (rc < 0) + goto out_err; + + return msg; + +out_err: + msgb_free(msg); + return NULL; +} diff --git a/src/sccp_helpers.c b/src/sccp_helpers.c new file mode 100644 index 0000000..4b24392 --- /dev/null +++ b/src/sccp_helpers.c @@ -0,0 +1,326 @@ +/* SCCP User SAP helper functions */ + +/* (C) 2015-2017 by Harald Welte + * (C) 2016 by sysmocom s.m.f.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 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, see . + * + */ + +#include +#include + +#include +#include +#include + +#include +#include + +#include "sccp_internal.h" + +static struct msgb *scu_msgb_alloc(const char *name) +{ + return sccp_msgb_alloc("SCU"); +} + +void osmo_sccp_make_addr_pc_ssn(struct osmo_sccp_addr *addr, uint32_t pc, uint32_t ssn) +{ + *addr = (struct osmo_sccp_addr){ + .presence = OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC, + .ri = OSMO_SCCP_RI_SSN_PC, + .ssn = ssn, + .pc = pc, + }; +} + +void osmo_sccp_addr_set_ssn(struct osmo_sccp_addr *addr, uint32_t ssn) +{ + addr->presence |= OSMO_SCCP_ADDR_T_SSN; + addr->ssn = ssn; +} + +int osmo_sccp_tx_unitdata(struct osmo_sccp_user *scu, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + const uint8_t *data, unsigned int len) +{ + struct msgb *msg = scu_msgb_alloc(__func__); + struct osmo_scu_prim *prim; + struct osmo_scu_unitdata_param *param; + + prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim)); + param = &prim->u.unitdata; + memcpy(¶m->calling_addr, calling_addr, sizeof(*calling_addr)); + memcpy(¶m->called_addr, called_addr, sizeof(*called_addr)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST, msg); + + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + + return osmo_sccp_user_sap_down(scu, &prim->oph); +} + +int osmo_sccp_tx_unitdata_ranap(struct osmo_sccp_user *scu, + uint32_t src_point_code, + uint32_t dst_point_code, + const uint8_t *data, unsigned int len) +{ + struct osmo_sccp_addr calling_addr; + struct osmo_sccp_addr called_addr; + osmo_sccp_make_addr_pc_ssn(&calling_addr, src_point_code, + OSMO_SCCP_SSN_RANAP); + osmo_sccp_make_addr_pc_ssn(&called_addr, dst_point_code, + OSMO_SCCP_SSN_RANAP); + return osmo_sccp_tx_unitdata(scu, &calling_addr, &called_addr, + data, len); +} + +int osmo_sccp_tx_unitdata_msg(struct osmo_sccp_user *scu, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + struct msgb *msg) +{ + int rc; + + rc = osmo_sccp_tx_unitdata(scu, calling_addr, called_addr, + msg->data, msgb_length(msg)); + msgb_free(msg); + + return rc; +} + +int osmo_sccp_tx_conn_req(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + const uint8_t *data, unsigned int len) +{ + struct msgb *msg = scu_msgb_alloc(__func__); + struct osmo_scu_prim *prim; + struct osmo_scu_connect_param *param; + + prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + OSMO_SCU_PRIM_N_CONNECT, + PRIM_OP_REQUEST, msg); + param = &prim->u.connect; + if (calling_addr) + memcpy(¶m->calling_addr, calling_addr, sizeof(*calling_addr)); + memcpy(¶m->called_addr, called_addr, sizeof(*called_addr)); + param->sccp_class = 2; + param->conn_id = conn_id; + + if (data && len) { + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + } + + return osmo_sccp_user_sap_down(scu, &prim->oph); +} + +int osmo_sccp_tx_conn_req_msg(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *calling_addr, + const struct osmo_sccp_addr *called_addr, + struct msgb *msg) +{ + int rc; + + rc = osmo_sccp_tx_conn_req(scu, conn_id, calling_addr, called_addr, + msg->data, msgb_length(msg)); + msgb_free(msg); + + return rc; +} + +int osmo_sccp_tx_data(struct osmo_sccp_user *scu, uint32_t conn_id, + const uint8_t *data, unsigned int len) +{ + struct msgb *msg = scu_msgb_alloc(__func__); + struct osmo_scu_prim *prim; + + prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + OSMO_SCU_PRIM_N_DATA, + PRIM_OP_REQUEST, msg); + prim->u.data.conn_id = conn_id; + + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + + return osmo_sccp_user_sap_down(scu, &prim->oph); +} + +int osmo_sccp_tx_data_msg(struct osmo_sccp_user *scu, uint32_t conn_id, + struct msgb *msg) +{ + int rc; + + rc = osmo_sccp_tx_data(scu, conn_id, msg->data, msgb_length(msg)); + msgb_free(msg); + + return rc; +} + +/* N-DISCONNECT.req */ +int osmo_sccp_tx_disconn(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *resp_addr, + uint32_t cause) +{ + struct msgb *msg = scu_msgb_alloc(__func__); + struct osmo_scu_prim *prim; + struct osmo_scu_disconn_param *param; + + prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_REQUEST, msg); + param = &prim->u.disconnect; + memset(param, 0, sizeof(*param)); + param->originator = OSMO_SCCP_ORIG_NS_USER; + if (resp_addr) + memcpy(¶m->responding_addr, resp_addr, sizeof(*resp_addr)); + param->conn_id = conn_id; + param->cause = cause; + + return osmo_sccp_user_sap_down(scu, &prim->oph); +} + +/* N-CONNECT.resp */ +int osmo_sccp_tx_conn_resp_msg(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *resp_addr, + struct msgb *msg) +{ + struct osmo_scu_prim *prim; + struct osmo_scu_connect_param *param; + + msg->l2h = msg->data; + + prim = (struct osmo_scu_prim *) msgb_push(msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + OSMO_SCU_PRIM_N_CONNECT, + PRIM_OP_RESPONSE, msg); + param = &prim->u.connect; + param->conn_id = conn_id; + memcpy(¶m->responding_addr, resp_addr, sizeof(*resp_addr)); + param->sccp_class = 2; + + return osmo_sccp_user_sap_down(scu, &prim->oph); +} + +int osmo_sccp_tx_conn_resp(struct osmo_sccp_user *scu, uint32_t conn_id, + const struct osmo_sccp_addr *resp_addr, + const uint8_t *data, unsigned int len) +{ + struct msgb *msg = scu_msgb_alloc(__func__); + + if (data && len) { + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + } + return osmo_sccp_tx_conn_resp_msg(scu, conn_id, resp_addr, msg); +} + +static void append_to_buf(char *buf, size_t size, bool *comma, const char *fmt, ...) +{ + va_list ap; + size_t printed; + + va_start(ap, fmt); + if (*comma == true) { + strcat(buf, ","); + } else + *comma = true; + printed = strlen(buf); + OSMO_ASSERT(printed <= size); + vsnprintf(buf + printed, size - printed, fmt, ap); + va_end(ap); +} + +char *osmo_sccp_gt_dump(const struct osmo_sccp_gt *gt) +{ + static char buf[256]; + bool comma = false; + + buf[0] = '\0'; + + if (gt->gti == OSMO_SCCP_GTI_NO_GT) { + strcat(buf, "NONE"); + return buf; + } + if (gt->gti == OSMO_SCCP_GTI_NAI_ONLY) { + return buf; + } + if (gt->gti == OSMO_SCCP_GTI_TT_ONLY || + gt->gti == OSMO_SCCP_GTI_TT_NPL_ENC || + gt->gti == OSMO_SCCP_GTI_TT_NPL_ENC_NAI) + append_to_buf(buf, sizeof(buf), &comma, "TT=%u", gt->tt); + + if (gt->gti == OSMO_SCCP_GTI_TT_NPL_ENC || + gt->gti == OSMO_SCCP_GTI_TT_NPL_ENC_NAI) + append_to_buf(buf, sizeof(buf), &comma, "NPL=%u", gt->npi); + + if (gt->gti == OSMO_SCCP_GTI_TT_NPL_ENC_NAI) + append_to_buf(buf, sizeof(buf), &comma, "NAI=%u", gt->nai); + + append_to_buf(buf, sizeof(buf), &comma, "DIG=%s", gt->digits); + + return buf; +} + +/* Return string representation of SCCP address raw bytes in a static string. */ +char *osmo_sccp_addr_dump(const struct osmo_sccp_addr *addr) +{ + static char buf[256]; + bool comma = false; + + buf[0] = '\0'; + + append_to_buf(buf, sizeof(buf), &comma, "RI=%d", addr->ri); + + if (addr->presence & OSMO_SCCP_ADDR_T_PC) + append_to_buf(buf, sizeof(buf), &comma, "PC=%u", addr->pc); + if (addr->presence & OSMO_SCCP_ADDR_T_SSN) + append_to_buf(buf, sizeof(buf), &comma, "SSN=%u", addr->ssn); + if (addr->presence & OSMO_SCCP_ADDR_T_IPv4) + append_to_buf(buf, sizeof(buf), &comma, "IP=%s", inet_ntoa(addr->ip.v4)); + append_to_buf(buf, sizeof(buf), &comma, "GTI=%u", addr->gt.gti); + if (addr->presence & OSMO_SCCP_ADDR_T_GT) + append_to_buf(buf, sizeof(buf), &comma, "GT=(%s)", osmo_sccp_gt_dump(&addr->gt)); + + return buf; +} + +/* Like osmo_sccp_addr_dump() but print human readable representations instead of raw values. */ +char *osmo_sccp_addr_name(const struct osmo_ss7_instance *ss7, const struct osmo_sccp_addr *addr) +{ + static char buf[256]; + bool comma = false; + + buf[0] = '\0'; + + append_to_buf(buf, sizeof(buf), &comma, "RI=%s", osmo_sccp_routing_ind_name(addr->ri)); + + if (addr->presence & OSMO_SCCP_ADDR_T_PC) + append_to_buf(buf, sizeof(buf), &comma, "PC=%s", osmo_ss7_pointcode_print(ss7, addr->pc)); + if (addr->presence & OSMO_SCCP_ADDR_T_SSN) + append_to_buf(buf, sizeof(buf), &comma, "SSN=%s", osmo_sccp_ssn_name(addr->ssn)); + if (addr->presence & OSMO_SCCP_ADDR_T_IPv4) + append_to_buf(buf, sizeof(buf), &comma, "IP=%s", inet_ntoa(addr->ip.v4)); + append_to_buf(buf, sizeof(buf), &comma, "GTI=%s", osmo_sccp_gti_name(addr->gt.gti)); + if (addr->presence & OSMO_SCCP_ADDR_T_GT) + append_to_buf(buf, sizeof(buf), &comma, "GT=(%s)", osmo_sccp_gt_dump(&addr->gt)); + + return buf; +} diff --git a/src/sccp_internal.h b/src/sccp_internal.h new file mode 100644 index 0000000..66d768d --- /dev/null +++ b/src/sccp_internal.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include + +/* an instance of the SCCP stack */ +struct osmo_sccp_instance { + /* entry in global list of ss7 instances */ + struct llist_head list; + /* list of 'struct sccp_connection' in this instance */ + struct llist_head connections; + /* list of SCCP users in this instance */ + struct llist_head users; + /* routing context to be used in all outbound messages */ + uint32_t route_ctx; + /* next local reference to allocate */ + uint32_t next_id; + struct osmo_ss7_instance *ss7; + void *priv; + + struct osmo_ss7_user ss7_user; +}; + +struct osmo_sccp_user { + /*! \brief entry in list of sccp users of \ref osmo_sccp_instance */ + struct llist_head list; + /*! \brief pointer back to SCCP instance */ + struct osmo_sccp_instance *inst; + /*! \brief human-readable name of this user */ + char *name; + + /*! \brief SSN and/or point code to which we are bound */ + uint16_t ssn; + uint32_t pc; + + /* set if we are a server */ + struct llist_head links; + + /* user call-back function in case of incoming primitives */ + osmo_prim_cb prim_cb; + void *priv; + + /* Application Server FSM Instance */ + struct osmo_fsm_inst *as_fi; +}; + +extern int DSCCP; + +struct xua_msg; + +struct osmo_sccp_user * +sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc); + +/* Message from SCOC -> SCRC */ +int sccp_scrc_rx_scoc_conn_msg(struct osmo_sccp_instance *inst, + struct xua_msg *xua); + +/* Message from SCLC -> SCRC */ +int sccp_scrc_rx_sclc_msg(struct osmo_sccp_instance *inst, struct xua_msg *xua); + +/* Message from MTP (SUA) -> SCRC */ +int scrc_rx_mtp_xfer_ind_xua(struct osmo_sccp_instance *inst, + struct xua_msg *xua); + +/* Message from SCRC -> SCOC */ +void sccp_scoc_rx_from_scrc(struct osmo_sccp_instance *inst, + struct xua_msg *xua); +void sccp_scoc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst, + struct xua_msg *xua, uint32_t cause); + +void sccp_scoc_flush_connections(struct osmo_sccp_instance *inst); + +/* Message from SCRC -> SCLC */ +int sccp_sclc_rx_from_scrc(struct osmo_sccp_instance *inst, + struct xua_msg *xua); +void sccp_sclc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst, + struct xua_msg *xua, uint32_t cause); + +int sccp_user_prim_up(struct osmo_sccp_user *scut, struct osmo_scu_prim *prim); + +/* SCU -> SCLC */ +int sccp_sclc_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph); + +struct msgb *sccp_msgb_alloc(const char *name); + +struct osmo_fsm sccp_scoc_fsm; + +void sccp_scoc_show_connections(struct vty *vty, struct osmo_sccp_instance *inst); diff --git a/src/sccp_sap.c b/src/sccp_sap.c new file mode 100644 index 0000000..3646685 --- /dev/null +++ b/src/sccp_sap.c @@ -0,0 +1,135 @@ +/* SCCP User SAP related routines */ + +/* (C) 2017 by Harald Welte + * 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 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, see . + */ + +#include +#include +#include + +#include + +const struct value_string osmo_scu_prim_names[] = { + { OSMO_SCU_PRIM_N_CONNECT, "N-CONNECT" }, + { OSMO_SCU_PRIM_N_DATA, "N-DATA" }, + { OSMO_SCU_PRIM_N_EXPEDITED_DATA, "N-EXPEDITED-DATA" }, + { OSMO_SCU_PRIM_N_DISCONNECT, "N-DISCONNECT" }, + { OSMO_SCU_PRIM_N_RESET, "N-RESET" }, + { OSMO_SCU_PRIM_N_INFORM, "N-INFORM" }, + { OSMO_SCU_PRIM_N_UNITDATA, "N-UNITDATA" }, + { OSMO_SCU_PRIM_N_NOTICE, "N-NOTICE" }, + /* management */ + { OSMO_SCU_PRIM_N_COORD, "N-COORD" }, + { OSMO_SCU_PRIM_N_STATE, "N-STATE" }, + { OSMO_SCU_PRIM_N_PCSTATE, "N-PCSATE" }, + { 0, NULL } +}; + +static char prim_name_buf[128]; + +char *osmo_scu_prim_name(struct osmo_prim_hdr *oph) +{ + const char *name = get_value_string(osmo_scu_prim_names, oph->primitive); + + snprintf(prim_name_buf, sizeof(prim_name_buf), "%s.%s", name, + get_value_string(osmo_prim_op_names, oph->operation)); + + return prim_name_buf; +} + + +#include + +const struct value_string osmo_xlm_prim_names[] = { + { OSMO_XLM_PRIM_M_SCTP_ESTABLISH, "M-SCTP_ESTABLISH" }, + { OSMO_XLM_PRIM_M_SCTP_RELEASE, "M-SCTP_RELEASE" }, + { OSMO_XLM_PRIM_M_SCTP_RESTART, "M-SCTP_RESTART" }, + { OSMO_XLM_PRIM_M_SCTP_STATUS, "M-SCTP_STATUS" }, + { OSMO_XLM_PRIM_M_ASP_STATUS, "M-ASP_STATUS" }, + { OSMO_XLM_PRIM_M_AS_STATUS, "M-AS_STATUS" }, + { OSMO_XLM_PRIM_M_NOTIFY, "M-NOTIFY" }, + { OSMO_XLM_PRIM_M_ERROR, "M-ERROR" }, + { OSMO_XLM_PRIM_M_ASP_UP, "M-ASP_UP" }, + { OSMO_XLM_PRIM_M_ASP_DOWN, "M-ASP_DOWN" }, + { OSMO_XLM_PRIM_M_ASP_ACTIVE, "M-ASP_ACTIVE" }, + { OSMO_XLM_PRIM_M_ASP_INACTIVE, "M-ASP_INACTIVE" }, + { OSMO_XLM_PRIM_M_AS_ACTIVE, "M-AS_ACTIVE" }, + { OSMO_XLM_PRIM_M_AS_INACTIVE, "M-AS_INACTIVE" }, + { OSMO_XLM_PRIM_M_AS_DOWN, "M-AS_DOWN" }, + /* optional as per spec, not implemented yet */ + { OSMO_XLM_PRIM_M_RK_REG, "M-RK_REG" }, + { OSMO_XLM_PRIM_M_RK_DEREG, "M-RK_DEREG" }, + { 0, NULL }, +}; + +char *osmo_xlm_prim_name(struct osmo_prim_hdr *oph) +{ + const char *name = get_value_string(osmo_xlm_prim_names, oph->primitive); + + snprintf(prim_name_buf, sizeof(prim_name_buf), "%s.%s", name, + get_value_string(osmo_prim_op_names, oph->operation)); + + return prim_name_buf; +} + +const struct value_string osmo_sccp_routing_ind_names[] = { + { OSMO_SCCP_RI_NONE, "NONE" }, + { OSMO_SCCP_RI_GT, "GT" }, + { OSMO_SCCP_RI_SSN_PC, "SSN_PC" }, + { OSMO_SCCP_RI_SSN_IP, "SSN_IP" }, + { 0, NULL } +}; + +const struct value_string osmo_sccp_gti_names[] = { + { OSMO_SCCP_GTI_NO_GT, "NO_GT" }, + { OSMO_SCCP_GTI_NAI_ONLY, "NAI_ONLY" }, + { OSMO_SCCP_GTI_TT_ONLY, "TT_ONLY" }, + { OSMO_SCCP_GTI_TT_NPL_ENC, "TT_NPL_ENC" }, + { OSMO_SCCP_GTI_TT_NPL_ENC_NAI, "TT_NPL_ENC_NAI" }, + { 0, NULL } +}; + +const struct value_string osmo_sccp_ssn_names[] = { + { OSMO_SCCP_SSN_MGMT, "MGMT" }, + { OSMO_SCCP_SSN_ISUP, "ISUP" }, + { OSMO_SCCP_SSN_OMAP, "OMAP" }, + { OSMO_SCCP_SSN_MAP, "MAP" }, + { OSMO_SCCP_SSN_HLR, "HLR" }, + { OSMO_SCCP_SSN_VLR, "VLR" }, + { OSMO_SCCP_SSN_MSC, "MSC" }, + { OSMO_SCCP_SSN_EIR, "EIR" }, + { OSMO_SCCP_SSN_AUC, "AUC" }, + { OSMO_SCCP_SSN_ISDN_SS, "ISDN_SS" }, + { OSMO_SCCP_SSN_RES_INTL, "RES_INTL" }, + { OSMO_SCCP_SSN_BISDN, "BISDN" }, + { OSMO_SCCP_SSN_TC_TEST, "TC_TEST" }, + { OSMO_SCCP_SSN_RANAP, "RANAP" }, + { OSMO_SCCP_SSN_RNSAP, "RNSAP" }, + { OSMO_SCCP_SSN_GMLC_MAP, "GMLC_MAP" }, + { OSMO_SCCP_SSN_CAP, "CAP" }, + { OSMO_SCCP_SSN_gsmSCF_MAP, "gsmSCF_MAP" }, + { OSMO_SCCP_SSN_SIWF_MAP, "SIWF_MAP" }, + { OSMO_SCCP_SSN_SGSN_MAP, "SGSN_MAP" }, + { OSMO_SCCP_SSN_GGSN_MAP, "GGSN_MAP" }, + { OSMO_SCCP_SSN_PCAP, "PCAP" }, + { OSMO_SCCP_SSN_BSC_BSSAP_LE, "BSC_BSSAP_LE" }, + { OSMO_SCCP_SSN_MSC_BSSAP_LE, "MSC_BSSAP_LE" }, + { OSMO_SCCP_SSN_SMLC_BSSAP, "SMLC_BSSAP" }, + { OSMO_SCCP_SSN_BSS_OAM, "BSS_OAM" }, + { OSMO_SCCP_SSN_BSSAP, "BSSAP" }, + { 0, NULL } +}; diff --git a/src/sccp_sclc.c b/src/sccp_sclc.c new file mode 100644 index 0000000..8f9e577 --- /dev/null +++ b/src/sccp_sclc.c @@ -0,0 +1,350 @@ +/* SCCP Connectionless Control (SCLC) according to ITU-T Q.713/Q.714 */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +/* This code is a bit of a hybrid between the ITU-T Q.71x specifications + * for SCCP (particularly its connection-oriented part), and the IETF + * RFC 3868 (SUA). The idea here is to have one shared code base of the + * state machines for SCCP Connection Oriented, and use those both from + * SCCP and SUA. + * + * To do so, all SCCP messages are translated to SUA messages in the + * input side, and all generated SUA messages are translated to SCCP on + * the output side. + * + * The Choice of going for SUA messages as the "native" format was based + * on their easier parseability, and the fact that there are features in + * SUA which classic SCCP cannot handle (like IP addresses in GT). + * However, all SCCP features can be expressed in SUA. + * + * The code only supports Class 2. No support for Class 3 is intended, + * but patches are of course alwys welcome. + * + * Missing other features: + * * Segmentation/Reassembly support + * * T(guard) after (re)start + * * freezing of local references + * * parsing/encoding of IPv4/IPv6 addresses + * * use of multiple Routing Contexts in SUA case + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "xua_internal.h" +#include "sccp_internal.h" + +/* generate a 'struct xua_msg' of requested type from primitive data */ +static struct xua_msg *xua_gen_msg_cl(uint32_t event, + struct osmo_scu_prim *prim, int msg_type) +{ + struct xua_msg *xua = xua_msg_alloc(); + struct osmo_scu_unitdata_param *udpar = &prim->u.unitdata; + + if (!xua) + return NULL; + + switch (msg_type) { + case SUA_CL_CLDT: + xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */ + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, 0); + xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &udpar->calling_addr); + xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &udpar->called_addr); + xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, udpar->in_sequence_control); + /* optional: importance, ... correlation id? */ + if (!prim) + goto prim_needed; + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + default: + LOGP(DLSCCP, LOGL_ERROR, "Unknown msg_type %u\n", msg_type); + xua_msg_free(xua); + return NULL; + } + return xua; + +prim_needed: + xua_msg_free(xua); + LOGP(DLSCCP, LOGL_ERROR, "%s must be called with valid 'prim' " + "pointer for msg_type=%u\n", __func__, msg_type); + return NULL; +} + +/* generate xua_msg, encode it and send it to SCRC */ +static int xua_gen_encode_and_send(struct osmo_sccp_user *scu, uint32_t event, + struct osmo_scu_prim *prim, int msg_type) +{ + struct xua_msg *xua; + int rc; + + xua = xua_gen_msg_cl(event, prim, msg_type); + if (!xua) + return -1; + + rc = sccp_scrc_rx_sclc_msg(scu->inst, xua); + xua_msg_free(xua); + return rc; +} + +/*! \brief Main entrance function for primitives from SCCP User + * \param[in] scu SCCP User who is sending the primitive + * \param[on] oph Osmocom primitive header of the primitive + * \returns 0 on success; negtive in case of error */ +int sccp_sclc_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph) +{ + struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph; + struct msgb *msg = prim->oph.msg; + int rc = 0; + + /* we get called from osmo_sccp_user_sap_down() which already + * has debug-logged the primitive */ + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST): + /* Connectionless by-passes this altogether */ + rc = xua_gen_encode_and_send(scu, -1, prim, SUA_CL_CLDT); + goto out; + default: + LOGP(DLSCCP, LOGL_ERROR, "Received unknown SCCP User " + "primitive %s from user\n", + osmo_scu_prim_name(&prim->oph)); + rc = -1; + goto out; + } + +out: + /* the SAP is supposed to consume the primitive/msgb */ + msgb_free(msg); + + return rc; +} + +/* Process an incoming CLDT message (from a remote peer) */ +static int sclc_rx_cldt(struct osmo_sccp_instance *inst, struct xua_msg *xua) +{ + struct osmo_scu_prim *prim; + struct osmo_scu_unitdata_param *param; + struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + struct msgb *upmsg = sccp_msgb_alloc(__func__); + struct osmo_sccp_user *scu; + uint32_t protocol_class; + + if (!data_ie) { + LOGP(DLSCCP, LOGL_ERROR, "SCCP/SUA CLDT without user data?!?\n"); + return -1; + } + + /* fill primitive */ + prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim)); + param = &prim->u.unitdata; + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + OSMO_SCU_PRIM_N_UNITDATA, + PRIM_OP_INDICATION, upmsg); + sua_addr_parse(¶m->called_addr, xua, SUA_IEI_DEST_ADDR); + sua_addr_parse(¶m->calling_addr, xua, SUA_IEI_SRC_ADDR); + param->in_sequence_control = xua_msg_get_u32(xua, SUA_IEI_SEQ_CTRL); + protocol_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); + param->return_option = protocol_class & 0x80; + param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + + scu = sccp_user_find(inst, param->called_addr.ssn, + param->called_addr.pc); + + if (!scu) { + /* FIXME: Send destination unreachable? */ + LOGP(DLSUA, LOGL_NOTICE, "Received SUA message for unequipped SSN %u\n", + param->called_addr.ssn); + msgb_free(upmsg); + return 0; + } + + /* copy data */ + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + + /* send to user SAP */ + sccp_user_prim_up(scu, prim); + + /* xua_msg is free'd by our caller */ + return 0; +} + +static int sclc_rx_cldr(struct osmo_sccp_instance *inst, struct xua_msg *xua) +{ + struct osmo_scu_prim *prim; + struct osmo_scu_notice_param *param; + struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + struct msgb *upmsg = sccp_msgb_alloc(__func__); + struct osmo_sccp_user *scu; + + if (!data_ie) { + LOGP(DLSCCP, LOGL_ERROR, "SCCP/SUA CLDR without user data?!?\n"); + return -1; + } + + /* fill primitive */ + prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim)); + param = &prim->u.notice; + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + OSMO_SCU_PRIM_N_NOTICE, + PRIM_OP_INDICATION, upmsg); + + sua_addr_parse(¶m->called_addr, xua, SUA_IEI_DEST_ADDR); + sua_addr_parse(¶m->calling_addr, xua, SUA_IEI_SRC_ADDR); + param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + param->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE); + + scu = sccp_user_find(inst, param->called_addr.ssn, + param->called_addr.pc); + if (!scu) { + /* FIXME: Send destination unreachable? */ + LOGP(DLSUA, LOGL_NOTICE, "Received CLDR for unequipped SSN %u\n", + param->called_addr.ssn); + msgb_free(upmsg); + return 0; + } + + /* copy data */ + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + + /* send to user SAP */ + sccp_user_prim_up(scu, prim); + + /* xua_msg is free'd by our caller */ + return 0; +} + +/*! \brief SCRC -> SCLC (connectionless message) + * \param[in] inst SCCP Instance in which we operate + * \param[in] xua SUA connectionless message + * \returns 0 on success; negative on error */ +int sccp_sclc_rx_from_scrc(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + int rc = -1; + + OSMO_ASSERT(xua->hdr.msg_class == SUA_MSGC_CL); + + switch (xua->hdr.msg_type) { + case SUA_CL_CLDT: + rc = sclc_rx_cldt(inst, xua); + break; + case SUA_CL_CLDR: + rc = sclc_rx_cldr(inst, xua); + break; + default: + LOGP(DLSUA, LOGL_NOTICE, "Received unknown/unsupported " + "message %s\n", xua_hdr_dump(xua, &xua_dialect_sua)); + break; + } + + return rc; +} + +/* generate a return/refusal message (SUA CLDR == SCCP UDTS) based on + * the incoming message. We need to flip all identities between sender + * and receiver */ +static struct xua_msg *gen_ret_msg(struct osmo_sccp_instance *inst, + const struct xua_msg *xua_in, + uint32_t ret_cause) +{ + struct xua_msg *xua_out = xua_msg_alloc(); + struct osmo_sccp_addr called; + + xua_out->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR); + xua_msg_add_u32(xua_out, SUA_IEI_ROUTE_CTX, inst->route_ctx); + xua_msg_add_u32(xua_out, SUA_IEI_CAUSE, + SUA_CAUSE_T_RETURN | ret_cause); + /* Swap Calling and Called Party */ + xua_msg_copy_part(xua_out, SUA_IEI_SRC_ADDR, xua_in, SUA_IEI_DEST_ADDR); + xua_msg_copy_part(xua_out, SUA_IEI_DEST_ADDR, xua_in, SUA_IEI_SRC_ADDR); + /* TODO: Optional: Hop Count */ + /* Optional: Importance */ + xua_msg_copy_part(xua_out, SUA_IEI_IMPORTANCE, + xua_in, SUA_IEI_IMPORTANCE); + /* Optional: Message Priority */ + xua_msg_copy_part(xua_out, SUA_IEI_MSG_PRIO, xua_in, SUA_IEI_MSG_PRIO); + /* Optional: Correlation ID */ + xua_msg_copy_part(xua_out, SUA_IEI_CORR_ID, xua_in, SUA_IEI_CORR_ID); + /* Optional: Segmentation */ + xua_msg_copy_part(xua_out, SUA_IEI_SEGMENTATION, + xua_in, SUA_IEI_SEGMENTATION); + /* Optional: Data */ + xua_msg_copy_part(xua_out, SUA_IEI_DATA, xua_in, SUA_IEI_DATA); + + sua_addr_parse(&called, xua_out, SUA_IEI_DEST_ADDR); + /* Route on PC + SSN ? */ + if (called.ri == OSMO_SCCP_RI_SSN_PC) { + /* if no PC, copy OPC into called addr */ + if (!(called.presence & OSMO_SCCP_ADDR_T_PC)) { + struct osmo_sccp_addr calling; + sua_addr_parse(&calling, xua_out, SUA_IEI_SRC_ADDR); + called.presence |= OSMO_SCCP_ADDR_T_PC; + called.pc = calling.pc; + /* Re-encode / replace called address */ + xua_msg_free_tag(xua_out, SUA_IEI_DEST_ADDR); + xua_msg_add_sccp_addr(xua_out, SUA_IEI_DEST_ADDR, + &called); + } + } + return xua_out; +} + +/*! \brief SCRC -> SCLC (Routing Failure + * \param[in] inst SCCP Instance in which we operate + * \param[in] xua_in Message that failed to be routed + * \param[in] cause SCCP Return Cause */ +void sccp_sclc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst, + struct xua_msg *xua_in, uint32_t cause) +{ + struct xua_msg *xua_out; + + /* Figure C.12/Q.714 (Sheet 8) Node 9 */ + switch (xua_in->hdr.msg_type) { + case SUA_CL_CLDT: + xua_out = gen_ret_msg(inst, xua_in, cause); + /* TODO: Message Return Option? */ + if (!osmo_ss7_pc_is_local(inst->ss7, xua_in->mtp.opc)) { + /* non-local originator: send UDTS */ + /* TODO: Assign SLS */ + sccp_scrc_rx_sclc_msg(inst, xua_out); + } else { + /* local originator: send N-NOTICE to user */ + /* TODO: N-NOTICE.ind SCLC -> SCU */ + sclc_rx_cldr(inst, xua_out); + } + xua_msg_free(xua_out); + break; + case SUA_CL_CLDR: + /* do nothing */ + break; + } +} diff --git a/src/sccp_scoc.c b/src/sccp_scoc.c new file mode 100644 index 0000000..74fb0e7 --- /dev/null +++ b/src/sccp_scoc.c @@ -0,0 +1,1704 @@ +/* SCCP Connection Oriented (SCOC) according to ITU-T Q.713/Q.714 */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +/* This code is a bit of a hybrid between the ITU-T Q.71x specifications + * for SCCP (particularly its connection-oriented part), and the IETF + * RFC 3868 (SUA). The idea here is to have one shared code base of the + * state machines for SCCP Connection Oriented, and use those both from + * SCCP and SUA. + * + * To do so, all SCCP messages are translated to SUA messages in the + * input side, and all generated SUA messages are translated to SCCP on + * the output side. + * + * The Choice of going for SUA messages as the "native" format was based + * on their easier parseability, and the fact that there are features in + * SUA which classic SCCP cannot handle (like IP addresses in GT). + * However, all SCCP features can be expressed in SUA. + * + * The code only supports Class 2. No support for Class 3 is intended, + * but patches are of course alwys welcome. + * + * Missing other features: + * * Segmentation/Reassembly support + * * T(guard) after (re)start + * * freezing of local references + * * parsing/encoding of IPv4/IPv6 addresses + * * use of multiple Routing Contexts in SUA case + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "xua_internal.h" +#include "sccp_internal.h" + +#define S(x) (1 << (x)) +#define SCU_MSGB_SIZE 1024 + +/* Appendix C.4 of Q.714 (all in milliseconds) */ +#define CONNECTION_TIMER ( 1 * 60 * 100) +#define TX_INACT_TIMER ( 7 * 60 * 100) /* RFC 3868 Ch. 8. */ +#define RX_INACT_TIMER (15 * 60 * 100) /* RFC 3868 Ch. 8. */ +#define RELEASE_TIMER ( 10 * 100) +#define RELEASE_REP_TIMER ( 10 * 100) +#define INT_TIMER ( 1 * 60 * 100) +#define GUARD_TIMER (23 * 60 * 100) +#define RESET_TIMER ( 10 * 100) + +/* convert from single value in milliseconds to comma-separated + * "seconds, microseconds" format we use in osmocom/core/timers.h */ +#define MSEC_TO_S_US(x) (x/100), ((x%100)*10) + +/*********************************************************************** + * SCCP connection table + ***********************************************************************/ + +/* a logical connection within the SCCP instance */ +struct sccp_connection { + /* part of osmo_sccp_instance.list */ + struct llist_head list; + /* which instance are we part of? */ + struct osmo_sccp_instance *inst; + /* which user owns us? */ + struct osmo_sccp_user *user; + + /* remote point code */ + uint32_t remote_pc; + + /* local/remote addresses and identiies */ + struct osmo_sccp_addr calling_addr; + struct osmo_sccp_addr called_addr; + uint32_t conn_id; + uint32_t remote_ref; + + uint32_t importance; + uint32_t sccp_class; + uint32_t release_cause; /* WAIT_CONN_CONF */ + + /* incoming (true) or outgoing (false) */ + bool incoming; + + /* Osmo FSM Instance of sccp_scoc_fsm */ + struct osmo_fsm_inst *fi; + + /* Connect timer */ + struct osmo_timer_list t_conn; + + /* inactivity timers */ + struct osmo_timer_list t_ias; + struct osmo_timer_list t_iar; + + /* release timers */ + struct osmo_timer_list t_rel; + struct osmo_timer_list t_int; + struct osmo_timer_list t_rep_rel; +}; + +/*********************************************************************** + * various helper functions + ***********************************************************************/ + +enum sccp_connection_state { + S_IDLE, + S_CONN_PEND_IN, + S_CONN_PEND_OUT, + S_ACTIVE, + S_DISCONN_PEND, + S_RESET_IN, + S_RESET_OUT, + S_BOTHWAY_RESET, + S_WAIT_CONN_CONF, +}; + +/* Events that this FSM can process */ +enum sccp_scoc_event { + /* Primitives from SCCP-User */ + SCOC_E_SCU_N_CONN_REQ, + SCOC_E_SCU_N_CONN_RESP, + SCOC_E_SCU_N_DISC_REQ, + SCOC_E_SCU_N_DATA_REQ, + SCOC_E_SCU_N_EXP_DATA_REQ, + + /* Events from RCOC (Routing for Connection Oriented) */ + SCOC_E_RCOC_CONN_IND, + SCOC_E_RCOC_ROUT_FAIL_IND, + SCOC_E_RCOC_RLSD_IND, + SCOC_E_RCOC_REL_COMPL_IND, + SCOC_E_RCOC_CREF_IND, + SCOC_E_RCOC_CC_IND, + SCOC_E_RCOC_DT1_IND, + SCOC_E_RCOC_DT2_IND, + SCOC_E_RCOC_IT_IND, + SCOC_E_RCOC_OTHER_NPDU, + SCOC_E_RCOC_ERROR_IND, + + /* Timer Events */ + SCOC_E_T_IAR_EXP, + SCOC_E_T_IAS_EXP, + + SCOC_E_CONN_TMR_EXP, + + SCOC_E_T_REL_EXP, + SCOC_E_T_INT_EXP, + SCOC_E_T_REP_REL_EXP, +}; + +static const struct value_string scoc_event_names[] = { + /* Primitives from SCCP-User */ + { SCOC_E_SCU_N_CONN_REQ, "N-CONNECT.req" }, + { SCOC_E_SCU_N_CONN_RESP, "N-CONNECT.resp" }, + { SCOC_E_SCU_N_DISC_REQ, "N-DISCONNECT.req" }, + { SCOC_E_SCU_N_DATA_REQ, "N-DATA.req" }, + { SCOC_E_SCU_N_EXP_DATA_REQ, "N-EXPEDITED_DATA.req" }, + + /* Events from RCOC (Routing for Connection Oriented) */ + { SCOC_E_RCOC_CONN_IND, "RCOC-CONNECT.ind" }, + { SCOC_E_RCOC_ROUT_FAIL_IND, "RCOC-ROUT_FAIL.ind" }, + { SCOC_E_RCOC_RLSD_IND, "RCOC-RELEASED.ind" }, + { SCOC_E_RCOC_REL_COMPL_IND, "RCOC-RELEASE_COMPLETE.ind" }, + { SCOC_E_RCOC_CREF_IND, "RCOC-CONNECT_REFUSED.ind" }, + { SCOC_E_RCOC_CC_IND, "RCOC-CONNECT_CONFIRM.ind" }, + { SCOC_E_RCOC_DT1_IND, "RCOC-DT1.ind" }, + { SCOC_E_RCOC_DT2_IND, "RCOC-DT2.ind" }, + { SCOC_E_RCOC_IT_IND, "RCOC-IT.ind" }, + { SCOC_E_RCOC_OTHER_NPDU, "RCOC-OTHER_NPDU.ind" }, + { SCOC_E_RCOC_ERROR_IND, "RCOC-ERROR.ind" }, + + { SCOC_E_T_IAR_EXP, "T(iar)_expired" }, + { SCOC_E_T_IAS_EXP, "T(ias)_expired" }, + { SCOC_E_CONN_TMR_EXP, "T(conn)_expired" }, + { SCOC_E_T_REL_EXP, "T(rel)_expired" }, + { SCOC_E_T_INT_EXP, "T(int)_expired" }, + { SCOC_E_T_REP_REL_EXP, "T(rep_rel)_expired" }, + + { 0, NULL } +}; + +/* how to map a SCCP CO message to an event */ +static const struct xua_msg_event_map sua_scoc_event_map[] = { + { SUA_MSGC_CO, SUA_CO_CORE, SCOC_E_RCOC_CONN_IND }, + { SUA_MSGC_CO, SUA_CO_RELRE, SCOC_E_RCOC_RLSD_IND }, + { SUA_MSGC_CO, SUA_CO_RELCO, SCOC_E_RCOC_REL_COMPL_IND }, + { SUA_MSGC_CO, SUA_CO_COREF, SCOC_E_RCOC_CREF_IND }, + { SUA_MSGC_CO, SUA_CO_COAK, SCOC_E_RCOC_CC_IND }, + { SUA_MSGC_CO, SUA_CO_CODT, SCOC_E_RCOC_DT1_IND }, + { SUA_MSGC_CO, SUA_CO_COIT, SCOC_E_RCOC_IT_IND }, + { SUA_MSGC_CO, SUA_CO_COERR, SCOC_E_RCOC_ERROR_IND }, +}; + + +/* map from SCU-primitives to SCOC FSM events */ +static const struct osmo_prim_event_map scu_scoc_event_map[] = { + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, + SCOC_E_SCU_N_CONN_REQ }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_RESPONSE, + SCOC_E_SCU_N_CONN_RESP }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, + SCOC_E_SCU_N_DATA_REQ }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST, + SCOC_E_SCU_N_DISC_REQ }, + { SCCP_SAP_USER, OSMO_SCU_PRIM_N_EXPEDITED_DATA, PRIM_OP_REQUEST, + SCOC_E_SCU_N_EXP_DATA_REQ }, + { 0, 0, 0, OSMO_NO_EVENT } +}; + +/*********************************************************************** + * Timer Handling + ***********************************************************************/ + +/* T(ias) has expired, send a COIT message to the peer */ +static void tx_inact_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_IAS_EXP, NULL); +} + +/* T(iar) has expired, notify the FSM about it */ +static void rx_inact_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_IAR_EXP, NULL); +} + +/* T(rel) has expired, notify the FSM about it */ +static void rel_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_REL_EXP, NULL); +} + +/* T(int) has expired, notify the FSM about it */ +static void int_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_INT_EXP, NULL); +} + +/* T(repeat_rel) has expired, notify the FSM about it */ +static void rep_rel_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_REP_REL_EXP, NULL); +} + +/* T(conn) has expired, notify the FSM about it */ +static void conn_tmr_cb(void *data) +{ + struct sccp_connection *conn = data; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_CONN_TMR_EXP, NULL); +} + +/* Re-start the Tx inactivity timer */ +static void conn_restart_tx_inact_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_ias, MSEC_TO_S_US(TX_INACT_TIMER)); +} + +/* Re-start the Rx inactivity timer */ +static void conn_restart_rx_inact_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_iar, MSEC_TO_S_US(RX_INACT_TIMER)); +} + +/* Re-start both Rx and Tx inactivity timers */ +static void conn_start_inact_timers(struct sccp_connection *conn) +{ + conn_restart_tx_inact_timer(conn); + conn_restart_rx_inact_timer(conn); +} + +/* Stop both Rx and Tx inactivity timers */ +static void conn_stop_inact_timers(struct sccp_connection *conn) +{ + osmo_timer_del(&conn->t_ias); + osmo_timer_del(&conn->t_iar); +} + +/* Start release timer T(rel) */ +static void conn_start_rel_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_rel, MSEC_TO_S_US(RELEASE_TIMER)); +} + +/* Start repeat release timer T(rep_rel) */ +static void conn_start_rep_rel_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_rep_rel, MSEC_TO_S_US(RELEASE_REP_TIMER)); +} + +/* Start interval timer T(int) */ +static void conn_start_int_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_int, MSEC_TO_S_US(INT_TIMER)); +} + +/* Stop all release related timers: T(rel), T(int) and T(rep_rel) */ +static void conn_stop_release_timers(struct sccp_connection *conn) +{ + osmo_timer_del(&conn->t_rel); + osmo_timer_del(&conn->t_int); + osmo_timer_del(&conn->t_rep_rel); +} + +/* Start connect timer T(conn) */ +static void conn_start_connect_timer(struct sccp_connection *conn) +{ + osmo_timer_schedule(&conn->t_conn, MSEC_TO_S_US(CONNECTION_TIMER)); +} + +/* Stop connect timer T(conn) */ +static void conn_stop_connect_timer(struct sccp_connection *conn) +{ + osmo_timer_del(&conn->t_conn); +} + + +/*********************************************************************** + * SUA Instance and Connection handling + ***********************************************************************/ + +static void conn_destroy(struct sccp_connection *conn); + +static struct sccp_connection *conn_find_by_id(struct osmo_sccp_instance *inst, uint32_t id) +{ + struct sccp_connection *conn; + + llist_for_each_entry(conn, &inst->connections, list) { + if (conn->conn_id == id) + return conn; + } + return NULL; +} + +#define INIT_TIMER(x, fn, priv) do { (x)->cb = fn; (x)->data = priv; } while (0) + +/* allocate + init a SCCP Connection with given ID (local reference) */ +static struct sccp_connection *conn_create_id(struct osmo_sccp_instance *inst, + uint32_t conn_id) +{ + struct sccp_connection *conn = talloc_zero(inst, struct sccp_connection); + char name[16]; + + conn->conn_id = conn_id; + conn->inst = inst; + + llist_add_tail(&conn->list, &inst->connections); + + INIT_TIMER(&conn->t_conn, conn_tmr_cb, conn); + INIT_TIMER(&conn->t_ias, tx_inact_tmr_cb, conn); + INIT_TIMER(&conn->t_iar, rx_inact_tmr_cb, conn); + INIT_TIMER(&conn->t_rel, rel_tmr_cb, conn); + INIT_TIMER(&conn->t_int, int_tmr_cb, conn); + INIT_TIMER(&conn->t_rep_rel, rep_rel_tmr_cb, conn); + + /* this might change at runtime, as it is not a constant :/ */ + sccp_scoc_fsm.log_subsys = DLSCCP; + + /* we simply use the local reference as FSM instance name */ + snprintf(name, sizeof(name), "%u", conn->conn_id); + conn->fi = osmo_fsm_inst_alloc(&sccp_scoc_fsm, conn, conn, + LOGL_DEBUG, name); + if (!conn->fi) { + llist_del(&conn->list); + talloc_free(conn); + return NULL; + } + + return conn; +} + +/* Search for next free connection ID (local reference) and allocate conn */ +static struct sccp_connection *conn_create(struct osmo_sccp_instance *inst) +{ + uint32_t conn_id; + + do { + conn_id = inst->next_id++; + } while (conn_find_by_id(inst, conn_id)); + + return conn_create_id(inst, conn_id); +} + +/* destroy a SCCP connection state, releasing all timers, terminating + * FSM and releasing associated memory */ +static void conn_destroy(struct sccp_connection *conn) +{ + conn_stop_connect_timer(conn); + conn_stop_inact_timers(conn); + conn_stop_release_timers(conn); + llist_del(&conn->list); + + osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL); + + talloc_free(conn); +} + +/* allocate a message buffer for an SCCP User Primitive */ +static struct msgb *scu_msgb_alloc(void) +{ + return msgb_alloc(SCU_MSGB_SIZE, "SCCP User Primitive"); +} + +/* generate a RELRE (release request) xua_msg for given conn */ +static struct xua_msg *xua_gen_relre(struct sccp_connection *conn, + uint32_t cause, + struct osmo_scu_prim *prim) +{ + struct xua_msg *xua = xua_msg_alloc(); + + if (!xua) + return NULL; + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | cause); + /* optional: importance */ + if (prim && msgb_l2(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + + return xua; +} + +/* generate xua_msg, encode it and send it to SCRC */ +static int xua_gen_relre_and_send(struct sccp_connection *conn, uint32_t cause, + struct osmo_scu_prim *prim) +{ + struct xua_msg *xua; + + xua = xua_gen_relre(conn, cause, prim); + if (!xua) + return -1; + + /* amend this with point code information; The SUA RELRE + * includes neither called nor calling party address! */ + xua->mtp.dpc = conn->remote_pc; + sccp_scrc_rx_scoc_conn_msg(conn->inst, xua); + xua_msg_free(xua); + return 0; +} + +/* generate a 'struct xua_msg' of requested type from connection + + * primitive data */ +static struct xua_msg *xua_gen_msg_co(struct sccp_connection *conn, uint32_t event, + struct osmo_scu_prim *prim, int msg_type) +{ + struct xua_msg *xua = xua_msg_alloc(); + + if (!xua) + return NULL; + + switch (msg_type) { + case SUA_CO_CORE: /* Connect Request == SCCP CR */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CORE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->called_addr); + xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* TODO */ + /* optional: sequence number (class 3 only) */ + if (conn->calling_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->calling_addr); + /* optional: hop count; importance; priority; credit */ + if (prim && msgb_l2(prim->oph.msg) && msgb_l2len(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_COAK: /* Connect Acknowledge == SCCP CC */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* TODO */ + /* optional: sequence number (class 3 only) */ + if (conn->called_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->called_addr); + /* optional: hop count; importance; priority */ + /* FIXME: destination address will [only] be present in + * case the CORE message conveys the source address + * parameter */ + if (conn->calling_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->calling_addr); + if (prim && msgb_l2(prim->oph.msg) && msgb_l2len(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_RELRE: /* Release Request == SCCP REL */ + if (!prim) + goto prim_needed; + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | prim->u.disconnect.cause); + /* optional: importance */ + if (prim && msgb_l2(prim->oph.msg) && msgb_l2len(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_RELCO: /* Release Confirm == SCCP RLSD */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + /* optional: importance */ + break; + case SUA_CO_CODT: /* Connection Oriented Data Transfer == SCCP DT1 */ + if (!prim) + goto prim_needed; + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + /* Sequence number only in expedited data */ + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + /* optional: priority; correlation id */ + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + case SUA_CO_COIT: /* Connection Oriented Interval Timer == SCCP IT */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COIT); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + /* optional: sequence number; credit (both class 3 only) */ + break; + case SUA_CO_COREF: /* Connect Refuse == SCCP CREF */ + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref); + //xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | prim->u.disconnect.cause); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | SCCP_REFUSAL_UNEQUIPPED_USER); + /* optional: source addr */ + if (conn->called_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->called_addr); + /* conditional: dest addr */ + if (conn->calling_addr.presence) + xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->calling_addr); + /* optional: importance */ + /* optional: data */ + if (prim && msgb_l2(prim->oph.msg) && msgb_l2len(prim->oph.msg)) + xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg), + msgb_l2(prim->oph.msg)); + break; + /* FIXME */ + default: + LOGP(DLSCCP, LOGL_ERROR, "Don't know how to encode msg_type %u\n", msg_type); + xua_msg_free(xua); + return NULL; + } + return xua; + +prim_needed: + xua_msg_free(xua); + LOGP(DLSCCP, LOGL_ERROR, "%s must be called with valid 'prim' " + "pointer for msg_type=%u\n", __func__, msg_type); + return NULL; +} + +/* generate xua_msg, encode it and send it to SCRC */ +static int xua_gen_encode_and_send(struct sccp_connection *conn, uint32_t event, + struct osmo_scu_prim *prim, int msg_type) +{ + struct xua_msg *xua; + + xua = xua_gen_msg_co(conn, event, prim, msg_type); + if (!xua) + return -1; + + /* amend this with point code information; Many CO msgs + * includes neither called nor calling party address! */ + xua->mtp.dpc = conn->remote_pc; + sccp_scrc_rx_scoc_conn_msg(conn->inst, xua); + xua_msg_free(xua); + return 0; +} + +/* allocate a SCU primitive to be sent to the user */ +static struct osmo_scu_prim *scu_prim_alloc(unsigned int primitive, enum osmo_prim_operation operation) +{ + struct msgb *upmsg = scu_msgb_alloc(); + struct osmo_scu_prim *prim; + + prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + primitive, operation, upmsg); + upmsg->l2h = upmsg->tail; + return prim; +} + +/* high-level function to generate a SCCP User primitive of requested + * type based on the connection and currently processed XUA message */ +static void scu_gen_encode_and_send(struct sccp_connection *conn, uint32_t event, + struct xua_msg *xua, unsigned int primitive, + enum osmo_prim_operation operation) +{ + struct osmo_scu_prim *scu_prim; + struct osmo_scu_disconn_param *udisp; + struct osmo_scu_connect_param *uconp; + struct osmo_scu_data_param *udatp; + struct xua_msg_part *data_ie; + + scu_prim = scu_prim_alloc(primitive, operation); + + switch (OSMO_PRIM_HDR(&scu_prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): + udisp = &scu_prim->u.disconnect; + udisp->conn_id = conn->conn_id; + udisp->responding_addr = conn->called_addr; + udisp->originator = OSMO_SCCP_ORIG_UNDEFINED; + //udisp->in_sequence_control; + if (xua) { + udisp->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE); + if (xua_msg_find_tag(xua, SUA_IEI_SRC_ADDR)) + sua_addr_parse(&udisp->responding_addr, xua, SUA_IEI_SRC_ADDR); + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + udisp->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + } + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): + uconp = &scu_prim->u.connect; + uconp->conn_id = conn->conn_id; + uconp->called_addr = conn->called_addr; + uconp->calling_addr = conn->calling_addr; + uconp->sccp_class = conn->sccp_class; + uconp->importance = conn->importance; + if (xua) { + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + } + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): + uconp = &scu_prim->u.connect; + uconp->conn_id = conn->conn_id; + uconp->called_addr = conn->called_addr; + uconp->calling_addr = conn->calling_addr; + //scu_prim->u.connect.in_sequence_control + uconp->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3; + uconp->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): + udatp = &scu_prim->u.data; + udatp->conn_id = conn->conn_id; + udatp->importance = conn->importance; + data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA); + if (data_ie) { + struct msgb *upmsg = scu_prim->oph.msg; + upmsg->l2h = msgb_put(upmsg, data_ie->len); + memcpy(upmsg->l2h, data_ie->dat, data_ie->len); + } + break; + default: + LOGPFSML(conn->fi, LOGL_ERROR, "Unsupported primitive %u:%u\n", + scu_prim->oph.primitive, scu_prim->oph.operation); + talloc_free(scu_prim->oph.msg); + return; + } + + sccp_user_prim_up(conn->user, scu_prim); +} + + +/*********************************************************************** + * Actual SCCP Connection Oriented Control (SCOC) Finite Stte Machine + ***********************************************************************/ + +/* Figure C.2/Q.714 (sheet 1 of 7) and C.3/Q.714 (sheet 1 of 6) */ +static void scoc_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + struct osmo_scu_connect_param *uconp; + struct xua_msg *xua = NULL; + + switch (event) { + case SCOC_E_SCU_N_CONN_REQ: + prim = data; + uconp = &prim->u.connect; + /* copy relevant parameters from prim to conn */ + conn->called_addr = uconp->called_addr; + conn->calling_addr = uconp->calling_addr; + conn->sccp_class = uconp->sccp_class; + /* generate + send CR PDU to SCRC */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_CORE); + /* start connection timer */ + conn_start_connect_timer(conn); + osmo_fsm_inst_state_chg(fi, S_CONN_PEND_OUT, 0, 0); + break; +#if 0 + case SCOC_E_SCU_N_TYPE1_REQ: + /* ?!? */ + break; +#endif + case SCOC_E_RCOC_RLSD_IND: + /* send release complete to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + break; + case SCOC_E_RCOC_REL_COMPL_IND: + /* do nothing */ + break; + case SCOC_E_RCOC_OTHER_NPDU: +#if 0 + if (src_ref) { + /* FIXME: send ERROR to SCRC */ + } +#endif + break; + /* destination node / incoming connection */ + /* Figure C.3 / Q.714 (sheet 1 of 6) */ + case SCOC_E_RCOC_CONN_IND: + xua = data; + /* copy relevant parameters from xua to conn */ + sua_addr_parse(&conn->calling_addr, xua, SUA_IEI_SRC_ADDR); + sua_addr_parse(&conn->called_addr, xua, SUA_IEI_DEST_ADDR); + conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + conn->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3; + conn->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE); + /* 3.1.6.1 The originating node of the CR message + * (identified by the OPC in the calling party address + * or by default by the OPC in the MTP label, [and the + * MTP-SAP instance]) is associated with the incoming + * connection section. */ + if (conn->calling_addr.presence & OSMO_SCCP_ADDR_T_PC) + conn->remote_pc = conn->calling_addr.pc; + else { + /* Hack to get the MTP label here ?!? */ + conn->remote_pc = xua->mtp.opc; + } + + osmo_fsm_inst_state_chg(fi, S_CONN_PEND_IN, 0, 0); + /* N-CONNECT.ind to User */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_CONNECT, + PRIM_OP_INDICATION); + break; + } +} + +static void scoc_fsm_idle_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + conn_destroy(fi->priv); +} + +/* Figure C.3 / Q.714 (sheet 2 of 6) */ +static void scoc_fsm_conn_pend_in(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + + switch (event) { + case SCOC_E_SCU_N_CONN_RESP: + prim = data; + /* FIXME: assign local reference (only now?) */ + /* FIXME: assign sls, protocol class and credit */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_COAK); + /* start inactivity timers */ + conn_start_inact_timers(conn); + osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0); + break; + case SCOC_E_SCU_N_DISC_REQ: + prim = data; + /* release resources: implicit */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_COREF); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + } +} + +/* Figure C.2/Q.714 (sheet 2 of 7) */ +static void scoc_fsm_conn_pend_out(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + struct xua_msg *xua = NULL; + + switch (event) { + case SCOC_E_SCU_N_DISC_REQ: + prim = data; + conn->release_cause = prim->u.disconnect.cause; + osmo_fsm_inst_state_chg(fi, S_WAIT_CONN_CONF, 0, 0); + /* keep conn timer running(!) */ + break; + case SCOC_E_CONN_TMR_EXP: + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, NULL, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* below implicitly releases resources + local ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_ROUT_FAIL_IND: + case SCOC_E_RCOC_CREF_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref (implicit by going to idle) */ + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* below implicitly releases resources + local ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_RLSD_IND: + xua = data; + /* RLC to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref (implicit) */ + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_OTHER_NPDU: + xua = data; + conn_start_connect_timer(conn); + /* release local res + ref (implicit) */ + /* N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_CC_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* start inactivity timers */ + conn_start_inact_timers(conn); + /* TODO: assign PCU and credit */ + /* associate remote ref to conn */ + conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + /* 3.1.4.2 The node sending the CC message (identified + * by the parameter OPC contained in the + * MTP-TRANSFER.indication primitive which conveyed the + * CC message [plus the MTP-SAP instance]) is associated + * with the connection section. */ + conn->remote_pc = xua->mtp.opc; + + osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0); + /* N-CONNECT.conf to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_CONNECT, + PRIM_OP_CONFIRM); + break; + } +} + +/* Figure C.2/Q.714 (sheet 3 of 7) */ +static void scoc_fsm_wait_conn_conf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + struct xua_msg *xua = NULL; + + switch (event) { + case SCOC_E_RCOC_RLSD_IND: + xua = data; + /* release complete to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref (implicit) */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_CC_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* associate rem ref to conn */ + conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + /* released to SCRC */ + xua_gen_relre_and_send(conn, conn->release_cause, NULL); + /* start rel timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + case SCOC_E_RCOC_OTHER_NPDU: + case SCOC_E_RCOC_CREF_IND: + case SCOC_E_RCOC_ROUT_FAIL_IND: + xua = data; + /* stop conn timer */ + conn_stop_connect_timer(conn); + /* release local res + ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_CONN_TMR_EXP: + /* release local res + ref */ + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + } +} + +/* C.2/Q.714 (sheet 4+5 of 7) and C.3/Q714 (sheet 3+4 of 6) */ +static void scoc_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_msg *xua = data; + struct sccp_connection *conn = fi->priv; + struct osmo_scu_prim *prim = NULL; + + switch (event) { +#pragma message ("TODO: internal disco: send N-DISCONNECT.ind to user") + /* send N-DISCONNECT.ind to user */ + /*scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION);*/ + /* fall-through */ + case SCOC_E_SCU_N_DISC_REQ: + prim = data; + /* stop inact timers */ + conn_stop_inact_timers(conn); + /* send RLSD to SCRC */ + xua_gen_encode_and_send(conn, event, prim, SUA_CO_RELRE); + /* start rel timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + case SCOC_E_RCOC_CREF_IND: + case SCOC_E_RCOC_CC_IND: + case SCOC_E_RCOC_REL_COMPL_IND: + /* do nothing */ + break; + case SCOC_E_RCOC_RLSD_IND: + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* release res + local ref (implicit) */ + /* stop inact timers */ + conn_stop_inact_timers(conn); + /* RLC to SCRC */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_ERROR_IND: + xua = data; + /* FIXME: check for cause service_class_mismatch */ + /* release res + local ref (implicit) */ + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* stop inact timers */ + conn_stop_inact_timers(conn); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_T_IAR_EXP: + /* Send N-DISCONNECT.ind to local user */ + scu_gen_encode_and_send(conn, event, NULL, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* Send RLSD to peer */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_EXPIRATION_INACTIVE, NULL); + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + case SCOC_E_RCOC_ROUT_FAIL_IND: + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, NULL, OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* stop inact timers */ + conn_stop_inact_timers(conn); + /* start release timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + break; + /* Figure C.4/Q.714 */ + case SCOC_E_SCU_N_DATA_REQ: + case SCOC_E_SCU_N_EXP_DATA_REQ: + prim = data; + xua_gen_encode_and_send(conn, event, prim, SUA_CO_CODT); + conn_restart_tx_inact_timer(conn); + break; + case SCOC_E_RCOC_DT1_IND: + /* restart receive inactivity timer */ + conn_restart_rx_inact_timer(conn); + /* TODO: M-bit */ + scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DATA, + PRIM_OP_INDICATION); + break; + /* Figure C.4/Q.714 (sheet 4 of 4) */ + case SCOC_E_RCOC_IT_IND: + xua = data; + /* check if remote reference is what we expect */ + /* check class is what we expect */ + if (xua_msg_get_u32(xua, SUA_IEI_SRC_REF) != conn->remote_ref || + xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) != conn->sccp_class) { + /* Release connection */ + /* send N-DISCONNECT.ind to user */ + scu_gen_encode_and_send(conn, event, NULL, + OSMO_SCU_PRIM_N_DISCONNECT, + PRIM_OP_INDICATION); + /* Stop inactivity Timers */ + conn_stop_inact_timers(conn); + /* Send RLSD to SCRC */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_INCONSISTENT_CONN_DATA, NULL); + /* Start release timer */ + conn_start_rel_timer(conn); + osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0); + } + conn_restart_rx_inact_timer(conn); + break; + case SCOC_E_T_IAS_EXP: + /* Send IT to peer */ + xua_gen_encode_and_send(conn, event, NULL, SUA_CO_COIT); + conn_restart_tx_inact_timer(conn); + break; + } +} + +/* C.2/Q.714 (sheet 6+7 of 7) and C.3/Q.714 (sheet 5+6 of 6) */ +static void scoc_fsm_disconn_pend(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sccp_connection *conn = fi->priv; + + switch (event) { + case SCOC_E_RCOC_REL_COMPL_IND: + case SCOC_E_RCOC_RLSD_IND: + /* release res + local ref (implicit) */ + /* freeze local ref */ + /* stop release + interval timers */ + conn_stop_release_timers(conn); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_RCOC_ROUT_FAIL_IND: + case SCOC_E_RCOC_OTHER_NPDU: + /* do nothing */ + break; + case SCOC_E_T_REL_EXP: /* release timer exp */ + /* send RLSD */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_UNQUALIFIED, NULL); + /* start interval timer */ + conn_start_int_timer(conn); + /* start repeat release timer */ + conn_start_rep_rel_timer(conn); + break; + case SCOC_E_T_INT_EXP: /* interval timer exp */ + /* TODO: Inform maintenance */ + /* stop release and interval timers */ + conn_stop_release_timers(conn); + osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0); + break; + case SCOC_E_T_REP_REL_EXP: /* repeat release timer exp */ + /* send RLSD */ + xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_UNQUALIFIED, NULL); + /* re-start repeat release timer */ + conn_start_rep_rel_timer(conn); + break; + } +} + +static const struct osmo_fsm_state sccp_scoc_states[] = { + [S_IDLE] = { + .name = "IDLE", + .action = scoc_fsm_idle, + .onenter= scoc_fsm_idle_onenter, + .in_event_mask = S(SCOC_E_SCU_N_CONN_REQ) | + //S(SCOC_E_SCU_N_TYPE1_REQ) | + S(SCOC_E_RCOC_CONN_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_REL_COMPL_IND) | + S(SCOC_E_RCOC_OTHER_NPDU), + .out_state_mask = S(S_CONN_PEND_OUT) | + S(S_CONN_PEND_IN), + }, + [S_CONN_PEND_IN] = { + .name = "CONN_PEND_IN", + .action = scoc_fsm_conn_pend_in, + .in_event_mask = S(SCOC_E_SCU_N_CONN_RESP) | + S(SCOC_E_SCU_N_DISC_REQ), + .out_state_mask = S(S_IDLE) | + S(S_ACTIVE), + }, + [S_CONN_PEND_OUT] = { + .name = "CONN_PEND_OUT", + .action = scoc_fsm_conn_pend_out, + .in_event_mask = S(SCOC_E_SCU_N_DISC_REQ) | + S(SCOC_E_CONN_TMR_EXP) | + S(SCOC_E_RCOC_ROUT_FAIL_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_OTHER_NPDU) | + S(SCOC_E_RCOC_CREF_IND) | + S(SCOC_E_RCOC_CC_IND), + .out_state_mask = S(S_IDLE) | + S(S_ACTIVE) | + S(S_WAIT_CONN_CONF), + }, + [S_ACTIVE] = { + .name = "ACTIVE", + .action = scoc_fsm_active, + .in_event_mask = S(SCOC_E_SCU_N_DISC_REQ) | + /* internal disconnect */ + S(SCOC_E_RCOC_CREF_IND) | + S(SCOC_E_RCOC_REL_COMPL_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_ERROR_IND) | + S(SCOC_E_T_IAR_EXP) | + S(SCOC_E_T_IAS_EXP) | + S(SCOC_E_RCOC_ROUT_FAIL_IND) | + S(SCOC_E_SCU_N_DATA_REQ) | + S(SCOC_E_SCU_N_EXP_DATA_REQ) | + S(SCOC_E_RCOC_DT1_IND) | + S(SCOC_E_RCOC_IT_IND), + .out_state_mask = S(S_IDLE) | + S(S_DISCONN_PEND), + }, + [S_DISCONN_PEND] = { + .name = "DISCONN_PEND", + .action = scoc_fsm_disconn_pend, + .in_event_mask = S(SCOC_E_RCOC_REL_COMPL_IND) | + S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_ROUT_FAIL_IND) | + S(SCOC_E_RCOC_OTHER_NPDU) | + S(SCOC_E_T_REL_EXP) | + S(SCOC_E_T_INT_EXP) | + S(SCOC_E_T_REP_REL_EXP), + .out_state_mask = S(S_IDLE), + }, + [S_RESET_IN] = { + .name = "RESET_IN", + }, + [S_RESET_OUT] = { + .name = "RESET_OUT", + }, + [S_BOTHWAY_RESET] = { + .name = "BOTHWAY_RESET", + }, + [S_WAIT_CONN_CONF] = { + .name = "WAIT_CONN_CONF", + .action = scoc_fsm_wait_conn_conf, + .in_event_mask = S(SCOC_E_RCOC_RLSD_IND) | + S(SCOC_E_RCOC_CC_IND) | + S(SCOC_E_RCOC_OTHER_NPDU) | + S(SCOC_E_CONN_TMR_EXP) | + S(SCOC_E_RCOC_CREF_IND) | + S(SCOC_E_RCOC_ROUT_FAIL_IND), + }, +}; + +struct osmo_fsm sccp_scoc_fsm = { + .name = "SCCP-SCOC", + .states = sccp_scoc_states, + .num_states = ARRAY_SIZE(sccp_scoc_states), + /* ".log_subsys = DLSCCP" doesn't work as DLSCCP is not a constant */ + .event_names = scoc_event_names, +}; + +/* map from SCCP return cause to SCCP Refusal cause */ +static const uint8_t cause_map_cref[] = { + [SCCP_RETURN_CAUSE_SUBSYSTEM_CONGESTION] = + SCCP_REFUSAL_SUBSYTEM_CONGESTION, + [SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE] = + SCCP_REFUSAL_SUBSYSTEM_FAILURE, + [SCCP_RETURN_CAUSE_UNEQUIPPED_USER] = + SCCP_REFUSAL_UNEQUIPPED_USER, + [SCCP_RETURN_CAUSE_UNQUALIFIED] = + SCCP_REFUSAL_UNQUALIFIED, + [SCCP_RETURN_CAUSE_SCCP_FAILURE] = + SCCP_REFUSAL_SCCP_FAILURE, + [SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION] = + SCCP_REFUSAL_HOP_COUNTER_VIOLATION, +}; + +static uint8_t get_cref_cause_for_ret(uint8_t ret_cause) +{ + if (ret_cause < ARRAY_SIZE(cause_map_cref)) + return cause_map_cref[ret_cause]; + else + return SCCP_REFUSAL_UNQUALIFIED; +} + +/* Generate a COREF message purely based on an incoming SUA message, + * without the use of any local connection state */ +static struct xua_msg *gen_coref_without_conn(struct osmo_sccp_instance *inst, + struct xua_msg *xua_in, + uint32_t ref_cause) +{ + struct xua_msg *xua; + + xua = xua_msg_alloc(); + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, inst->route_ctx); + + xua_msg_copy_part(xua, SUA_IEI_DEST_REF, xua_in, SUA_IEI_SRC_REF); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | ref_cause); + /* optional: source addr */ + xua_msg_copy_part(xua, SUA_IEI_SRC_ADDR, xua_in, SUA_IEI_DEST_ADDR); + /* conditional: dest addr */ + xua_msg_copy_part(xua, SUA_IEI_DEST_ADDR, xua_in, SUA_IEI_SRC_ADDR); + /* optional: importance */ + xua_msg_copy_part(xua, SUA_IEI_IMPORTANCE, xua_in, SUA_IEI_IMPORTANCE); + /* optional: data */ + xua_msg_copy_part(xua, SUA_IEI_DATA, xua_in, SUA_IEI_DATA); + + return xua; +} + +/*! \brief SCOC: Receive SCRC Routing Failure + * \param[in] inst SCCP Instance on which we operate + * \param[in] xua SUA message that was failed to route + * \param[in] return_cause Reason (cause) for routing failure */ +void sccp_scoc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst, + struct xua_msg *xua, uint32_t return_cause) +{ + uint32_t conn_id; + struct sccp_connection *conn; + + /* try to dispatch to connection FSM (if any) */ + conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF); + conn = conn_find_by_id(inst, conn_id); + if (conn) { + osmo_fsm_inst_dispatch(conn->fi, + SCOC_E_RCOC_ROUT_FAIL_IND, xua); + } else { + /* generate + send CREF directly */ + struct xua_msg *cref; + uint8_t cref_cause = get_cref_cause_for_ret(return_cause); + cref = gen_coref_without_conn(inst, xua, cref_cause); + sccp_scrc_rx_scoc_conn_msg(inst, cref); + xua_msg_free(cref); + } +} + +/* Find a SCCP user for given SUA message (based on SUA_IEI_DEST_ADDR */ +static struct osmo_sccp_user *sccp_find_user(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + int rc; + struct osmo_sccp_addr called_addr; + + rc = sua_addr_parse(&called_addr, xua, SUA_IEI_DEST_ADDR); + if (rc < 0) { + LOGP(DLSCCP, LOGL_ERROR, "Cannot find SCCP User for XUA " + "Message %s without valid DEST_ADDR\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + return NULL; + } + + if (!(called_addr.presence & OSMO_SCCP_ADDR_T_SSN)) { + LOGP(DLSCCP, LOGL_ERROR, "Cannot resolve SCCP User for " + "XUA Message %s without SSN in CalledAddr\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + return NULL; + } + + return sccp_user_find(inst, called_addr.ssn, called_addr.pc); +} + +/* Generate a COERR based in input arguments */ +static struct xua_msg *gen_coerr(uint32_t route_ctx, uint32_t dest_ref, + uint32_t err_cause) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COERR); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref); + xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_ERROR | err_cause); + + return xua; +} + +/* generate COERR from incoming XUA and send it */ +static void tx_coerr_from_xua(struct osmo_sccp_instance *inst, + struct xua_msg *in, uint32_t err_cause) +{ + struct xua_msg *xua; + uint32_t route_ctx, dest_ref; + + route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX); + /* get *source* reference and use as destination ref */ + dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF); + + xua = gen_coerr(route_ctx, dest_ref, err_cause); + /* copy over the MTP parameters */ + xua->mtp.dpc = in->mtp.opc; + xua->mtp.opc = in->mtp.dpc; + xua->mtp.sio = in->mtp.sio; + + /* sent to SCRC for transmission */ + sccp_scrc_rx_scoc_conn_msg(inst, xua); + xua_msg_free(xua); +} + +/* Generate a RELCO based in input arguments */ +static struct xua_msg *gen_relco(uint32_t route_ctx, uint32_t dest_ref, + uint32_t src_ref) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, src_ref); + + return xua; +} + +/* generate RELCO from incoming XUA and send it */ +static void tx_relco_from_xua(struct osmo_sccp_instance *inst, + struct xua_msg *in) +{ + struct xua_msg *xua; + uint32_t route_ctx, dest_ref, src_ref; + + route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX); + /* get *source* reference and use as destination ref */ + dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF); + /* get *dest* reference and use as source ref */ + src_ref = xua_msg_get_u32(in, SUA_IEI_DEST_REF); + + xua = gen_relco(route_ctx, dest_ref, src_ref); + /* copy over the MTP parameters */ + xua->mtp.dpc = in->mtp.opc; + xua->mtp.opc = in->mtp.dpc; + xua->mtp.sio = in->mtp.sio; + + /* send to SCRC for transmission */ + sccp_scrc_rx_scoc_conn_msg(inst, xua); + xua_msg_free(xua); +} + +/* Generate a RLSD based in input arguments */ +static struct xua_msg *gen_rlsd(uint32_t route_ctx, uint32_t dest_ref, + uint32_t src_ref) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE); + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx); + xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref); + xua_msg_add_u32(xua, SUA_IEI_SRC_REF, src_ref); + + return xua; +} + +/* Generate a RLSD to both the remote side and the local conn */ +static void tx_rlsd_from_xua_twoway(struct sccp_connection *conn, + struct xua_msg *in) +{ + struct xua_msg *xua; + uint32_t route_ctx, dest_ref, src_ref; + + route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX); + /* get *source* reference and use as destination ref */ + dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF); + /* get *source* reference and use as destination ref */ + src_ref = xua_msg_get_u32(in, SUA_IEI_DEST_REF); + + /* Generate RLSD towards remote peer */ + xua = gen_rlsd(route_ctx, dest_ref, src_ref); + /* copy over the MTP parameters */ + xua->mtp.dpc = in->mtp.opc; + xua->mtp.opc = in->mtp.dpc; + xua->mtp.sio = in->mtp.sio; + /* send to SCRC for transmission */ + sccp_scrc_rx_scoc_conn_msg(conn->inst, xua); + xua_msg_free(xua); + + /* Generate RLSD towards local peer */ + xua = gen_rlsd(conn->inst->route_ctx, conn->conn_id, conn->remote_ref); + xua->mtp.dpc = in->mtp.dpc; + xua->mtp.opc = conn->remote_pc; + xua->mtp.sio = in->mtp.sio; + osmo_fsm_inst_dispatch(conn->fi, SCOC_E_RCOC_RLSD_IND, xua); + xua_msg_free(xua); +} + +/* process received message for unasigned local reference */ +static void sccp_scoc_rx_unass_local_ref(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + /* we have received a message with unassigned destination local + * reference and thus apply the action indicated in Table + * B.2/Q.714 */ + switch (xua->hdr.msg_type) { + case SUA_CO_COAK: /* CC */ + case SUA_CO_COIT: /* IT */ + case SUA_CO_RESRE: /* RSR */ + case SUA_CO_RESCO: /* RSC */ + /* Send COERR */ + tx_coerr_from_xua(inst, xua, SCCP_ERROR_LRN_MISMATCH_UNASSIGNED); + break; + case SUA_CO_COREF: /* CREF */ + case SUA_CO_RELCO: /* RLC */ + case SUA_CO_CODT: /* DT1 */ + case SUA_CO_CODA: /* AK */ + case SUA_CO_COERR: /* ERR */ + /* DISCARD */ + break; + case SUA_CO_RELRE: /* RLSD */ + /* Send RLC */ + tx_relco_from_xua(inst, xua); + break; + default: + LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + break; + } +} + +/* process received message for invalid source local reference */ +static void sccp_scoc_rx_inval_src_ref(struct sccp_connection *conn, + struct xua_msg *xua, + uint32_t inval_src_ref) +{ + LOGP(DLSCCP, LOGL_NOTICE, + "Received message for source ref %u on conn with mismatching remote ref %u\n", + inval_src_ref, conn->remote_ref); + + /* we have received a message with invalid source local + * reference and thus apply the action indicated in Table + * B.2/Q.714 */ + switch (xua->hdr.msg_type) { + case SUA_CO_RELRE: /* RLSD */ + case SUA_CO_RESRE: /* RSR */ + case SUA_CO_RESCO: /* RSC */ + /* Send ERR */ + tx_coerr_from_xua(conn->inst, xua, SCCP_ERROR_LRN_MISMATCH_INCONSISTENT); + break; + case SUA_CO_COIT: /* IT */ + /* FIXME: RLSD to both sides */ + tx_rlsd_from_xua_twoway(conn, xua); + break; + case SUA_CO_RELCO: /* RLC */ + /* DISCARD */ + break; + default: + LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + break; + } +} + +/* process received message for invalid origin point code */ +static void sccp_scoc_rx_inval_opc(struct sccp_connection *conn, + struct xua_msg *xua) +{ + LOGP(DLSCCP, LOGL_NOTICE, + "Received message for opc=%u=%s on conn with mismatching remote pc=%u=%s\n", + xua->mtp.opc, osmo_ss7_pointcode_print(conn->inst->ss7, xua->mtp.opc), + conn->remote_pc, osmo_ss7_pointcode_print2(conn->inst->ss7, conn->remote_pc)); + /* we have received a message with invalid origin PC and thus + * apply the action indiacted in Table B.2/Q.714 */ + switch (xua->hdr.msg_type) { + case SUA_CO_RELRE: /* RLSD */ + case SUA_CO_RESRE: /* RSR */ + case SUA_CO_RESCO: /* RSC */ + /* Send ERR */ + tx_coerr_from_xua(conn->inst, xua, SCCP_ERROR_POINT_CODE_MISMATCH); + break; + case SUA_CO_RELCO: /* RLC */ + case SUA_CO_CODT: /* DT1 */ + case SUA_CO_CODA: /* AK */ + case SUA_CO_COERR: /* ERR */ + /* DISCARD */ + break; + default: + LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + break; + } +} + +/*! \brief Main entrance function for primitives from the SCRC (Routing Control) + * \param[in] inst SCCP Instance in which we operate + * \param[in] xua SUA message in xua_msg format */ +void sccp_scoc_rx_from_scrc(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + struct sccp_connection *conn; + struct osmo_sccp_user *scu; + uint32_t src_loc_ref; + int event; + + /* we basically try to convert the SUA message into an event, + * and then dispatch the event to the connection-specific FSM. + * If it is a CORE (Connect REquest), we create the connection + * (and imlpicitly its FSM) first */ + + if (xua->hdr.msg_type == SUA_CO_CORE) { + scu = sccp_find_user(inst, xua); + if (!scu) { + /* this shouldn't happen, as the caller should + * have already verified that a local user is + * equipped for this SSN */ + LOGP(DLSCCP, LOGL_ERROR, "Cannot find user for " + "CORE ?!?\n"); + return; + } + /* Allocate new connection */ + conn = conn_create(inst); + conn->user = scu; + conn->incoming = true; + } else { + uint32_t conn_id; + /* Resolve existing connection */ + conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF); + conn = conn_find_by_id(inst, conn_id); + if (!conn) { + LOGP(DLSCCP, LOGL_NOTICE, "Cannot find connection for " + "local reference %u\n", conn_id); + sccp_scoc_rx_unass_local_ref(inst, xua); + return; + } + } + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->fi); + + DEBUGP(DLSCCP, "Received %s for local reference %u\n", + xua_hdr_dump(xua, &xua_dialect_sua), conn->conn_id); + + if (xua->hdr.msg_type != SUA_CO_CORE && + xua->hdr.msg_type != SUA_CO_COAK && + xua->hdr.msg_type != SUA_CO_COREF) { + if (xua_msg_find_tag(xua, SUA_IEI_SRC_REF)) { + /* Check if received source local reference != + * the one we saved in local state */ + src_loc_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF); + if (src_loc_ref != conn->remote_ref) { + sccp_scoc_rx_inval_src_ref(conn, xua, src_loc_ref); + return; + } + } + + /* Check if received OPC != the remote_pc we stored locally */ + if (xua->mtp.opc != conn->remote_pc) { + sccp_scoc_rx_inval_opc(conn, xua); + return; + } + } + + /* Map from XUA message to event */ + event = xua_msg_event_map(xua, sua_scoc_event_map, ARRAY_SIZE(sua_scoc_event_map)); + if (event < 0) { + LOGP(DLSCCP, LOGL_ERROR, "Cannot map SCRC msg %s to event\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + /* Table B.1/Q714 states DISCARD for any message with + * unknown type */ + return; + } + + /* Dispatch event to existing connection */ + osmo_fsm_inst_dispatch(conn->fi, event, xua); +} + +/* get the Connection ID of the given SCU primitive */ +static uint32_t scu_prim_conn_id(const struct osmo_scu_prim *prim) +{ + switch (prim->oph.primitive) { + case OSMO_SCU_PRIM_N_CONNECT: + return prim->u.connect.conn_id; + case OSMO_SCU_PRIM_N_DATA: + return prim->u.data.conn_id; + case OSMO_SCU_PRIM_N_DISCONNECT: + return prim->u.disconnect.conn_id; + case OSMO_SCU_PRIM_N_RESET: + return prim->u.reset.conn_id; + default: + return 0; + } +} + +/*! \brief Main entrance function for primitives from SCCP User + * \param[in] scu SCCP User sending us the primitive + * \param[in] oph Osmocom primitive sent by the user + * \returns 0 on success; negative on error */ +int osmo_sccp_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph) +{ + struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph; + struct osmo_sccp_instance *inst = scu->inst; + struct msgb *msg = prim->oph.msg; + struct sccp_connection *conn; + int rc = 0; + int event; + + LOGP(DLSCCP, LOGL_DEBUG, "Received SCCP User Primitive %s)\n", + osmo_scu_prim_name(&prim->oph)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST): + /* other CL primitives? */ + /* Connectionless by-passes this altogether */ + return sccp_sclc_user_sap_down(scu, oph); + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST): + /* Allocate new connection structure */ + conn = conn_create_id(inst, prim->u.connect.conn_id); + if (!conn) { + /* FIXME: inform user */ + goto out; + } + conn->user = scu; + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_RESPONSE): + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST): + case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST): + case OSMO_PRIM(OSMO_SCU_PRIM_N_RESET, PRIM_OP_REQUEST): + /* Resolve existing connection structure */ + conn = conn_find_by_id(inst, scu_prim_conn_id(prim)); + if (!conn) { + /* FIXME: inform user */ + goto out; + } + break; + default: + LOGP(DLSCCP, LOGL_ERROR, "Received unknown primitive %s\n", + osmo_scu_prim_name(&prim->oph)); + rc = -1; + goto out; + } + + /* Map from primitive to event */ + event = osmo_event_for_prim(oph, scu_scoc_event_map); + + /* Dispatch event into connection */ + rc = osmo_fsm_inst_dispatch(conn->fi, event, prim); +out: + /* the SAP is supposed to consume the primitive/msgb */ + msgb_free(msg); + + return rc; +} + +void sccp_scoc_flush_connections(struct osmo_sccp_instance *inst) +{ + struct sccp_connection *conn, *conn2; + + llist_for_each_entry_safe(conn, conn2, &inst->connections, list) + conn_destroy(conn); +} + +#include + +static void vty_show_connection(struct vty *vty, struct sccp_connection *conn) +{ + struct osmo_ss7_instance *s7i = conn->inst->ss7; + struct osmo_sccp_addr *remote_addr; + uint32_t local_pc = OSMO_SS7_PC_INVALID; + + if (osmo_ss7_pc_is_valid(conn->user->pc)) + local_pc = conn->user->pc; + else if (osmo_ss7_pc_is_valid(s7i->cfg.primary_pc)) + local_pc = s7i->cfg.primary_pc; + + if (conn->incoming) + remote_addr = &conn->calling_addr; + else + remote_addr = &conn->called_addr; + + vty_out(vty, "%c %06x %3u %7s ", conn->incoming ? 'I' : 'O', + conn->conn_id, conn->user->ssn, + osmo_ss7_pointcode_print(s7i, local_pc)); + vty_out(vty, "%16s %06x %3u %7s%s", + osmo_fsm_inst_state_name(conn->fi), conn->remote_ref, remote_addr->ssn, + osmo_ss7_pointcode_print(s7i, conn->remote_pc), + VTY_NEWLINE); +} + +void sccp_scoc_show_connections(struct vty *vty, struct osmo_sccp_instance *inst) +{ + struct sccp_connection *conn; + + vty_out(vty, "I Local Conn. Remote %s", VTY_NEWLINE); + vty_out(vty, "O Ref SSN PC State Ref SSN PC %s", VTY_NEWLINE); + vty_out(vty, "- ------ --- ------- ---------------- ------ --- -------%s", VTY_NEWLINE); + + llist_for_each_entry(conn, &inst->connections, list) + vty_show_connection(vty, conn); +} diff --git a/src/sccp_scrc.c b/src/sccp_scrc.c new file mode 100644 index 0000000..2afd696 --- /dev/null +++ b/src/sccp_scrc.c @@ -0,0 +1,487 @@ +/* SCCP Routing Control (SCRC) according to ITU-T Q.714 */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sccp_internal.h" +#include "xua_internal.h" + +/*********************************************************************** + * Helper Functions + ***********************************************************************/ + +static bool sua_is_connectionless(struct xua_msg *xua) +{ + if (xua->hdr.msg_class == SUA_MSGC_CL) + return true; + else + return false; +} + +static bool sua_is_cr(struct xua_msg *xua) +{ + if (xua->hdr.msg_class == SUA_MSGC_CO && + xua->hdr.msg_type == SUA_CO_CORE) + return true; + + return false; +} + +static bool dpc_accessible(struct osmo_sccp_instance *inst, uint32_t pc) +{ + /* TODO: implement this! */ + return true; +} + +static bool sccp_available(struct osmo_sccp_instance *inst, + const struct osmo_sccp_addr *addr) +{ + /* TODO: implement this! */ + return true; +} + +static int sua2sccp_tx_m3ua(struct osmo_sccp_instance *inst, + struct xua_msg *sua) +{ + struct msgb *msg; + struct osmo_mtp_prim *omp; + struct osmo_mtp_transfer_param *param; + struct osmo_ss7_instance *s7i = inst->ss7; + uint32_t remote_pc = sua->mtp.dpc; + + /* 1) encode the SUA in xua_msg to SCCP message */ + msg = osmo_sua_to_sccp(sua); + if (!msg) { + LOGP(DLSCCP, LOGL_ERROR, "Cannot encode SUA to SCCP\n"); + return -1; + } + + /* 2) wrap into MTP-TRANSFER.req primtiive */ + msg->l2h = msg->data; + omp = (struct osmo_mtp_prim *) msgb_push(msg, sizeof(*omp)); + osmo_prim_init(&omp->oph, MTP_SAP_USER, + OSMO_MTP_PRIM_TRANSFER, PRIM_OP_REQUEST, msg); + param = &omp->u.transfer; + if (sua->mtp.opc) + param->opc = sua->mtp.opc; + else { + if (!osmo_ss7_pc_is_valid(s7i->cfg.primary_pc)) { + LOGP(DLSCCP, LOGL_ERROR, "SS7 instance %u: no primary point-code set\n", + s7i->cfg.id); + return -1; + } + param->opc = s7i->cfg.primary_pc; + } + param->dpc = remote_pc; + param->sls = sua->mtp.sls; + param->sio = MTP_SIO(MTP_SI_SCCP, s7i->cfg.network_indicator); + + /* 3) send via MTP-SAP (osmo_ss7_instance) */ + return osmo_ss7_user_mtp_xfer_req(s7i, omp); +} + +/* Gererate MTP-TRANSFER.req from xUA message */ +static int gen_mtp_transfer_req_xua(struct osmo_sccp_instance *inst, + struct xua_msg *xua, + const struct osmo_sccp_addr *called) +{ + struct osmo_ss7_route *rt; + + /* this is a bit fishy due to the different requirements of + * classic SSCP/MTP compared to various SIGTRAN stackings. + * Normally, we would expect a fully encoded SCCP message here, + * but then if the route points to a SUA link, we actually need + * the SUA version of the message. + * + * We need to differentiate the following cases: + * a) SUA: encode XUA to SUA and send via ASP + * b) M3UA: encode XUA to SCCP, create MTP-TRANSFER.req + * primitive and send it via ASP + * c) M2UA/M2PA or CS7: encode XUA, create MTP-TRANSFER.req + * primitive and send it via link + */ + + if (called->presence & OSMO_SCCP_ADDR_T_PC) + xua->mtp.dpc = called->pc; + if (!xua->mtp.dpc) { + LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP " + "without DPC?!? called=%s\n", + osmo_sccp_addr_dump(called)); + return -1; + } + + rt = osmo_ss7_route_lookup(inst->ss7, xua->mtp.dpc); + if (!rt) { + LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP for " + "DPC %u: no route!\n", xua->mtp.dpc); + return -1; + } + + if (rt->dest.as) { + struct osmo_ss7_as *as = rt->dest.as; + switch (as->cfg.proto) { + case OSMO_SS7_ASP_PROT_SUA: + return sua_tx_xua_as(as, xua); + case OSMO_SS7_ASP_PROT_M3UA: + case OSMO_SS7_ASP_PROT_IPA: + return sua2sccp_tx_m3ua(inst, xua); + default: + LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req for " + "unknown protocol %u\n", as->cfg.proto); + break; + } + } else if (rt->dest.linkset) { + LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP for " + "linkset %s unsupported\n", rt->dest.linkset->cfg.name); + } else { + OSMO_ASSERT(0); + } + return -1; +} + +/*********************************************************************** + * Global Title Translation + ***********************************************************************/ + +static int translate(struct osmo_sccp_instance *inst, + const struct osmo_sccp_addr *called, + struct osmo_sccp_addr *translated) +{ + /* TODO: implement this! */ + *translated = *called; + return 0; +} + + +/*********************************************************************** + * Individual SCRC Nodes + ***********************************************************************/ + +static int scrc_local_out_common(struct osmo_sccp_instance *inst, + struct xua_msg *xua, + const struct osmo_sccp_addr *called); + +static int scrc_node_12(struct osmo_sccp_instance *inst, struct xua_msg *xua, + const struct osmo_sccp_addr *called) +{ + /* TODO: Determine restriction */ + /* TODO: Treat Calling Party Addr */ + /* TODO: Hop counter */ + /* MTP-TRANSFER.req to MTP */ + return gen_mtp_transfer_req_xua(inst, xua, called); +} + +static int scrc_node_2(struct osmo_sccp_instance *inst, struct xua_msg *xua, + const struct osmo_sccp_addr *called) +{ + /* Node 2 on Sheet 5, only CO */ + /* Is DPC accessible? */ + if (!dpc_accessible(inst, called->pc)) { + /* Error: MTP Failure */ + /* Routing Failure SCRC -> SCOC */ + sccp_scoc_rx_scrc_rout_fail(inst, xua, + SCCP_RETURN_CAUSE_MTP_FAILURE); + return 0; + } + /* Is SCCP available? */ + if (!sccp_available(inst, called)) { + /* Error: SCCP Failure */ + /* Routing Failure SCRC -> SCOC */ + sccp_scoc_rx_scrc_rout_fail(inst, xua, + SCCP_RETURN_CAUSE_SCCP_FAILURE); + return 0; + } + return scrc_node_12(inst, xua, called); +} + +static int scrc_node_7(struct osmo_sccp_instance *inst, + struct xua_msg *xua, + const struct osmo_sccp_addr *called) +{ + /* Connection Oriented? */ + if (sua_is_connectionless(xua)) { + /* TODO: Perform Capability Test */ + /* TODO: Canges Needed? */ + if (0) { + /* Changes Needed -> SCLC */ + return 0; + } + } else { + /* TODO: Coupling Required? */ + if (0) { + /* Node 13 (Sheet 5) */ + } + } + return scrc_node_12(inst, xua, called); +} + +/* Node 4 (Sheet 3) */ +static int scrc_node_4(struct osmo_sccp_instance *inst, + struct xua_msg *xua, uint32_t return_cause) +{ + /* TODO: Routing Failure SCRC -> OMAP */ + if (sua_is_connectionless(xua)) { + /* Routing Failure SCRC -> SCLC */ + sccp_sclc_rx_scrc_rout_fail(inst, xua, return_cause); + } else { + /* Routing Failure SCRC -> SCOC */ + sccp_scoc_rx_scrc_rout_fail(inst, xua, return_cause); + } + return 0; +} + +static int scrc_translate_node_9(struct osmo_sccp_instance *inst, + struct xua_msg *xua, + const struct osmo_sccp_addr *called) +{ + struct osmo_sccp_addr translated; + int rc; + + /* Translate */ + rc = translate(inst, called, &translated); + /* Node 9 (Sheet 3) */ + if (rc < 0) { + /* Node 4 (Sheet 3) */ + return scrc_node_4(inst, xua, + SCCP_RETURN_CAUSE_NO_TRANSLATION); + } + /* Route on SSN? */ + if (translated.ri != OSMO_SCCP_RI_SSN_PC && + translated.ri != OSMO_SCCP_RI_SSN_IP) { + /* TODO: GT Routing */ + LOGP(DLSCCP, LOGL_NOTICE, "GT Routing not implemented yet\n"); + /* Node 7 (Sheet 5) */ + return scrc_node_7(inst, xua, called); + } + + /* Check DPC resultant from GT translation */ + if (osmo_ss7_pc_is_local(inst->ss7, translated.pc)) { + if (sua_is_connectionless(xua)) { + /* CL_MSG -> SCLC */ + sccp_sclc_rx_from_scrc(inst, xua); + } else { + /* Node 1 (Sheet 3) */ + /* CO_MSG -> SCOC */ + sccp_scoc_rx_from_scrc(inst, xua); + } + return 0; + } else { + /* Availability already checked */ + /* Node 7 (Sheet 5) */ + return scrc_node_7(inst, xua, called); + } +} + +/* Node 6 (Sheet 3) */ +static int scrc_node_6(struct osmo_sccp_instance *inst, + struct xua_msg *xua, + const struct osmo_sccp_addr *called) +{ + struct osmo_sccp_user *scu; + /* it is not really clear that called->pc will be set to + * anything here, in the case of a SSN-only CalledAddr */ + scu = sccp_user_find(inst, called->ssn, called->pc); + + /* Is subsystem equipped? */ + if (!scu) { + /* Error: unequipped user */ + return scrc_node_4(inst, xua, + SCCP_RETURN_CAUSE_UNEQUIPPED_USER); + } + /* Is subsystem available? */ + if (0 /* !subsys_available(scu) */) { + /* Error: subsystem failure */ + /* TODO: SCRC -> SSPC */ + if (sua_is_connectionless(xua)) { + /* Routing Failure SCRC -> SCLC */ + sccp_sclc_rx_scrc_rout_fail(inst, xua, + SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE); + } else { + /* Routing Failure SCRC -> SCOC */ + sccp_scoc_rx_scrc_rout_fail(inst, xua, + SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE); + } + return 0; + } + if (sua_is_connectionless(xua)) { + /* CL_MSG -> SCLC */ + sccp_sclc_rx_from_scrc(inst, xua); + } else { + /* Node 1 (Sheet 3) */ + /* CO_MSG -> SCOC */ + sccp_scoc_rx_from_scrc(inst, xua); + } + return 0; +} + +static int scrc_local_out_common(struct osmo_sccp_instance *inst, + struct xua_msg *xua, + const struct osmo_sccp_addr *called) +{ + struct osmo_ss7_instance *s7i = inst->ss7; + + /* Called address includes DPC? */ + if (called->presence & OSMO_SCCP_ADDR_T_PC) { + if (!osmo_ss7_pc_is_local(s7i, called->pc)) { + /* Node 7 of sheet 5 */ + /* Coupling required: no */ + return scrc_node_12(inst, xua, called); + } + /* Called address includes SSN? */ + if (called->presence & OSMO_SCCP_ADDR_T_SSN) { + if (/* TODO: check if we are doing global translation && */ + (called->presence & OSMO_SCCP_ADDR_T_GT)) + return scrc_translate_node_9(inst, xua, called); + else + return scrc_node_6(inst, xua, called); + } + } + /* No SSN in CalledAddr or no DPC included */ + if (!(called->presence & OSMO_SCCP_ADDR_T_GT)) { + /* Error reason: Unqualified */ + /* TODO: Routing Failure SCRC -> OMAP */ + /* Node 4 (Sheet 3) */ + return scrc_node_4(inst, xua, + SCCP_RETURN_CAUSE_UNQUALIFIED); + } else + return scrc_translate_node_9(inst, xua, called); +} + +/*********************************************************************** + * Entrance points from MTP, SCLC, SCOC, ... + ***********************************************************************/ + +/* Figure C.1/Q.714 - SCCP Routing control procedures (SCRC) */ + +/* Connection oriented message SCOC -> SCRC */ +int sccp_scrc_rx_scoc_conn_msg(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + struct osmo_sccp_addr called; + + LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua)); + + sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR); + + /* Is this a CR message ? */ + if (xua->hdr.msg_type != SUA_CO_CORE) + return scrc_node_2(inst, xua, &called); + + /* TOOD: Coupling performed (not supported) */ + if (0) { + return scrc_node_2(inst, xua, &called); + } + + return scrc_local_out_common(inst, xua, &called); +} + +/* Connectionless Message SCLC -> SCRC */ +int sccp_scrc_rx_sclc_msg(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + struct osmo_sccp_addr called; + + LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua)); + + sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR); + + /* Message Type */ + if (xua->hdr.msg_type == SUA_CL_CLDR) { + /* UDTS, XUDTS or LUDTS */ + if (called.ri != OSMO_SCCP_RI_GT) + return scrc_node_7(inst, xua, &called); + /* Fall-through */ + } else { + if (0 /* TODO: translation already performed */) { + /* Node 12 (Sheet 5) */ + return scrc_node_12(inst, xua, &called); + } + } + + return scrc_local_out_common(inst, xua, &called); +} + +/* Figure C.1/Q.714 Sheet 1 of 12, after we converted the + * MTP-TRANSFER.ind to SUA */ +int scrc_rx_mtp_xfer_ind_xua(struct osmo_sccp_instance *inst, + struct xua_msg *xua) +{ + struct osmo_sccp_addr called; + uint32_t proto_class; + struct xua_msg_part *hop_ctr_part; + + LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua)); + /* TODO: SCCP or nodal congestion? */ + + /* CR or CL message? */ + if (!sua_is_connectionless(xua) && !sua_is_cr(xua)) { + /* Node 1 (Sheet 3) */ + /* deliver to SCOC */ + sccp_scoc_rx_from_scrc(inst, xua); + return 0; + } + /* We only treat connectionless and CR below */ + + sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR); + + /* Route on GT? */ + if (called.ri != OSMO_SCCP_RI_GT) { + /* Node 6 (Sheet 3) */ + return scrc_node_6(inst, xua, &called); + } + /* Message with hop-counter? */ + hop_ctr_part = xua_msg_find_tag(xua, SUA_IEI_S7_HOP_CTR); + if (hop_ctr_part) { + uint32_t hop_counter = xua_msg_part_get_u32(hop_ctr_part); + if (hop_counter <= 1) { + /* Error: hop-counter violation */ + /* node 4 */ + return scrc_node_4(inst, xua, SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION); + } + /* Decrement hop-counter */ + hop_counter--; + *(uint32_t *)hop_ctr_part->dat = htonl(hop_counter); + } + + /* node 3 (Sheet 2) */ + /* Protocol class 0? */ + proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS); + switch (proto_class) { + case 0: + /* TODO: Assign SLS */ + break; + case 1: + /* TODO: Map incoming SLS to outgoing SLS */ + break; + default: + break; + } + return scrc_translate_node_9(inst, xua, &called); +} diff --git a/src/sccp_user.c b/src/sccp_user.c new file mode 100644 index 0000000..21b2eed --- /dev/null +++ b/src/sccp_user.c @@ -0,0 +1,632 @@ +/* SCCP User related routines */ + +/* (C) 2017 by Harald Welte + * All Rights Reserved + * + * based on my 2011 Erlang implementation osmo_ss7/src/sua_sccp_conv.erl + * + * References: ITU-T Q.713 and IETF RFC 3868 + * + * 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, see . + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "sccp_internal.h" +#include "xua_internal.h" + +/*! \brief Find a SCCP User registered for given PC+SSN or SSN only + * \param[in] inst SCCP Instance in which to search + * \param[in] ssn Sub-System Number to search for + * \param[in] pc Point Code to search for + * \returns Matching SCCP User; NULL if none found */ +struct osmo_sccp_user * +sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc) +{ + struct osmo_sccp_user *scu; + + /* First try to find match for PC + SSN */ + llist_for_each_entry(scu, &inst->users, list) { + if (osmo_ss7_pc_is_valid(scu->pc) && scu->pc == pc && scu->ssn == ssn) + return scu; + } + + /* Then try to match on SSN only */ + llist_for_each_entry(scu, &inst->users, list) { + if (!osmo_ss7_pc_is_valid(scu->pc) && scu->ssn == ssn) + return scu; + } + + return NULL; +} + +/*! \brief Bind a SCCP User to a given Point Code + * \param[in] inst SCCP Instance + * \param[in] name human-readable name + * \param[in] ssn Sub-System Number to bind to + * \param[in] pc Point Code to bind to, or OSMO_SS7_PC_INVALID if none. + * \returns Callee-allocated SCCP User on success; negative otherwise */ +static struct osmo_sccp_user * +sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc) +{ + struct osmo_sccp_user *scu; + + scu = sccp_user_find(inst, ssn, pc); + if (scu) { + LOGP(DLSCCP, LOGL_ERROR, + "Cannot bind user '%s' to SSN=%u PC=%s, this SSN and PC" + " is already bound by '%s'\n", + name, ssn, osmo_ss7_pointcode_print(inst->ss7, pc), scu->name); + return NULL; + } + + LOGP(DLSCCP, LOGL_INFO, "Binding user '%s' to SSN=%u PC=%s\n", + name, ssn, osmo_ss7_pointcode_print(inst->ss7, pc)); + + scu = talloc_zero(inst, struct osmo_sccp_user); + scu->name = talloc_strdup(scu, name); + scu->inst = inst; + scu->prim_cb = prim_cb; + scu->ssn = ssn; + scu->pc = pc; + llist_add_tail(&scu->list, &inst->users); + + return scu; +} + +/*! \brief Bind a given SCCP User to a given SSN+PC + * \param[in] inst SCCP Instance + * \param[in] name human-readable name + * \param[in] ssn Sub-System Number to bind to + * \param[in] pc Point Code to bind to + * \returns Callee-allocated SCCP User on success; negative otherwise */ +struct osmo_sccp_user * +osmo_sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc) +{ + return sccp_user_bind_pc(inst, name, prim_cb, ssn, pc); +} + +/*! \brief Bind a given SCCP User to a given SSN (at any PC) + * \param[in] inst SCCP Instance + * \param[in] name human-readable name + * \param[in] ssn Sub-System Number to bind to + * \returns Callee-allocated SCCP User on success; negative otherwise */ +struct osmo_sccp_user * +osmo_sccp_user_bind(struct osmo_sccp_instance *inst, const char *name, + osmo_prim_cb prim_cb, uint16_t ssn) +{ + return sccp_user_bind_pc(inst, name, prim_cb, ssn, OSMO_SS7_PC_INVALID); +} + +/*! \brief Unbind a given SCCP user + * \param[in] scu SCCP User which is to be un-bound. Will be destroyed + * at the time this function returns. */ +void osmo_sccp_user_unbind(struct osmo_sccp_user *scu) +{ + LOGP(DLSCCP, LOGL_INFO, "Unbinding user '%s' from SSN=%u PC=%s\n", + scu->name, scu->ssn, + osmo_ss7_pointcode_print(scu->inst->ss7, scu->pc)); + /* FIXME: free/release all connections held by this user? */ + llist_del(&scu->list); + talloc_free(scu); +} + +void osmo_sccp_user_set_priv(struct osmo_sccp_user *scu, void *priv) +{ + scu->priv = priv; +} + +void *osmo_sccp_user_get_priv(struct osmo_sccp_user *scu) +{ + return scu->priv; +} + +/*! \brief Send a SCCP User SAP Primitive up to the User + * \param[in] scu SCCP User to whom to send the primitive + * \param[in] prim Primitive to send to the user + * \returns return value of the SCCP User's prim_cb() function */ +int sccp_user_prim_up(struct osmo_sccp_user *scu, struct osmo_scu_prim *prim) +{ + LOGP(DLSCCP, LOGL_DEBUG, "Delivering %s to SCCP User '%s'\n", + osmo_scu_prim_name(&prim->oph), scu->name); + return scu->prim_cb(&prim->oph, scu); +} + +/* prim_cb handed to MTP code for incoming MTP-TRANSFER.ind */ +static int mtp_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_sccp_instance *inst = ctx; + struct osmo_mtp_prim *omp = (struct osmo_mtp_prim *)oph; + struct xua_msg *xua; + int rc; + + OSMO_ASSERT(oph->sap == MTP_SAP_USER); + + switch OSMO_PRIM(oph->primitive, oph->operation) { + case OSMO_PRIM(OSMO_MTP_PRIM_TRANSFER, PRIM_OP_INDICATION): + /* Convert from SCCP to SUA in xua_msg format */ + xua = osmo_sccp_to_xua(oph->msg); + xua->mtp = omp->u.transfer; + /* hand this primitive into SCCP via the SCRC code */ + rc = scrc_rx_mtp_xfer_ind_xua(inst, xua); + break; + default: + LOGP(DLSCCP, LOGL_ERROR, "Unknown primitive %u:%u receivd\n", + oph->primitive, oph->operation); + rc = -1; + } + msgb_free(oph->msg); + return rc; +} + +static LLIST_HEAD(sccp_instances); + +/*! \brief create a SCCP Instance and register it as user with SS7 inst + * \param[in] ss7 SS7 instance to which this SCCP instance belongs + * \param[in] priv private data to be stored within SCCP instance + * \returns callee-allocated SCCP instance on success; NULL on error */ +struct osmo_sccp_instance * +osmo_sccp_instance_create(struct osmo_ss7_instance *ss7, void *priv) +{ + struct osmo_sccp_instance *inst; + + inst = talloc_zero(ss7, struct osmo_sccp_instance); + if (!inst) + return NULL; + + inst->ss7 = ss7; + inst->priv = priv; + INIT_LLIST_HEAD(&inst->connections); + INIT_LLIST_HEAD(&inst->users); + + inst->ss7_user.inst = ss7; + inst->ss7_user.name = "SCCP"; + inst->ss7_user.prim_cb = mtp_user_prim_cb; + inst->ss7_user.priv = inst; + + osmo_ss7_user_register(ss7, MTP_SI_SCCP, &inst->ss7_user); + + llist_add_tail(&inst->list, &sccp_instances); + + return inst; +} + +void osmo_sccp_instance_destroy(struct osmo_sccp_instance *inst) +{ + struct osmo_sccp_user *scu, *scu2; + + inst->ss7->sccp = NULL; + osmo_ss7_user_unregister(inst->ss7, MTP_SI_SCCP, &inst->ss7_user); + + llist_for_each_entry_safe(scu, scu2, &inst->users, list) { + osmo_sccp_user_unbind(scu); + } + sccp_scoc_flush_connections(inst); + llist_del(&inst->list); + talloc_free(inst); +} + +/*! \brief derive a basic local SCCP-Address from a given SCCP instance. + * \param[out] dest_addr pointer to output address memory + * \param[in] inst SCCP instance + * \param[in] ssn Subsystem Number */ +void osmo_sccp_local_addr_by_instance(struct osmo_sccp_addr *dest_addr, + const struct osmo_sccp_instance *inst, + uint32_t ssn) +{ + struct osmo_ss7_instance *ss7; + + OSMO_ASSERT(dest_addr); + OSMO_ASSERT(inst); + ss7 = inst->ss7; + OSMO_ASSERT(ss7); + + *dest_addr = (struct osmo_sccp_addr){}; + + osmo_sccp_make_addr_pc_ssn(dest_addr, ss7->cfg.primary_pc, ssn); +} + +/*! \brief check whether a given SCCP-Address is consistent. + * \param[in] addr SCCP address to check + * \param[in] presence mask with minimum required address components + * \returns true when address data seems plausible */ +bool osmo_sccp_check_addr(struct osmo_sccp_addr *addr, uint32_t presence) +{ + /* Minimum requirements do not match */ + if ((addr->presence & presence) != presence) + return false; + + /* GT ranges */ + if (addr->presence & OSMO_SCCP_ADDR_T_GT) { + if (addr->gt.gti > 15) + return false; + if (addr->gt.npi > 15) + return false; + if (addr->gt.nai > 127) + return false; + } + + /* Routing by GT, but no GT present */ + if (addr->ri == OSMO_SCCP_RI_GT + && !(addr->presence & OSMO_SCCP_ADDR_T_GT)) + return false; + + /* Routing by PC/SSN, but no PC/SSN present */ + if (addr->ri == OSMO_SCCP_RI_SSN_PC) { + if ((addr->presence & OSMO_SCCP_ADDR_T_PC) == 0) + return false; + if ((addr->presence & OSMO_SCCP_ADDR_T_SSN) == 0) + return false; + } + + if (addr->ri == OSMO_SCCP_RI_SSN_IP) { + if ((addr->presence & OSMO_SCCP_ADDR_T_IPv4) == 0 && + (addr->presence & OSMO_SCCP_ADDR_T_IPv6) == 0) + return false; + } + + return true; +} + +/*********************************************************************** + * Convenience function for CLIENT + ***********************************************************************/ + +/*! \brief request an sccp client instance + * \param[in] ctx talloc context + * \param[in] ss7_id of the SS7/CS7 instance + * \param[in] name human readable name + * \param[in] default_pc pointcode to be used on missing VTY setting + * \param[in] prot protocol to be used (e.g OSMO_SS7_ASP_PROT_M3UA) + * \param[in] default_local_port local port to be usd on missing VTY setting + * \param[in] default_local_ip local IP-address to be usd on missing VTY setting + * \param[in] default_remote_port remote port to be usd on missing VTY setting + * \param[in] default_remote_ip remote IP-address to be usd on missing VTY setting + * \returns callee-allocated SCCP instance on success; NULL on error */ + +struct osmo_sccp_instance * +osmo_sccp_simple_client_on_ss7_id(void *ctx, uint32_t ss7_id, const char *name, + uint32_t default_pc, + enum osmo_ss7_asp_protocol prot, + int default_local_port, + const char *default_local_ip, + int default_remote_port, + const char *default_remote_ip) +{ + struct osmo_ss7_instance *ss7; + bool ss7_created = false; + struct osmo_ss7_as *as; + bool as_created = false; + struct osmo_ss7_route *rt; + bool rt_created = false; + struct osmo_ss7_asp *asp; + bool asp_created = false; + char *as_name, *asp_name = NULL; + + /*! The function will examine the given CS7 instance and its sub + * components (as, asp, etc.). If necessary it will allocate + * the missing components. If no CS7 instance can be detected + * under the caller supplied ID, a new instance will be created + * beforehand. */ + + /* Choose default ports when the caller does not supply valid port + * numbers. */ + if (!default_remote_port || default_remote_port < 0) + default_remote_port = osmo_ss7_asp_protocol_port(prot); + if (default_local_port < 0) + default_local_port = osmo_ss7_asp_protocol_port(prot); + + /* Check if there is already an ss7 instance present under + * the given id. If not, we will create a new one. */ + ss7 = osmo_ss7_instance_find(ss7_id); + if (!ss7) { + LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating SS7 instance\n", + name); + + /* Create a new ss7 instance */ + ss7 = osmo_ss7_instance_find_or_create(ctx, ss7_id); + if (!ss7) { + LOGP(DLSCCP, LOGL_ERROR, + "Failed to find or create SS7 instance\n"); + return NULL; + } + + /* Setup primary pointcode + * NOTE: This means that the user must set the pointcode to a + * proper value when a cs7 instance is defined via the VTY. */ + ss7->cfg.primary_pc = default_pc; + ss7_created = true; + } + + /* In case no valid point-code has been configured via the VTY, we + * will fall back to the default pointcode. */ + if (!osmo_ss7_pc_is_valid(ss7->cfg.primary_pc)) { + LOGP(DLSCCP, LOGL_ERROR, + "SS7 instance %u: no primary point-code set, using default point-code\n", + ss7->cfg.id); + ss7->cfg.primary_pc = default_pc; + } + + LOGP(DLSCCP, LOGL_NOTICE, "%s: Using SS7 instance %u, pc:%s\n", name, + ss7->cfg.id, osmo_ss7_pointcode_print(ss7, ss7->cfg.primary_pc)); + + /* There must not be an existing SCCP istance, regarless if the simple + * client has created the SS7 instance or if it was already present. + * An already existing SCCP instance would be an indication that this + * function has been called twice with the same SS7 instance, which + * must not be the case! */ + OSMO_ASSERT(ss7->sccp == NULL); + + /* Check if there is already an application server that matches + * the protocol we intend to use. If not, we will create one. */ + as = osmo_ss7_as_find_by_proto(ss7, prot); + if (!as) { + LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating AS instance\n", + name); + as_name = talloc_asprintf(ctx, "as-clnt-%s", name); + as = osmo_ss7_as_find_or_create(ss7, as_name, prot); + talloc_free(as_name); + if (!as) + goto out_ss7; + as_created = true; + as->cfg.routing_key.pc = ss7->cfg.primary_pc; + } + LOGP(DLSCCP, LOGL_NOTICE, "%s: Using AS instance %s\n", name, + as->cfg.name); + + /* Create a default route if necessary */ + rt = osmo_ss7_route_find_dpc_mask(ss7->rtable_system, 0, 0); + if (!rt) { + LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating default route\n", name); + rt = osmo_ss7_route_create(ss7->rtable_system, 0, 0, + as->cfg.name); + if (!rt) + goto out_as; + rt_created = true; + } + + /* Check if we do already have an application server process + * that is associated with the application server we have choosen + * the application server process must also match the protocol + * we intend to use. */ + asp = osmo_ss7_asp_find_by_proto(as, prot); + if (!asp) { + /* Check if the user has already created an ASP elsewhere under + * the default asp name. */ + asp_name = talloc_asprintf(ctx, "asp-clnt-%s", name); + asp = osmo_ss7_asp_find_by_name(ss7, asp_name); + if (!asp) { + LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating ASP instance\n", + name); + asp = + osmo_ss7_asp_find_or_create(ss7, asp_name, + default_remote_port, + default_local_port, + prot); + talloc_free(asp_name); + if (!asp) + goto out_rt; + asp_created = true; + + asp->cfg.local.host = NULL; + asp->cfg.remote.host = NULL; + if (default_local_ip) { + asp->cfg.local.host = + talloc_strdup(asp, default_local_ip); + } + if (default_remote_ip) { + asp->cfg.remote.host = + talloc_strdup(asp, default_remote_ip); + } + } else + talloc_free(asp_name); + + osmo_ss7_as_add_asp(as, asp->cfg.name); + } + + /* Ensure that the ASP we use is set to client mode. */ + asp->cfg.is_server = false; + + /* Restart ASP */ + if (prot != OSMO_SS7_ASP_PROT_IPA) + osmo_ss7_asp_use_default_lm(asp, LOGL_DEBUG); + osmo_ss7_asp_restart(asp); + LOGP(DLSCCP, LOGL_NOTICE, "%s: Using ASP instance %s\n", name, + asp->cfg.name); + + /* Allocate SCCP instance */ + LOGP(DLSCCP, LOGL_NOTICE, "%s: Creating SCCP instance\n", name); + ss7->sccp = osmo_sccp_instance_create(ss7, NULL); + if (!ss7->sccp) + goto out_asp; + + return ss7->sccp; + +out_asp: + if (asp_created) + osmo_ss7_asp_destroy(asp); +out_rt: + if (rt_created) + osmo_ss7_route_destroy(rt); +out_as: + if (as_created) + osmo_ss7_as_destroy(as); +out_ss7: + if (ss7_created) + osmo_ss7_instance_destroy(ss7); + + return NULL; +} + +/*! \brief request an sccp client instance + * \param[in] ctx talloc context + * \param[in] name human readable name + * \param[in] default_pc pointcode to be used on missing VTY setting + * \param[in] prot protocol to be used (e.g OSMO_SS7_ASP_PROT_M3UA) + * \param[in] default_local_port local port to be usd on missing VTY setting + * \param[in] default_local_ip local IP-address to be usd on missing VTY setting + * \param[in] default_remote_port remote port to be usd on missing VTY setting + * \param[in] default_remote_ip remote IP-address to be usd on missing VTY setting + * \returns callee-allocated SCCP instance on success; NULL on error */ +struct osmo_sccp_instance * +osmo_sccp_simple_client(void *ctx, const char *name, uint32_t default_pc, + enum osmo_ss7_asp_protocol prot, int default_local_port, + const char *default_local_ip, int default_remote_port, + const char *default_remote_ip) +{ + /*! This is simplified version of osmo_sccp_simple_client_on_ss7_id(). + * the only difference is that the ID of the CS7 instance will be + * set to 1 statically */ + + return osmo_sccp_simple_client_on_ss7_id(ctx, 1, name, default_pc, prot, + default_local_port, + default_local_ip, + default_remote_port, + default_remote_ip); +} + +/*********************************************************************** + * Convenience function for SERVER + ***********************************************************************/ + +struct osmo_sccp_instance * +osmo_sccp_simple_server_on_ss7_id(void *ctx, uint32_t ss7_id, uint32_t pc, + enum osmo_ss7_asp_protocol prot, + int local_port, const char *local_ip) +{ + struct osmo_ss7_instance *ss7; + struct osmo_xua_server *xs; + + if (local_port < 0) + local_port = osmo_ss7_asp_protocol_port(prot); + + /* allocate + initialize SS7 instance */ + ss7 = osmo_ss7_instance_find_or_create(ctx, ss7_id); + if (!ss7) + return NULL; + ss7->cfg.primary_pc = pc; + + xs = osmo_ss7_xua_server_create(ss7, prot, local_port, local_ip); + if (!xs) + goto out_ss7; + + /* Allocate SCCP stack */ + ss7->sccp = osmo_sccp_instance_create(ss7, NULL); + if (!ss7->sccp) + goto out_xs; + + return ss7->sccp; + +out_xs: + osmo_ss7_xua_server_destroy(xs); +out_ss7: + osmo_ss7_instance_destroy(ss7); + + return NULL; +} + +struct osmo_sccp_instance * +osmo_sccp_simple_server(void *ctx, uint32_t pc, + enum osmo_ss7_asp_protocol prot, int local_port, + const char *local_ip) +{ + return osmo_sccp_simple_server_on_ss7_id(ctx, 1, pc, prot, + local_port, local_ip); +} + +struct osmo_sccp_instance * +osmo_sccp_simple_server_add_clnt(struct osmo_sccp_instance *inst, + enum osmo_ss7_asp_protocol prot, + const char *name, uint32_t pc, + int local_port, int remote_port, + const char *remote_ip) +{ + struct osmo_ss7_instance *ss7 = inst->ss7; + struct osmo_ss7_as *as; + struct osmo_ss7_route *rt; + struct osmo_ss7_asp *asp; + char *as_name, *asp_name; + + if (local_port < 0) + local_port = osmo_ss7_asp_protocol_port(prot); + + if (remote_port < 0) + remote_port = osmo_ss7_asp_protocol_port(prot); + + as_name = talloc_asprintf(ss7, "as-srv-%s", name); + asp_name = talloc_asprintf(ss7, "asp-srv-%s", name); + + /* application server */ + as = osmo_ss7_as_find_or_create(ss7, as_name, prot); + if (!as) + goto out_strings; + talloc_free(as_name); + + /* route only selected PC to the client */ + rt = osmo_ss7_route_create(ss7->rtable_system, pc, 0xffff, as_name); + if (!rt) + goto out_as; + + asp = osmo_ss7_asp_find_or_create(ss7, asp_name, remote_port, local_port, prot); + if (!asp) + goto out_rt; + asp->cfg.is_server = true; + osmo_ss7_as_add_asp(as, asp_name); + talloc_free(asp_name); + osmo_ss7_asp_restart(asp); + + return ss7->sccp; + +out_rt: + osmo_ss7_route_destroy(rt); +out_as: + osmo_ss7_as_destroy(as); +out_strings: + talloc_free(as_name); + talloc_free(asp_name); + + return NULL; +} + +/*! \brief get the SS7 instance that is related to the given SCCP instance + * \param[in] sccp SCCP instance + * \returns SS7 instance; NULL if sccp was NULL */ +struct osmo_ss7_instance *osmo_sccp_get_ss7(const struct osmo_sccp_instance *sccp) +{ + if (!sccp) + return NULL; + return sccp->ss7; +} + +/*! \brief get the SCCP instance that is related to the given sccp user + * \param[in] scu SCCP user + * \returns SCCP instance; NULL if scu was NULL */ +struct osmo_sccp_instance *osmo_sccp_get_sccp(const struct osmo_sccp_user *scu) +{ + if (!scu) + return NULL; + return scu->inst; +} diff --git a/src/sccp_vty.c b/src/sccp_vty.c new file mode 100644 index 0000000..7f0a0de --- /dev/null +++ b/src/sccp_vty.c @@ -0,0 +1,149 @@ +/* Core SS7 Instance/Linkset/Link/AS/ASP VTY Interface */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "xua_internal.h" +#include "sccp_internal.h" + +static void show_user(struct vty *vty, struct osmo_sccp_user *user) +{ + struct osmo_sccp_instance *sccp = user->inst; + + if (osmo_ss7_pc_is_valid(user->pc)) + vty_out(vty, "SSN %3u %7s : %s%s", user->ssn, + osmo_ss7_pointcode_print(sccp->ss7, user->pc), + user->name, VTY_NEWLINE); + else + vty_out(vty, "SSN %3u ANY : %s%s", user->ssn, user->name, VTY_NEWLINE); +} + +DEFUN(show_sccp_users, show_sccp_users_cmd, + "show cs7 instance <0-15> sccp users", + SHOW_STR CS7_STR INST_STR INST_STR + "Signaling Connection Control Part\n" + "Show List of SCCP Users registered\n") +{ + int id = atoi(argv[0]); + struct osmo_ss7_instance *inst; + struct osmo_sccp_instance *sccp; + struct osmo_sccp_user *scu; + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + sccp = inst->sccp; + if (!sccp) { + vty_out(vty, "SS7 instance %d has no SCCP%s", id, VTY_NEWLINE); + return CMD_WARNING; + }; + + llist_for_each_entry(scu, &sccp->users, list) + show_user(vty, scu); + + return CMD_SUCCESS; +} + +DEFUN(show_sccp_user_ssn, show_sccp_user_ssn_cmd, + "show cs7 instance <0-15> sccp ssn <0-65535>", + SHOW_STR CS7_STR INST_STR INST_STR + "Signaling Connection Control Part\n" + "Show List of SCCP Users registered\n") +{ + int id = atoi(argv[0]); + int ssn = atoi(argv[1]); + struct osmo_ss7_instance *inst; + struct osmo_sccp_instance *sccp; + struct osmo_sccp_user *scu; + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + sccp = inst->sccp; + if (!sccp) { + vty_out(vty, "SS7 instance %d has no SCCP%s", id, VTY_NEWLINE); + return CMD_WARNING; + }; + + scu = sccp_user_find(sccp, ssn, 0); + if (!scu) { + vty_out(vty, "Can't find SCCP User in instance %d%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + show_user(vty, scu); + + return CMD_SUCCESS; +} + +DEFUN(show_sccp_connections, show_sccp_connections_cmd, + "show cs7 instance <0-15> sccp connections", + SHOW_STR CS7_STR INST_STR INST_STR + "Signaling Connection Control Part\n" + "Show List of SCCP Users registered\n") +{ + int id = atoi(argv[0]); + struct osmo_ss7_instance *inst; + struct osmo_sccp_instance *sccp; + + inst = osmo_ss7_instance_find(id); + if (!inst) { + vty_out(vty, "No SS7 instance %d found%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + sccp = inst->sccp; + if (!sccp) { + vty_out(vty, "SS7 instance %d has no SCCP%s", id, VTY_NEWLINE); + return CMD_WARNING; + }; + + sccp_scoc_show_connections(vty, sccp); + + return CMD_SUCCESS; +} + +void osmo_sccp_vty_init(void) +{ + install_element_ve(&show_sccp_users_cmd); + install_element_ve(&show_sccp_user_ssn_cmd); + install_element_ve(&show_sccp_connections_cmd); +} diff --git a/src/sua.c b/src/sua.c new file mode 100644 index 0000000..fb7545e --- /dev/null +++ b/src/sua.c @@ -0,0 +1,735 @@ +/* Minimal implementation of RFC 3868 - SCCP User Adaptation Layer */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "xua_as_fsm.h" +#include "xua_asp_fsm.h" +#include "xua_internal.h" +#include "sccp_internal.h" + +/* Appendix C.4 of Q.714 (all in milliseconds) */ +#define CONNECTION_TIMER ( 1 * 60 * 100) +#define TX_INACT_TIMER ( 7 * 60 * 100) /* RFC 3868 Ch. 8. */ +#define RX_INACT_TIMER (15 * 60 * 100) /* RFC 3868 Ch. 8. */ +#define RELEASE_TIMER ( 10 * 100) +#define RELEASE_REP_TIMER ( 10 * 100) +#define INT_TIMER ( 1 * 60 * 100) +#define GUARD_TIMER (23 * 60 * 100) +#define RESET_TIMER ( 10 * 100) + +#define SCCP_MSG_SIZE 2048 +#define SCCP_MSG_HEADROOM 512 + +struct msgb *sccp_msgb_alloc(const char *name) +{ + if (!name) + name = "SCCP"; + return msgb_alloc_headroom(SCCP_MSG_SIZE+SCCP_MSG_HEADROOM, + SCCP_MSG_HEADROOM, name); +} + +/*********************************************************************** + * Protocol Definition (string tables, mandatory IE checking) + ***********************************************************************/ + +static const struct value_string sua_iei_names[] = { + { SUA_IEI_ROUTE_CTX, "Routing Context" }, + { SUA_IEI_CORR_ID, "Correlation Id" }, + { SUA_IEI_REG_RESULT, "Registration Result" }, + { SUA_IEI_DEREG_RESULT, "De-Registration Result" }, + + { SUA_IEI_S7_HOP_CTR, "SS7 Hop Counter" }, + { SUA_IEI_SRC_ADDR, "Source Address" }, + { SUA_IEI_DEST_ADDR, "Destination Address" }, + { SUA_IEI_SRC_REF, "Source Reference" }, + { SUA_IEI_DEST_REF, "Destination Reference" }, + { SUA_IEI_CAUSE, "Cause" }, + { SUA_IEI_SEQ_NR, "Sequence Number" }, + { SUA_IEI_RX_SEQ_NR, "Receive Sequence Number" }, + { SUA_IEI_ASP_CAPA, "ASP Capability" }, + { SUA_IEI_CREDIT, "Credit" }, + { SUA_IEI_DATA, "Data" }, + { SUA_IEI_USER_CAUSE, "User/Cause" }, + { SUA_IEI_NET_APPEARANCE, "Network Appearance" }, + { SUA_IEI_ROUTING_KEY, "Routing Key" }, + { SUA_IEI_DRN, "DRN Label" }, + { SUA_IEI_TID, "TID Label" }, + { SUA_IEI_SMI, "SMI" }, + { SUA_IEI_IMPORTANCE, "Importance" }, + { SUA_IEI_MSG_PRIO, "Message Priority" }, + { SUA_IEI_PROTO_CLASS, "Protocol Class" }, + { SUA_IEI_SEQ_CTRL, "Sequence Control" }, + { SUA_IEI_SEGMENTATION, "Segmentation" }, + { SUA_IEI_CONG_LEVEL, "Congestion Level" }, + + { SUA_IEI_GT, "Global Title" }, + { SUA_IEI_PC, "Point Code" }, + { SUA_IEI_SSN, "Sub-System Number" }, + { SUA_IEI_IPv4, "IPv4 Address" }, + { SUA_IEI_HOST, "Host Name" }, + { SUA_IEI_IPv6, "IPv6 Address" }, + { 0, NULL } +}; + +#define MAND_IES(msgt, ies) [msgt] = (ies) + +static const uint16_t cldt_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_SRC_ADDR, + SUA_IEI_DEST_ADDR, SUA_IEI_SEQ_CTRL, SUA_IEI_DATA, 0 +}; +static const uint16_t cldr_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_CAUSE, SUA_IEI_SRC_ADDR, + SUA_IEI_DEST_ADDR, 0 +}; +static const struct value_string sua_cl_msgt_names[] = { + { SUA_CL_CLDT, "CLDT" }, + { SUA_CL_CLDR, "CLDR" }, + { 0, NULL } +}; +static const struct xua_msg_class msg_class_cl = { + .name = "CL", + .msgt_names = sua_cl_msgt_names, + .iei_names = sua_iei_names, + .mand_ies = { + MAND_IES(SUA_CL_CLDT, cldt_mand_ies), + MAND_IES(SUA_CL_CLDR, cldr_mand_ies), + }, +}; + +static const uint16_t codt_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_DATA, 0 +}; +static const uint16_t coda_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, 0 +}; +static const uint16_t core_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_SRC_REF, + SUA_IEI_DEST_ADDR, SUA_IEI_SEQ_CTRL, 0 +}; +static const uint16_t coak_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_DEST_REF, + SUA_IEI_SRC_REF, SUA_IEI_SEQ_CTRL, 0 +}; +static const uint16_t coref_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0 +}; +static const uint16_t relre_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, + SUA_IEI_CAUSE, 0 +}; +static const uint16_t relco_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0 +}; +static const uint16_t resre_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, + SUA_IEI_CAUSE, 0 +}; +static const uint16_t resco_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_SRC_REF, 0 +}; +static const uint16_t coerr_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_DEST_REF, SUA_IEI_CAUSE, 0 +}; +static const uint16_t coit_mand_ies[] = { + SUA_IEI_ROUTE_CTX, SUA_IEI_PROTO_CLASS, SUA_IEI_SRC_REF, + SUA_IEI_DEST_REF, 0 +}; +static const struct value_string sua_co_msgt_names[] = { + { SUA_CO_CODT, "CODT" }, + { SUA_CO_CODA, "CODA" }, + { SUA_CO_CORE, "CORE" }, + { SUA_CO_COAK, "COAK" }, + { SUA_CO_COREF, "COREF" }, + { SUA_CO_RELRE, "RELRE" }, + { SUA_CO_RELCO, "RELCO" }, + { SUA_CO_RESRE, "RESRE" }, + { SUA_CO_RESCO, "RESCO" }, + { SUA_CO_COERR, "COERR" }, + { SUA_CO_COIT, "COIT" }, + { 0, NULL } +}; +static const struct xua_msg_class msg_class_co = { + .name = "CO", + .msgt_names = sua_co_msgt_names, + .iei_names = sua_iei_names, + .mand_ies = { + MAND_IES(SUA_CO_CODT, codt_mand_ies), + MAND_IES(SUA_CO_CODA, coda_mand_ies), + MAND_IES(SUA_CO_CORE, core_mand_ies), + MAND_IES(SUA_CO_COAK, coak_mand_ies), + MAND_IES(SUA_CO_COREF, coref_mand_ies), + MAND_IES(SUA_CO_RELRE, relre_mand_ies), + MAND_IES(SUA_CO_RELCO, relco_mand_ies), + MAND_IES(SUA_CO_RESRE, resre_mand_ies), + MAND_IES(SUA_CO_RESCO, resco_mand_ies), + MAND_IES(SUA_CO_COERR, coerr_mand_ies), + MAND_IES(SUA_CO_COIT, coit_mand_ies), + }, +}; + +const struct xua_dialect xua_dialect_sua = { + .name = "SUA", + .ppid = SUA_PPID, + .port = SUA_PORT, + .log_subsys = DLSUA, + .class = { + [SUA_MSGC_MGMT] = &m3ua_msg_class_mgmt, + [SUA_MSGC_SNM] = &m3ua_msg_class_snm, + [SUA_MSGC_ASPSM] = &m3ua_msg_class_aspsm, + [SUA_MSGC_ASPTM] = &m3ua_msg_class_asptm, + [SUA_MSGC_CL] = &msg_class_cl, + [SUA_MSGC_CO] = &msg_class_co, + [SUA_MSGC_RKM] = &m3ua_msg_class_rkm, + }, +}; + +/*********************************************************************** + * ERROR generation + ***********************************************************************/ + +static struct xua_msg *sua_gen_error(uint32_t err_code) +{ + struct xua_msg *xua = xua_msg_alloc(); + + xua->hdr = XUA_HDR(SUA_MSGC_MGMT, SUA_MGMT_ERR); + xua->hdr.version = SUA_VERSION; + xua_msg_add_u32(xua, SUA_IEI_ERR_CODE, err_code); + + return xua; +} + +static struct xua_msg *sua_gen_error_msg(uint32_t err_code, struct msgb *msg) +{ + struct xua_msg *xua = sua_gen_error(err_code); + unsigned int len_max_40 = msgb_length(msg); + + if (len_max_40 > 40) + len_max_40 = 40; + + xua_msg_add_data(xua, SUA_IEI_DIAG_INFO, len_max_40, msgb_data(msg)); + + return xua; +} + +/*********************************************************************** + * Transmitting SUA messsages to SCTP + ***********************************************************************/ + +static struct msgb *sua_to_msg(struct xua_msg *xua) +{ + struct msgb *msg = xua_to_msg(SUA_VERSION, xua); + + if (!msg) { + LOGP(DLSUA, LOGL_ERROR, "Error encoding SUA Msg\n"); + return NULL; + } + + switch (xua->hdr.msg_class) { + case SUA_MSGC_CL: + case SUA_MSGC_CO: + msgb_sctp_stream(msg) = 1; + break; + default: + msgb_sctp_stream(msg) = 0; + break; + } + msgb_sctp_ppid(msg) = SUA_PPID; + + return msg; +} + +static int sua_tx_xua_asp(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct msgb *msg = sua_to_msg(xua); + + OSMO_ASSERT(asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA); + + if (!msg) + return -1; + + return osmo_ss7_asp_send(asp, msg); +} + +/*! \brief Send a given xUA message via a given SUA Application Server + * \param[in] as Application Server through which to send \ref xua + * \param[in] xua xUA message to be sent + * \return 0 on success; negative on error */ +int sua_tx_xua_as(struct osmo_ss7_as *as, struct xua_msg *xua) +{ + struct msgb *msg; + int rc; + + OSMO_ASSERT(as->cfg.proto == OSMO_SS7_ASP_PROT_SUA); + + /* Add RC for this AS */ + if (as->cfg.routing_key.context) + xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, as->cfg.routing_key.context); + + msg = sua_to_msg(xua); + if (!msg) + return -1; + + /* send the msg to the AS for transmission. The AS FSM might + * (depending on its state) enqueue it before trnsmission */ + rc = osmo_fsm_inst_dispatch(as->fi, XUA_AS_E_TRANSFER_REQ, msg); + if (rc < 0) + msgb_free(msg); + return rc; +} + +/*********************************************************************** + * Receiving SUA messsages from SCTP + ***********************************************************************/ + +/*! \brief Decode SUA Global Title according to RFC3868 Section 3.10.2.3 + * \param[out] gt User-allocated structure for decoded output + * \param[in] data binary-encoded data + * \param[in] datalen length of \ref data in octets + */ +int sua_parse_gt(struct osmo_sccp_gt *gt, const uint8_t *data, unsigned int datalen) +{ + uint8_t num_digits; + char *out_digits; + unsigned int i; + + /* 8 byte header at minimum, plus digits */ + if (datalen < 8) + return -EINVAL; + + /* parse header */ + gt->gti = data[3]; + num_digits = data[4]; + gt->tt = data[5]; + gt->npi = data[6]; + gt->nai = data[7]; + + /* parse digits */ + out_digits = gt->digits; + for (i = 0; i < datalen-8; i++) { + uint8_t byte = data[8+i]; + *out_digits++ = osmo_bcd2char(byte & 0x0F); + if (out_digits - gt->digits >= num_digits) + break; + *out_digits++ = osmo_bcd2char(byte >> 4); + if (out_digits - gt->digits >= num_digits) + break; + } + *out_digits++ = '\0'; + + return 0; +} + +/*! \brief parse SCCP address from given xUA message part + * \param[out] out caller-allocated decoded SCCP address struct + * \param[in] param xUA message part containing address + \returns 0 on success; negative on error */ +int sua_addr_parse_part(struct osmo_sccp_addr *out, + const struct xua_msg_part *param) +{ + const struct xua_parameter_hdr *par; + uint16_t ri; + uint16_t ai; + uint16_t pos; + uint16_t par_tag, par_len, par_datalen; + uint32_t *p32; + + memset(out, 0, sizeof(*out)); + + LOGP(DLSUA, LOGL_DEBUG, "%s(IEI=0x%04x) (%d) %s\n", __func__, + param->tag, param->len, + osmo_hexdump(param->dat, param->len)); + + if (param->len < 4) { + LOGP(DLSUA, LOGL_ERROR, "SUA IEI 0x%04x: invalid address length: %d\n", + param->tag, param->len); + return -EINVAL; + } + + pos = 0; + ri = ntohs(*(uint16_t*) ¶m->dat[pos]); + pos += 2; + ai = ntohs(*(uint16_t*) ¶m->dat[pos]); + pos += 2; + + switch (ri) { + case SUA_RI_GT: + out->ri = OSMO_SCCP_RI_GT; + break; + case SUA_RI_SSN_PC: + out->ri = OSMO_SCCP_RI_SSN_PC; + break; + case SUA_RI_SSN_IP: + out->ri = OSMO_SCCP_RI_SSN_IP; + break; + case SUA_RI_HOST: + default: + LOGP(DLSUA, LOGL_ERROR, "SUA IEI 0x%04x: Routing Indicator not supported yet: %d\n", + param->tag, ri); + return -ENOTSUP; + } + + if (ai != 7) { +#if 0 + LOGP(DLSUA, LOGL_ERROR, "SUA IEI 0x%04x: Address Indicator not supported yet: %x\n", + param->tag, ai); + return -ENOTSUP; +#endif + } + + /* + * FIXME: this parses the encapsulated T16L16V IEs on the go. We + * probably want to have a separate general parsing function storing + * the subparts in xua_msg_part. But before we do, we should find more + * users of this subpart parsing and be aware of the performance + * tradeoff. + */ + + while (pos + sizeof(*par) < param->len) { + par = (struct xua_parameter_hdr *) ¶m->dat[pos]; + par_tag = ntohs(par->tag); + par_len = ntohs(par->len); + par_datalen = par_len - sizeof(*par); + + LOGP(DLSUA, LOGL_DEBUG, "SUA IEI 0x%04x pos %hu/%hu: subpart tag 0x%04x, len %hu\n", + param->tag, pos, param->len, par_tag, par_len); + + switch (par_tag) { + case SUA_IEI_PC: + if (par_datalen != 4) + goto subpar_fail; + p32 = (uint32_t*)par->data; + out->pc = ntohl(*p32); + out->presence |= OSMO_SCCP_ADDR_T_PC; + break; + case SUA_IEI_SSN: + if (par_datalen != 4) + goto subpar_fail; + /* 24 zero bits, then 8 bits SSN */ + out->ssn = par->data[3]; + out->presence |= OSMO_SCCP_ADDR_T_SSN; + break; + case SUA_IEI_GT: + if (par_datalen < 8) + goto subpar_fail; + sua_parse_gt(&out->gt, par->data, par_datalen); + out->presence |= OSMO_SCCP_ADDR_T_GT; + break; + case SUA_IEI_IPv4: + if (par_datalen != 4) + goto subpar_fail; + p32 = (uint32_t*)par->data; + /* no endian conversion, both network order */ + out->ip.v4.s_addr = *p32; + out->presence |= OSMO_SCCP_ADDR_T_IPv4; + break; + default: + LOGP(DLSUA, LOGL_ERROR, "SUA IEI 0x%04x: Unknown subpart tag %hd\n", + param->tag, par_tag); + goto subpar_fail; + } + + pos += par_len; + } + + return 0; + +subpar_fail: + LOGP(DLSUA, LOGL_ERROR, "Failed to parse subparts of address IEI=0x%04x\n", + param->tag); + return -EINVAL; +} + +/*! \brief parse SCCP address from given xUA message IE + * \param[out] out caller-allocated decoded SCCP address struct + * \param[in] xua xUA message + * \param[in] iei Information Element Identifier inside \ref xua + \returns 0 on success; negative on error */ +int sua_addr_parse(struct osmo_sccp_addr *out, struct xua_msg *xua, uint16_t iei) +{ + const struct xua_msg_part *param = xua_msg_find_tag(xua, iei); + if (!param) { + memset(out, 0, sizeof(*out)); + return -ENODEV; + } + + return sua_addr_parse_part(out, param); +} + +/* connectionless messages received from socket */ +static int sua_rx_cl(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct osmo_sccp_instance *inst = asp->inst->sccp; + + OSMO_ASSERT(xua->hdr.msg_class == SUA_MSGC_CL); + + switch (xua->hdr.msg_type) { + case 0: /* Reserved, permitted by ETSI TS 101 592 5.2.3.2 */ + case SUA_CL_CLDT: + case SUA_CL_CLDR: + break; + default: + return SUA_ERR_UNSUPP_MSG_TYPE; + } + + /* We feed into SCRC, which then hands the message into + * either SCLC or SCOC, or forwards it to MTP */ + return scrc_rx_mtp_xfer_ind_xua(inst, xua); +} + +/* connection-oriented messages received from socket */ +static int sua_rx_co(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct osmo_sccp_instance *inst = asp->inst->sccp; + + OSMO_ASSERT(xua->hdr.msg_class == SUA_MSGC_CO); + + switch (xua->hdr.msg_type) { + case 0: /* Reserved, permitted by ETSI TS 101 592 5.2.3.2 */ + case SUA_CO_CORE: + case SUA_CO_COAK: + case SUA_CO_COREF: + case SUA_CO_RELRE: + case SUA_CO_RELCO: + case SUA_CO_RESCO: + case SUA_CO_RESRE: + case SUA_CO_CODT: + case SUA_CO_CODA: + case SUA_CO_COERR: + case SUA_CO_COIT: + break; + default: + return SUA_ERR_UNSUPP_MSG_TYPE; + } + + /* We feed into SCRC, which then hands the message into + * either SCLC or SCOC, or forwards it to MTP */ + return scrc_rx_mtp_xfer_ind_xua(inst, xua); +} + +static int sua_rx_mgmt_err(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + uint32_t err_code = xua_msg_get_u32(xua, SUA_IEI_ERR_CODE); + + LOGPASP(asp, DLSUA, LOGL_ERROR, "Received MGMT_ERR '%s': %s\n", + get_value_string(m3ua_err_names, err_code), + xua_msg_dump(xua, &xua_dialect_sua)); + + /* NEVER return != 0 here, as we cannot respont to an ERR + * message with another ERR! */ + return 0; +} + +static int sua_rx_mgmt_ntfy(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct osmo_xlm_prim_notify ntfy; + const char *type_name, *info_name; + + m3ua_decode_notify(&ntfy, asp, xua); + + type_name = get_value_string(m3ua_ntfy_type_names, ntfy.status_type); + + switch (ntfy.status_type) { + case M3UA_NOTIFY_T_STATCHG: + info_name = get_value_string(m3ua_ntfy_stchg_names, + ntfy.status_info); + break; + case M3UA_NOTIFY_T_OTHER: + info_name = get_value_string(m3ua_ntfy_other_names, + ntfy.status_info); + break; + default: + info_name = "NULL"; + break; + } + LOGPASP(asp, DLSUA, LOGL_NOTICE, "Received NOTIFY Type %s:%s (%s)\n", + type_name, info_name, + ntfy.info_string ? ntfy.info_string : ""); + + if (ntfy.info_string) + talloc_free(ntfy.info_string); + + /* TODO: should we report this soemwhere? */ + return 0; +} + +static int sua_rx_mgmt(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + switch (xua->hdr.msg_type) { + case SUA_MGMT_ERR: + return sua_rx_mgmt_err(asp, xua); + case SUA_MGMT_NTFY: + return sua_rx_mgmt_ntfy(asp, xua); + default: + return SUA_ERR_UNSUPP_MSG_TYPE; + } +} + +/* map from SUA ASPSM/ASPTM to xua_asp_fsm event */ +static const struct xua_msg_event_map sua_aspxm_map[] = { + { SUA_MSGC_ASPSM, SUA_ASPSM_UP, XUA_ASP_E_ASPSM_ASPUP }, + { SUA_MSGC_ASPSM, SUA_ASPSM_DOWN, XUA_ASP_E_ASPSM_ASPDN }, + { SUA_MSGC_ASPSM, SUA_ASPSM_BEAT, XUA_ASP_E_ASPSM_BEAT }, + { SUA_MSGC_ASPSM, SUA_ASPSM_UP_ACK, XUA_ASP_E_ASPSM_ASPUP_ACK }, + { SUA_MSGC_ASPSM, SUA_ASPSM_DOWN_ACK, XUA_ASP_E_ASPSM_ASPDN_ACK }, + { SUA_MSGC_ASPSM, SUA_ASPSM_BEAT_ACK, XUA_ASP_E_ASPSM_BEAT_ACK }, + { SUA_MSGC_ASPTM, SUA_ASPTM_ACTIVE, XUA_ASP_E_ASPTM_ASPAC }, + { SUA_MSGC_ASPTM, SUA_ASPTM_INACTIVE, XUA_ASP_E_ASPTM_ASPIA }, + { SUA_MSGC_ASPTM, SUA_ASPTM_ACTIVE_ACK, XUA_ASP_E_ASPTM_ASPAC_ACK }, + { SUA_MSGC_ASPTM, SUA_ASPTM_INACTIVE_ACK, XUA_ASP_E_ASPTM_ASPIA_ACK }, +}; + +static int sua_rx_asp(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + int event; + + /* map from the SUA message class and message type to the XUA + * ASP FSM event number */ + event = xua_msg_event_map(xua, sua_aspxm_map, + ARRAY_SIZE(sua_aspxm_map)); + if (event < 0) + return SUA_ERR_UNSUPP_MSG_TYPE; + + /* deliver that event to the ASP FSM */ + osmo_fsm_inst_dispatch(asp->fi, event, xua); + + return 0; +} + +/*! \brief process SUA message received from socket + * \param[in] asp Application Server Process receiving \ref msg + * \param[in] msg received message buffer + * \returns 0 on success; negative on error */ +int sua_rx_msg(struct osmo_ss7_asp *asp, struct msgb *msg) +{ + struct xua_msg *xua = NULL, *err = NULL; + int rc = 0; + + OSMO_ASSERT(asp->cfg.proto == OSMO_SS7_ASP_PROT_SUA); + + /* caller owns msg memory, we shall neither free it here nor + * keep references beyon the execution of this function and its + * callees. */ + + if (!asp->inst->sccp) { + LOGP(DLSUA, LOGL_ERROR, "%s(asp->inst->sccp=NULL)\n", __func__); + return -EIO; + } + + xua = xua_from_msg(1, msgb_length(msg), msg->data); + if (!xua) { + struct xua_common_hdr *hdr = (struct xua_common_hdr *) msg->data; + + LOGPASP(asp, DLSUA, LOGL_ERROR, "Unable to parse incoming " + "SUA message\n"); + + if (hdr->version != SUA_VERSION) + err = sua_gen_error_msg(SUA_ERR_INVALID_VERSION, msg); + else + err = sua_gen_error_msg(SUA_ERR_PARAM_FIELD_ERR, msg); + goto out; + } + +#if 0 + xua->mtp.opc = ; + xua->mtp.dpc = ; +#endif + xua->mtp.sio = MTP_SI_SCCP; + + LOGPASP(asp, DLSUA, LOGL_DEBUG, "Received SUA Message (%s)\n", + xua_hdr_dump(xua, &xua_dialect_sua)); + + if (!xua_dialect_check_all_mand_ies(&xua_dialect_sua, xua)) { + /* FIXME: Return error? */ + err = sua_gen_error_msg(SUA_ERR_MISSING_PARAM, msg); + goto out; + } + + /* TODO: check if any AS configured in ASP */ + /* TODO: check for valid routing context */ + + switch (xua->hdr.msg_class) { + case SUA_MSGC_CL: + if (msgb_sctp_stream(msg) == 0) { + rc = SUA_ERR_INVAL_STREAM_ID; + break; + } + rc = sua_rx_cl(asp, xua); + break; + case SUA_MSGC_CO: + if (msgb_sctp_stream(msg) == 0) { + rc = SUA_ERR_INVAL_STREAM_ID; + break; + } + rc = sua_rx_co(asp, xua); + break; + case SUA_MSGC_ASPSM: + case SUA_MSGC_ASPTM: + rc = sua_rx_asp(asp, xua); + break; + case SUA_MSGC_MGMT: + rc = sua_rx_mgmt(asp, xua); + break; + case SUA_MSGC_SNM: + case SUA_MSGC_RKM: + /* FIXME */ + LOGPASP(asp, DLSUA, LOGL_NOTICE, "Received unsupported SUA " + "Message Class %u\n", xua->hdr.msg_class); + err = sua_gen_error_msg(SUA_ERR_UNSUPP_MSG_CLASS, msg); + break; + default: + LOGPASP(asp, DLSUA, LOGL_NOTICE, "Received unknown SUA " + "Message Class %u\n", xua->hdr.msg_class); + err = sua_gen_error_msg(SUA_ERR_UNSUPP_MSG_CLASS, msg); + break; + } + + if (rc > 0) + err = sua_gen_error_msg(rc, msg); + +out: + if (err) + sua_tx_xua_asp(asp, err); + + xua_msg_free(xua); + + return rc; +} + diff --git a/src/xua_as_fsm.c b/src/xua_as_fsm.c new file mode 100644 index 0000000..8f764f1 --- /dev/null +++ b/src/xua_as_fsm.c @@ -0,0 +1,395 @@ +/* SCCP M3UA / SUA AS osmo_fsm according to RFC3868 4.3.1 / RFC4666 4.3.2 */ +/* (C) Copyright 2017 by Harald Welte + * + * All Rights reserved. + * + * Based on Erlang implementation xua_as_fsm.erl in osmo-ss7.git + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "xua_asp_fsm.h" +#include "xua_as_fsm.h" +#include "xua_internal.h" + +static struct msgb *encode_notify(const struct osmo_xlm_prim_notify *npar) +{ + struct xua_msg *xua = m3ua_encode_notify(npar); + struct msgb *msg = xua_to_msg(M3UA_VERSION, xua); + xua_msg_free(xua); + return msg; +} + +static int asp_notify_all_as(struct osmo_ss7_as *as, struct osmo_xlm_prim_notify *npar) +{ + struct msgb *msg; + unsigned int i, sent = 0; + + /* we don't send notify to IPA peers! */ + if (as->cfg.proto == OSMO_SS7_ASP_PROT_IPA) + return 0; + + /* iterate over all non-DOWN ASPs and send them the message */ + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + struct osmo_ss7_asp *asp = as->cfg.asps[i]; + + if (!asp) + continue; + + if (!asp->fi || asp->fi->state == XUA_ASP_S_DOWN) + continue; + + /* Optional: ASP Identifier (if sent in ASP-UP) */ + if (asp->asp_id_present) { + npar->presence |= NOTIFY_PAR_P_ASP_ID; + npar->asp_id = asp->asp_id; + } else + npar->presence &= ~NOTIFY_PAR_P_ASP_ID; + + /* TODO: Optional Routing Context */ + + msg = encode_notify(npar); + osmo_ss7_asp_send(asp, msg); + sent++; + } + + return sent; +} + +/* actually transmit a message through this AS */ +int xua_as_transmit_msg(struct osmo_ss7_as *as, struct msgb *msg) +{ + struct osmo_ss7_asp *asp; + unsigned int i; + + /* FIXME: proper selection of the ASP based on the SLS and the + * traffic mode type! */ + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + asp = as->cfg.asps[i]; + if (!asp) + continue; + if (asp) + break; + } + + if (!asp) { + LOGPFSM(as->fi, "No ASP in AS, dropping message\n"); + msgb_free(msg); + return -1; + } + + return osmo_ss7_asp_send(asp, msg); +} + + +/*********************************************************************** + * Actual FSM + ***********************************************************************/ + +#define S(x) (1 << (x)) + +#define MSEC_TO_S_US(x) (x/1000), ((x%1000)*10) + +enum xua_as_state { + XUA_AS_S_DOWN, + XUA_AS_S_INACTIVE, + XUA_AS_S_ACTIVE, + XUA_AS_S_PENDING, +}; + +static const struct value_string xua_as_event_names[] = { + { XUA_ASPAS_ASP_INACTIVE_IND, "ASPAS-ASP_INACTIVE.ind" }, + { XUA_ASPAS_ASP_DOWN_IND, "ASPAS-ASP_DOWN.ind" }, + { XUA_ASPAS_ASP_ACTIVE_IND, "ASPAS-ASP_ACTIVE.ind" }, + { XUA_AS_E_RECOVERY_EXPD, "AS-T_REC_EXPD.ind" }, + { XUA_AS_E_TRANSFER_REQ, "AS-TRANSFER.req" }, + { 0, NULL } +}; + +struct xua_as_fsm_priv { + struct osmo_ss7_as *as; + struct { + struct osmo_timer_list t_r; + struct llist_head queued_msgs; + } recovery; +}; + +/* is any other ASP in this AS in state != DOWN? */ +static bool check_any_other_asp_not_down(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp_cmp) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + struct osmo_ss7_asp *asp = as->cfg.asps[i]; + if (!asp) + continue; + + if (asp_cmp == asp) + continue; + + if (asp->fi && asp->fi->state != XUA_ASP_S_DOWN) + return true; + } + + return false; +} + +/* is any other ASP in this AS in state ACTIVE? */ +static bool check_any_other_asp_in_active(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp_cmp) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + struct osmo_ss7_asp *asp = as->cfg.asps[i]; + if (!asp) + continue; + + if (asp_cmp == asp) + continue; + + if (asp->fi && asp->fi->state == XUA_ASP_S_ACTIVE) + return true; + } + + return false; +} + +static void t_r_callback(void *_fi) +{ + struct osmo_fsm_inst *fi = _fi; + osmo_fsm_inst_dispatch(fi, XUA_AS_E_RECOVERY_EXPD, NULL); +} + +static void xua_as_fsm_down(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case XUA_ASPAS_ASP_INACTIVE_IND: + /* one ASP transitions into ASP-INACTIVE */ + osmo_fsm_inst_state_chg(fi, XUA_AS_S_INACTIVE, 0, 0); + break; + case XUA_ASPAS_ASP_DOWN_IND: + /* ignore */ + break; + } +} + +/* onenter call-back responsible of transmitting NTFY to all ASPs in + * case of AS state changes */ +static void xua_as_fsm_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv; + struct osmo_ss7_as *as = xafp->as; + struct osmo_xlm_prim_notify npar = { + .status_type = M3UA_NOTIFY_T_STATCHG, + }; + + switch (fi->state) { + case XUA_AS_S_INACTIVE: + npar.status_info = M3UA_NOTIFY_I_AS_INACT; + break; + case XUA_AS_S_ACTIVE: + npar.status_info = M3UA_NOTIFY_I_AS_ACT; + break; + case XUA_AS_S_PENDING: + npar.status_info = M3UA_NOTIFY_I_AS_PEND; + break; + default: + return; + } + + /* Add the routing context, if it is configured */ + if (as->cfg.routing_key.context) { + npar.presence |= NOTIFY_PAR_P_ROUTE_CTX; + npar.route_ctx = as->cfg.routing_key.context; + } + + /* TODO: ASP-Id of ASP triggering this state change */ + + asp_notify_all_as(xafp->as, &npar); +}; + +static void xua_as_fsm_inactive(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv; + struct osmo_ss7_asp *asp = data; + + switch (event) { + case XUA_ASPAS_ASP_DOWN_IND: + /* one ASP transitions into ASP-DOWN */ + if (check_any_other_asp_not_down(xafp->as, asp)) { + /* ignore, we stay AS_INACTIVE */ + } else + osmo_fsm_inst_state_chg(fi, XUA_AS_S_DOWN, 0, 0); + break; + case XUA_ASPAS_ASP_ACTIVE_IND: + /* one ASP transitions into ASP-ACTIVE */ + osmo_fsm_inst_state_chg(fi, XUA_AS_S_ACTIVE, 0, 0); + break; + case XUA_ASPAS_ASP_INACTIVE_IND: + /* ignore */ + break; + } +} + +static void xua_as_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv; + struct osmo_ss7_asp *asp; + struct msgb *msg; + + switch (event) { + case XUA_ASPAS_ASP_DOWN_IND: + case XUA_ASPAS_ASP_INACTIVE_IND: + asp = data; + if (check_any_other_asp_in_active(xafp->as, asp)) { + /* ignore, we stay AS_ACTIVE */ + } else { + uint32_t recovery_msec = xafp->as->cfg.recovery_timeout_msec; + osmo_fsm_inst_state_chg(fi, XUA_AS_S_PENDING, 0, 0); + /* Start T(r) */ + osmo_timer_schedule(&xafp->recovery.t_r, MSEC_TO_S_US(recovery_msec)); + /* FIXME: Queue all signalling messages until + * recovery or T(r) expiry */ + } + break; + case XUA_ASPAS_ASP_ACTIVE_IND: + /* ignore */ + break; + case XUA_AS_E_TRANSFER_REQ: + /* message for transmission */ + msg = data; + xua_as_transmit_msg(xafp->as, msg); + break; + } +} + +static void xua_as_fsm_pending(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv; + struct msgb *msg; + + switch (event) { + case XUA_ASPAS_ASP_ACTIVE_IND: + /* one ASP transitions into ASP-ACTIVE */ + osmo_timer_del(&xafp->recovery.t_r); + osmo_fsm_inst_state_chg(fi, XUA_AS_S_ACTIVE, 0, 0); + /* push out any pending queued messages */ + while ((msg = msgb_dequeue(&xafp->recovery.queued_msgs))) + xua_as_transmit_msg(xafp->as, msg); + break; + case XUA_ASPAS_ASP_INACTIVE_IND: + /* ignore */ + break; + case XUA_ASPAS_ASP_DOWN_IND: + /* ignore */ + break; + case XUA_AS_E_RECOVERY_EXPD: + LOGPFSM(fi, "T(r) expired; dropping queued messages\n"); + while ((msg = msgb_dequeue(&xafp->recovery.queued_msgs))) + talloc_free(msg); + if (check_any_other_asp_not_down(xafp->as, NULL)) + osmo_fsm_inst_state_chg(fi, XUA_AS_S_INACTIVE, 0, 0); + else + osmo_fsm_inst_state_chg(fi, XUA_AS_S_DOWN, 0, 0); + break; + case XUA_AS_E_TRANSFER_REQ: + /* enqueue the to-be-transferred message */ + msg = data; + msgb_enqueue(&xafp->recovery.queued_msgs, msg); + break; + } +} + +static const struct osmo_fsm_state xua_as_fsm_states[] = { + [XUA_AS_S_DOWN] = { + .in_event_mask = S(XUA_ASPAS_ASP_INACTIVE_IND) | + S(XUA_ASPAS_ASP_DOWN_IND), + .out_state_mask = S(XUA_AS_S_DOWN) | + S(XUA_AS_S_INACTIVE), + .name = "AS_DOWN", + .action = xua_as_fsm_down, + }, + [XUA_AS_S_INACTIVE] = { + .in_event_mask = S(XUA_ASPAS_ASP_DOWN_IND) | + S(XUA_ASPAS_ASP_ACTIVE_IND) | + S(XUA_ASPAS_ASP_INACTIVE_IND), + .out_state_mask = S(XUA_AS_S_DOWN) | + S(XUA_AS_S_INACTIVE) | + S(XUA_AS_S_ACTIVE), + .name = "AS_INACTIVE", + .action = xua_as_fsm_inactive, + .onenter = xua_as_fsm_onenter, + }, + [XUA_AS_S_ACTIVE] = { + .in_event_mask = S(XUA_ASPAS_ASP_DOWN_IND) | + S(XUA_ASPAS_ASP_INACTIVE_IND) | + S(XUA_ASPAS_ASP_ACTIVE_IND) | + S(XUA_AS_E_TRANSFER_REQ), + .out_state_mask = S(XUA_AS_S_ACTIVE) | + S(XUA_AS_S_PENDING), + .name = "AS_ACTIVE", + .action = xua_as_fsm_active, + .onenter = xua_as_fsm_onenter, + }, + [XUA_AS_S_PENDING] = { + .in_event_mask = S(XUA_ASPAS_ASP_INACTIVE_IND) | + S(XUA_ASPAS_ASP_DOWN_IND) | + S(XUA_ASPAS_ASP_ACTIVE_IND) | + S(XUA_AS_E_TRANSFER_REQ) | + S(XUA_AS_E_RECOVERY_EXPD), + .out_state_mask = S(XUA_AS_S_DOWN) | + S(XUA_AS_S_INACTIVE) | + S(XUA_AS_S_ACTIVE) | + S(XUA_AS_S_PENDING), + .name = "AS_PENDING", + .action = xua_as_fsm_pending, + .onenter = xua_as_fsm_onenter, + }, +}; + +struct osmo_fsm xua_as_fsm = { + .name = "XUA_AS", + .states = xua_as_fsm_states, + .num_states = ARRAY_SIZE(xua_as_fsm_states), + .log_subsys = DLSS7, + .event_names = xua_as_event_names, +}; + +/*! \brief Start an AS FSM for a given Application Server + * \param[in] as Application Server for which to start the AS FSM + * \param[in] log_level Logging level for logging of this FSM + * \returns FSM instance in case of success; NULL in case of error */ +struct osmo_fsm_inst *xua_as_fsm_start(struct osmo_ss7_as *as, int log_level) +{ + struct osmo_fsm_inst *fi; + struct xua_as_fsm_priv *xafp; + + fi = osmo_fsm_inst_alloc(&xua_as_fsm, as, NULL, log_level, as->cfg.name); + + xafp = talloc_zero(fi, struct xua_as_fsm_priv); + if (!xafp) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; + } + xafp->as = as; + xafp->recovery.t_r.cb = t_r_callback; + xafp->recovery.t_r.data = fi; + INIT_LLIST_HEAD(&xafp->recovery.queued_msgs); + + fi->priv = xafp; + + return fi; +} diff --git a/src/xua_as_fsm.h b/src/xua_as_fsm.h new file mode 100644 index 0000000..0128919 --- /dev/null +++ b/src/xua_as_fsm.h @@ -0,0 +1,15 @@ +#pragma once + +struct osmo_ss7_as; + +enum xua_as_event { + XUA_ASPAS_ASP_INACTIVE_IND, + XUA_ASPAS_ASP_DOWN_IND, + XUA_ASPAS_ASP_ACTIVE_IND, + XUA_AS_E_RECOVERY_EXPD, + XUA_AS_E_TRANSFER_REQ, +}; + +extern struct osmo_fsm xua_as_fsm; + +struct osmo_fsm_inst *xua_as_fsm_start(struct osmo_ss7_as *as, int log_level); diff --git a/src/xua_asp_fsm.c b/src/xua_asp_fsm.c new file mode 100644 index 0000000..d54d45e --- /dev/null +++ b/src/xua_asp_fsm.c @@ -0,0 +1,1058 @@ +/* SCCP M3UA / SUA ASP osmo_fsm according to RFC3868 4.3.1 */ +/* (C) Copyright 2017 by Harald Welte + * + * All Rights reserved. + * + * Based on my earlier Erlang implementation xua_asp_fsm.erl in + * osmo-ss7.git + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "xua_asp_fsm.h" +#include "xua_as_fsm.h" + +#define S(x) (1 << (x)) + +/* The general idea is: + * * translate incoming SUA/M3UA msg_class/msg_type to xua_asp_event + * * propagate state transitions to XUA_AS_FSM via _onenter functiosn + * * notify the Layer Management of any relevant changes + * * + */ + +/* According to RFC3868 Section 8 */ +#define XUA_T_A_SEC 2 +#define XUA_T_R_SEC 2 +#define XUA_T_ACK_SEC 2 +#define XUA_T_BEAT_SEC 30 +#define SUA_T_IAS_SEC (7*60) /* SUA only */ +#define SUA_T_IAR_SEC (15*60) /* SUA only */ + +static const struct value_string xua_asp_role_names[] = { + { XUA_ASPFSM_ROLE_ASP, "ASP" }, + { XUA_ASPFSM_ROLE_SG, "SG" }, + { XUA_ASPFSM_ROLE_IPSP, "IPSP" }, + { 0, NULL } +}; + +static const struct value_string xua_asp_event_names[] = { + { XUA_ASP_E_M_ASP_UP_REQ, "M-ASP_UP.req" }, + { XUA_ASP_E_M_ASP_ACTIVE_REQ, "M-ASP_ACTIVE.req" }, + { XUA_ASP_E_M_ASP_DOWN_REQ, "M-ASP_DOWN.req" }, + { XUA_ASP_E_M_ASP_INACTIVE_REQ, "M-ASP_INACTIVE.req" }, + + { XUA_ASP_E_SCTP_COMM_DOWN_IND, "SCTP-COMM_DOWN.ind" }, + { XUA_ASP_E_SCTP_RESTART_IND, "SCTP-RESTART.ind" }, + { XUA_ASP_E_SCTP_EST_IND, "SCTP-EST.ind" }, + + { XUA_ASP_E_ASPSM_ASPUP, "ASPSM-ASP_UP" }, + { XUA_ASP_E_ASPSM_ASPUP_ACK, "ASPSM-ASP_UP_ACK" }, + { XUA_ASP_E_ASPTM_ASPAC, "ASPTM-ASP_AC" }, + { XUA_ASP_E_ASPTM_ASPAC_ACK, "ASPTM-ASP_AC_ACK" }, + { XUA_ASP_E_ASPSM_ASPDN, "ASPSM-ASP_DN" }, + { XUA_ASP_E_ASPSM_ASPDN_ACK, "ASPSM-ASP_DN_ACK" }, + { XUA_ASP_E_ASPTM_ASPIA, "ASPTM-ASP_IA" }, + { XUA_ASP_E_ASPTM_ASPIA_ACK, "ASPTM_ASP_IA_ACK" }, + + { XUA_ASP_E_ASPSM_BEAT, "ASPSM_BEAT" }, + { XUA_ASP_E_ASPSM_BEAT_ACK, "ASPSM_BEAT_ACK" }, + + { IPA_ASP_E_ID_RESP, "IPA_CCM_ID_RESP" }, + { IPA_ASP_E_ID_GET, "IPA_CCM_ID_GET" }, + { IPA_ASP_E_ID_ACK, "IPA_CCM_ID_ACK" }, + + { 0, NULL } +}; + +/* private data structure for each FSM instance */ +struct xua_asp_fsm_priv { + /* pointer back to ASP to which we belong */ + struct osmo_ss7_asp *asp; + /* Role (ASP/SG/IPSP) */ + enum xua_asp_role role; + + /* routing context[s]: list of 32bit integers */ + /* ACTIVE: traffic mode type, tid label, drn label ? */ + + struct { + struct osmo_timer_list timer; + int out_event; + } t_ack; +}; + +struct osmo_xlm_prim *xua_xlm_prim_alloc(enum osmo_xlm_prim_type prim_type, + enum osmo_prim_operation op) +{ + struct osmo_xlm_prim *prim; + struct msgb *msg = msgb_alloc_headroom(2048+128, 128, "xua_asp-xlm msgb"); + if (!msg) + return NULL; + + prim = (struct osmo_xlm_prim *) msgb_put(msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, XUA_SAP_LM, prim_type, op, msg); + + return prim; +} + +/* Send a XUA LM Primitive to the XUA Layer Manager (LM) */ +void xua_asp_send_xlm_prim(struct osmo_ss7_asp *asp, struct osmo_xlm_prim *prim) +{ + const struct osmo_xua_layer_manager *lm = asp->lm; + + if (lm && lm->prim_cb) + lm->prim_cb(&prim->oph, asp); + else { + LOGPASP(asp, DLSS7, LOGL_DEBUG, "No Layer Manager, dropping %s\n", + osmo_xlm_prim_name(&prim->oph)); + } + + msgb_free(prim->oph.msg); +} + +/* wrapper around send_xlm_prim for primitives without data */ +void xua_asp_send_xlm_prim_simple(struct osmo_ss7_asp *asp, + enum osmo_xlm_prim_type prim_type, + enum osmo_prim_operation op) +{ + struct osmo_xlm_prim *prim = xua_xlm_prim_alloc(prim_type, op); + if (!prim) + return; + xua_asp_send_xlm_prim(asp, prim); +} + +static void send_xlm_prim_simple(struct osmo_fsm_inst *fi, + enum osmo_xlm_prim_type prim_type, + enum osmo_prim_operation op) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + struct osmo_ss7_asp *asp = xafp->asp; + xua_asp_send_xlm_prim_simple(asp, prim_type, op); +} + +/* ask the xUA implementation to transmit a specific message */ +static int peer_send(struct osmo_fsm_inst *fi, int out_event, struct xua_msg *in) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + struct osmo_ss7_asp *asp = xafp->asp; + struct xua_msg *xua = xua_msg_alloc(); + struct msgb *msg; + + switch (out_event) { + case XUA_ASP_E_ASPSM_ASPUP: + /* RFC 3868 Ch. 3.5.1 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_UP); + /* Optional: ASP ID */ + if (asp->asp_id_present) + xua_msg_add_u32(xua, SUA_IEI_ASP_ID, asp->asp_id); + /* Optional: Info String */ + break; + case XUA_ASP_E_ASPSM_ASPUP_ACK: + /* RFC3868 Ch. 3.5.2 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_UP_ACK); + /* Optional: Info String */ + break; + case XUA_ASP_E_ASPSM_ASPDN: + /* RFC3868 Ch. 3.5.3 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_DOWN); + /* Optional: Info String */ + break; + case XUA_ASP_E_ASPSM_ASPDN_ACK: + /* RFC3868 Ch. 3.5.4 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_DOWN_ACK); + /* Optional: Info String */ + break; + case XUA_ASP_E_ASPSM_BEAT: + /* RFC3868 Ch. 3.5.5 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_BEAT); + /* Optional: Heartbeat Data */ + break; + case XUA_ASP_E_ASPSM_BEAT_ACK: + /* RFC3868 Ch. 3.5.6 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_BEAT_ACK); + /* Optional: Heartbeat Data */ + xua_msg_copy_part(xua, M3UA_IEI_HEARDBT_DATA, in, M3UA_IEI_HEARDBT_DATA); + break; + case XUA_ASP_E_ASPTM_ASPAC: + /* RFC3868 Ch. 3.6.1 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_ACTIVE); + /* Optional: Traffic Mode Type */ + /* Optional: Routing Context */ + /* Optional: TID Label */ + /* Optional: DRN Label */ + /* Optional: Info String */ + break; + case XUA_ASP_E_ASPTM_ASPAC_ACK: + /* RFC3868 Ch. 3.6.2 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_ACTIVE_ACK); + /* Optional: Traffic Mode Type */ + /* Mandatory: Routing Context */ + //FIXME xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, + /* Optional: Info String */ + break; + case XUA_ASP_E_ASPTM_ASPIA: + /* RFC3868 Ch. 3.6.3 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_INACTIVE); + /* Optional: Routing Context */ + /* Optional: Info String */ + break; + case XUA_ASP_E_ASPTM_ASPIA_ACK: + /* RFC3868 Ch. 3.6.4 */ + xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_INACTIVE_ACK); + /* Optional: Routing Context */ + /* Optional: Info String */ + break; + } + + msg = xua_to_msg(SUA_VERSION, xua); + xua_msg_free(xua); + if (!msg) + return -1; + + return osmo_ss7_asp_send(asp, msg); +} + +static int peer_send_error(struct osmo_fsm_inst *fi, uint32_t err_code) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + struct osmo_ss7_asp *asp = xafp->asp; + struct xua_msg *xua = xua_msg_alloc(); + struct msgb *msg; + + xua->hdr = XUA_HDR(SUA_MSGC_MGMT, SUA_MGMT_ERR); + xua->hdr.version = SUA_VERSION; + xua_msg_add_u32(xua, SUA_IEI_ERR_CODE, err_code); + + msg = xua_to_msg(SUA_VERSION, xua); + xua_msg_free(xua); + if (!msg) + return -1; + + return osmo_ss7_asp_send(asp, msg); +} + +static void xua_t_ack_cb(void *data) +{ + struct osmo_fsm_inst *fi = data; + struct xua_asp_fsm_priv *xafp = fi->priv; + + LOGPFSML(fi, LOGL_INFO, "T(ack) callback: re-transmitting event %s\n", + osmo_fsm_event_name(fi->fsm, xafp->t_ack.out_event)); + + /* Re-transmit message */ + peer_send(fi, xafp->t_ack.out_event, NULL); + + /* Re-start the timer */ + osmo_timer_schedule(&xafp->t_ack.timer, XUA_T_ACK_SEC, 0); +} + +static int peer_send_and_start_t_ack(struct osmo_fsm_inst *fi, + int out_event) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + int rc; + + rc = peer_send(fi, out_event, NULL); + if (rc < 0) + return rc; + + xafp->t_ack.out_event = out_event; + xafp->t_ack.timer.cb = xua_t_ack_cb, + xafp->t_ack.timer.data = fi; + + osmo_timer_schedule(&xafp->t_ack.timer, XUA_T_ACK_SEC, 0); + + return rc; +} + +static const uint32_t evt_ack_map[_NUM_XUA_ASP_E] = { + [XUA_ASP_E_ASPSM_ASPUP] = XUA_ASP_E_ASPSM_ASPUP_ACK, + [XUA_ASP_E_ASPTM_ASPAC] = XUA_ASP_E_ASPTM_ASPAC_ACK, + [XUA_ASP_E_ASPSM_ASPDN] = XUA_ASP_E_ASPSM_ASPDN_ACK, + [XUA_ASP_E_ASPTM_ASPIA] = XUA_ASP_E_ASPTM_ASPIA_ACK, + [XUA_ASP_E_ASPSM_BEAT] = XUA_ASP_E_ASPSM_BEAT_ACK, +}; + + +/* check if expected message was received + stop t_ack */ +static void check_stop_t_ack(struct osmo_fsm_inst *fi, uint32_t event) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + int exp_ack; + + if (event >= ARRAY_SIZE(evt_ack_map)) + return; + + exp_ack = evt_ack_map[xafp->t_ack.out_event]; + if (exp_ack && event == exp_ack) { + LOGPFSML(fi, LOGL_DEBUG, "T(ack) stopped\n"); + osmo_timer_del(&xafp->t_ack.timer); + } +} + +#define ENSURE_ASP_OR_IPSP(fi, event) \ + do { \ + struct xua_asp_fsm_priv *_xafp = fi->priv; \ + if (_xafp->role != XUA_ASPFSM_ROLE_ASP && \ + _xafp->role != XUA_ASPFSM_ROLE_IPSP) { \ + LOGPFSML(fi, LOGL_ERROR, "event %s not permitted " \ + "in role %s\n", \ + osmo_fsm_event_name(fi->fsm, event), \ + get_value_string(xua_asp_role_names, _xafp->role));\ + return; \ + } \ + } while(0) + +#define ENSURE_SG_OR_IPSP(fi, event) \ + do { \ + struct xua_asp_fsm_priv *_xafp = fi->priv; \ + if (_xafp->role != XUA_ASPFSM_ROLE_SG && \ + _xafp->role != XUA_ASPFSM_ROLE_IPSP) { \ + LOGPFSML(fi, LOGL_ERROR, "event %s not permitted " \ + "in role %s\n", \ + osmo_fsm_event_name(fi->fsm, event), \ + get_value_string(xua_asp_role_names, _xafp->role));\ + return; \ + } \ + } while(0) + +static void xua_asp_fsm_down(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + struct osmo_ss7_asp *asp = xafp->asp; + struct xua_msg_part *asp_id_ie; + + check_stop_t_ack(fi, event); + + switch (event) { + case XUA_ASP_E_M_ASP_UP_REQ: + /* only if role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + /* Send M3UA_MSGT_ASPSM_ASPUP and start t_ack */ + peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPSM_ASPUP); + break; + case XUA_ASP_E_ASPSM_ASPUP_ACK: + /* only if role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0); + /* inform layer manager */ + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_UP, PRIM_OP_CONFIRM); + /* This hack should be in layer manager, but let's try + * to be smart in case there is no layer manager */ + if (!asp->lm) + osmo_fsm_inst_dispatch(fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL); + break; + case XUA_ASP_E_ASPSM_ASPUP: + /* only if role SG */ + ENSURE_SG_OR_IPSP(fi, event); + asp_id_ie = xua_msg_find_tag(data, SUA_IEI_ASP_ID); + /* Optional ASP Identifier: Store for NTFY */ + if (asp_id_ie) { + asp->asp_id = xua_msg_part_get_u32(asp_id_ie); + asp->asp_id_present = true; + } + /* send ACK */ + peer_send(fi, XUA_ASP_E_ASPSM_ASPUP_ACK, NULL); + /* transition state and inform layer manager */ + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_UP, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPSM_ASPDN: + /* only if role SG */ + ENSURE_SG_OR_IPSP(fi, event); + /* The SGP MUST send an ASP Down Ack message in response + * to a received ASP Down message from the ASP even if + * the ASP is already marked as ASP-DOWN at the SGP. */ + peer_send(fi, XUA_ASP_E_ASPSM_ASPDN_ACK, NULL); + break; + case XUA_ASP_E_SCTP_EST_IND: + break; + } +} + +/* Helper function to dispatch an ASP->AS event to all AS of which this + * ASP is a memmber. Ignores routing contexts for now. */ +static void dispatch_to_all_as(struct osmo_fsm_inst *fi, uint32_t event) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + struct osmo_ss7_asp *asp = xafp->asp; + struct osmo_ss7_instance *inst = asp->inst; + struct osmo_ss7_as *as; + + llist_for_each_entry(as, &inst->as_list, list) { + if (!osmo_ss7_as_has_asp(as, asp)) + continue; + osmo_fsm_inst_dispatch(as->fi, event, asp); + } +} + +static void xua_asp_fsm_down_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + dispatch_to_all_as(fi, XUA_ASPAS_ASP_DOWN_IND); +} + +static void xua_asp_fsm_inactive(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_asp_fsm_priv *xafp = fi->priv; + struct osmo_ss7_asp *asp = xafp->asp; + struct xua_msg *xua_in; + uint32_t traf_mode; + + check_stop_t_ack(fi, event); + switch (event) { + case XUA_ASP_E_M_ASP_ACTIVE_REQ: + /* send M3UA_MSGT_ASPTM_ASPAC and start t_ack */ + peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPTM_ASPAC); + break; + case XUA_ASP_E_M_ASP_DOWN_REQ: + /* send M3UA_MSGT_ASPSM_ASPDN and start t_ack */ + peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPSM_ASPDN); + break; + case XUA_ASP_E_ASPTM_ASPAC_ACK: + /* only in role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + /* transition state and inform layer manager */ + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_ACTIVE, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_ACTIVE, + PRIM_OP_CONFIRM); + break; + case XUA_ASP_E_ASPSM_ASPDN_ACK: + /* only in role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + /* transition state and inform layer manager */ + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN, + PRIM_OP_CONFIRM); + break; + case XUA_ASP_E_ASPTM_ASPAC: + xua_in = data; + /* only in role SG */ + ENSURE_SG_OR_IPSP(fi, event); + if (xua_msg_find_tag(xua_in, M3UA_IEI_TRAF_MODE_TYP)) { + traf_mode = xua_msg_get_u32(xua_in, M3UA_IEI_TRAF_MODE_TYP); + if (traf_mode != M3UA_TMOD_OVERRIDE && + traf_mode != M3UA_TMOD_LOADSHARE && + traf_mode != M3UA_TMOD_BCAST) { + peer_send_error(fi, M3UA_ERR_UNSUPP_TRAF_MOD_TYP); + break; + } + } + if (xua_msg_find_tag(xua_in, M3UA_IEI_ROUTE_CTX)) { + uint32_t rctx = xua_msg_get_u32(xua_in, M3UA_IEI_ROUTE_CTX); + if (!osmo_ss7_as_find_by_rctx(asp->inst, rctx)) { + peer_send_error(fi, M3UA_ERR_INVAL_ROUT_CTX); + break; + } + } + /* send ACK */ + peer_send(fi, XUA_ASP_E_ASPTM_ASPAC_ACK, NULL); + /* transition state and inform layer manager */ + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_ACTIVE, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_ACTIVE, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPSM_ASPDN: + /* only in role SG */ + ENSURE_SG_OR_IPSP(fi, event); + /* send ACK */ + peer_send(fi, XUA_ASP_E_ASPSM_ASPDN_ACK, NULL); + /* transition state and inform layer manager */ + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPSM_ASPUP: + /* only if role SG */ + ENSURE_SG_OR_IPSP(fi, event); + /* If an ASP Up message is received and internally the + * remote ASP is already in the ASP-INACTIVE state, an + * ASP Up Ack message is returned and no further action + * is taken. */ + peer_send(fi, XUA_ASP_E_ASPSM_ASPUP_ACK, NULL); + break; + case XUA_ASP_E_ASPTM_ASPIA: + /* only in role SG */ + ENSURE_SG_OR_IPSP(fi, event); + peer_send(fi, XUA_ASP_E_ASPTM_ASPIA_ACK, NULL); + break; + } +} + +static void xua_asp_fsm_inactive_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + dispatch_to_all_as(fi, XUA_ASPAS_ASP_INACTIVE_IND); +} + +static void xua_asp_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + check_stop_t_ack(fi, event); + switch (event) { + case XUA_ASP_E_ASPSM_ASPDN_ACK: + /* only in role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0); + /* inform layer manager */ + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN, + PRIM_OP_CONFIRM); + break; + case XUA_ASP_E_ASPTM_ASPIA_ACK: + /* only in role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0); + /* inform layer manager */ + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_INACTIVE, + PRIM_OP_CONFIRM); + break; + case XUA_ASP_E_M_ASP_DOWN_REQ: + /* only in role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + /* send M3UA_MSGT_ASPSM_ASPDN and star t_ack */ + peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPSM_ASPDN); + break; + case XUA_ASP_E_M_ASP_INACTIVE_REQ: + /* only in role ASP */ + ENSURE_ASP_OR_IPSP(fi, event); + /* send M3UA_MSGT_ASPTM_ASPIA and star t_ack */ + peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPTM_ASPIA); + break; + case XUA_ASP_E_ASPTM_ASPIA: + /* only in role SG */ + ENSURE_SG_OR_IPSP(fi, event); + /* send ACK */ + peer_send(fi, XUA_ASP_E_ASPTM_ASPIA_ACK, NULL); + /* transition state and inform layer manager */ + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_INACTIVE, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPSM_ASPDN: + /* only in role SG */ + ENSURE_SG_OR_IPSP(fi, event); + /* send ACK */ + peer_send(fi, XUA_ASP_E_ASPSM_ASPDN_ACK, NULL); + /* transition state and inform layer manager */ + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPSM_ASPUP: + /* only if role SG */ + ENSURE_SG_OR_IPSP(fi, event); + /* an ASP Up Ack message is returned, as well as + * an Error message ("Unexpected Message), and the + * remote ASP state is changed to ASP-INACTIVE in all + * relevant Application Servers */ + peer_send_error(fi, M3UA_ERR_UNEXPECTED_MSG); + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0); + peer_send(fi, XUA_ASP_E_ASPSM_ASPUP_ACK, NULL); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_INACTIVE, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPTM_ASPAC: + /* only in role SG */ + ENSURE_SG_OR_IPSP(fi, event); + /* send ACK */ + peer_send(fi, XUA_ASP_E_ASPTM_ASPAC_ACK, NULL); + break; + } +} + +static void xua_asp_fsm_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + dispatch_to_all_as(fi, XUA_ASPAS_ASP_ACTIVE_IND); +} + +static void xua_asp_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct xua_msg *xua; + + switch (event) { + case XUA_ASP_E_SCTP_COMM_DOWN_IND: + case XUA_ASP_E_SCTP_RESTART_IND: + osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPSM_BEAT: + xua = data; + peer_send(fi, XUA_ASP_E_ASPSM_BEAT_ACK, xua); + break; + case XUA_ASP_E_ASPSM_BEAT_ACK: + /* FIXME: stop timer, if any */ + break; + default: + break; + } +} + +static int xua_asp_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + /* We don't use the fsm timer, so any calls to this are an error */ + OSMO_ASSERT(0); + return 0; +} + +static const struct osmo_fsm_state xua_asp_states[] = { + [XUA_ASP_S_DOWN] = { + .in_event_mask = S(XUA_ASP_E_M_ASP_UP_REQ) | + S(XUA_ASP_E_ASPSM_ASPUP) | + S(XUA_ASP_E_ASPSM_ASPUP_ACK) | + S(XUA_ASP_E_ASPSM_ASPDN) | + S(XUA_ASP_E_SCTP_EST_IND), + .out_state_mask = S(XUA_ASP_S_INACTIVE), + .name = "ASP_DOWN", + .action = xua_asp_fsm_down, + .onenter = xua_asp_fsm_down_onenter, + }, + [XUA_ASP_S_INACTIVE] = { + .in_event_mask = S(XUA_ASP_E_M_ASP_ACTIVE_REQ) | + S(XUA_ASP_E_M_ASP_DOWN_REQ) | + S(XUA_ASP_E_ASPTM_ASPAC) | + S(XUA_ASP_E_ASPTM_ASPAC_ACK) | + S(XUA_ASP_E_ASPTM_ASPIA) | + S(XUA_ASP_E_ASPSM_ASPDN) | + S(XUA_ASP_E_ASPSM_ASPDN_ACK) | + S(XUA_ASP_E_ASPSM_ASPUP), + .out_state_mask = S(XUA_ASP_S_DOWN) | + S(XUA_ASP_S_ACTIVE), + .name = "ASP_INACTIVE", + .action = xua_asp_fsm_inactive, + .onenter = xua_asp_fsm_inactive_onenter, + }, + [XUA_ASP_S_ACTIVE] = { + .in_event_mask = S(XUA_ASP_E_ASPSM_ASPDN) | + S(XUA_ASP_E_ASPSM_ASPDN_ACK) | + S(XUA_ASP_E_ASPSM_ASPUP) | + S(XUA_ASP_E_ASPTM_ASPIA) | + S(XUA_ASP_E_ASPTM_ASPIA_ACK) | + S(XUA_ASP_E_ASPTM_ASPAC) | + S(XUA_ASP_E_M_ASP_DOWN_REQ) | + S(XUA_ASP_E_M_ASP_INACTIVE_REQ), + .out_state_mask = S(XUA_ASP_S_INACTIVE) | + S(XUA_ASP_S_DOWN), + .name = "ASP_ACTIVE", + .action = xua_asp_fsm_active, + .onenter = xua_asp_fsm_active_onenter, + }, +}; + + +struct osmo_fsm xua_asp_fsm = { + .name = "XUA_ASP", + .states = xua_asp_states, + .num_states = ARRAY_SIZE(xua_asp_states), + .timer_cb = xua_asp_fsm_timer_cb, + .log_subsys = DLSS7, + .event_names = xua_asp_event_names, + .allstate_event_mask = S(XUA_ASP_E_SCTP_COMM_DOWN_IND) | + S(XUA_ASP_E_SCTP_RESTART_IND) | + S(XUA_ASP_E_ASPSM_BEAT) | + S(XUA_ASP_E_ASPSM_BEAT_ACK), + .allstate_action = xua_asp_allstate, +}; + +static struct osmo_fsm_inst *ipa_asp_fsm_start(struct osmo_ss7_asp *asp, + enum xua_asp_role role, int log_level); + +/*! \brief Start a new ASP finite stae machine for given ASP + * \param[in] asp Application Server Process for which to start FSM + * \param[in] role Role (ASP, SG, IPSP) of this FSM + * \param[in] log_level Logging Level for ASP FSM logging + * \returns FSM instance on success; NULL on error */ +struct osmo_fsm_inst *xua_asp_fsm_start(struct osmo_ss7_asp *asp, + enum xua_asp_role role, int log_level) +{ + struct osmo_fsm_inst *fi; + struct xua_asp_fsm_priv *xafp; + + if (asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA) + return ipa_asp_fsm_start(asp, role, log_level); + + /* allocate as child of AS? */ + fi = osmo_fsm_inst_alloc(&xua_asp_fsm, asp, NULL, log_level, asp->cfg.name); + + xafp = talloc_zero(fi, struct xua_asp_fsm_priv); + if (!xafp) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; + } + xafp->role = role; + xafp->asp = asp; + + fi->priv = xafp; + + return fi; +} + + + + + +/*********************************************************************** + * IPA Compatibility FSM + ***********************************************************************/ + +/* The idea here is to have a FSM that handles an IPA / SCCPlite link in + * a way that the higher-layer code considers it the same like an M3UA + * or SUA link. We have a couple of different states and some + * additional events. */ + +enum ipa_asp_state { + IPA_ASP_S_DOWN = XUA_ASP_S_DOWN, + IPA_ASP_S_ACTIVE = XUA_ASP_S_ACTIVE, + IPA_ASP_S_WAIT_ID_RESP, /* Waiting for ID_RESP from peer */ + IPA_ASP_S_WAIT_ID_GET, /* Waiting for ID_GET from peer */ + IPA_ASP_S_WAIT_ID_ACK, /* Waiting for ID_ACK from peer */ + IPA_ASP_S_WAIT_ID_ACK2, /* Waiting for ID_ACK (of ACK) from peer */ +}; + +/* private data structure for each FSM instance */ +struct ipa_asp_fsm_priv { + /* pointer back to ASP to which we belong */ + struct osmo_ss7_asp *asp; + /* Role (ASP/SG/IPSP) */ + enum xua_asp_role role; + + /* Structure holding parsed data of the IPA CCM ID exchange */ + struct ipaccess_unit *ipa_unit; + /* Timer for tracking if no PONG is received in response to PING */ + struct osmo_timer_list pong_timer; +}; + +enum ipa_asp_fsm_t { + T_WAIT_ID_RESP = 1, + T_WAIT_ID_ACK, + T_WAIT_ID_GET, +}; + +/* get the file descriptor related to a given ASP */ +static int get_fd_from_iafp(struct ipa_asp_fsm_priv *iafp) +{ + struct osmo_ss7_asp *asp = iafp->asp; + struct osmo_fd *ofd; + + if (asp->server) + ofd = osmo_stream_srv_get_ofd(asp->server); + else if (asp->client) + ofd = osmo_stream_cli_get_ofd(asp->client); + else + return -1; + + return ofd->fd; +} + +/* Server + Client: Initial State, wait for M-ASP-UP.req */ +static void ipa_asp_fsm_down(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ipa_asp_fsm_priv *iafp = fi->priv; + int fd = get_fd_from_iafp(iafp); + + switch (event) { + case XUA_ASP_E_M_ASP_UP_REQ: + case XUA_ASP_E_SCTP_EST_IND: + if (iafp->role == XUA_ASPFSM_ROLE_SG) { + /* Server: Transmit IPA ID GET + Wait for Response */ + if (fd >= 0) { + ipa_ccm_send_id_req(fd); + osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_RESP, 10, T_WAIT_ID_RESP); + } + } else { + /* Client: We simply wait for an ID GET */ + osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_GET, 10, T_WAIT_ID_GET); + } + break; + } +} + +/* Server: We're waiting for an ID RESP */ +static void ipa_asp_fsm_wait_id_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ipa_asp_fsm_priv *iafp = fi->priv; + struct osmo_ss7_asp *asp = iafp->asp; + int fd = get_fd_from_iafp(iafp); + struct osmo_ss7_as *as; + struct tlv_parsed tp; + struct msgb *msg; + int rc; + + switch (event) { + case IPA_ASP_E_ID_RESP: + /* resolve the AS based on the identity provided by peer. */ + msg = data; + rc = ipa_ccm_idtag_parse(&tp, msgb_l2(msg)+2, msgb_l2len(msg)-2); + if (rc < 0) { + LOGPFSML(fi, LOGL_ERROR, "Error %d parsing ID_RESP TLV: %s\n", rc, + msgb_hexdump(msg)); + goto out_err; + } + rc = ipa_ccm_tlv_to_unitdata(iafp->ipa_unit, &tp); + if (rc < 0) { + LOGPFSML(fi, LOGL_ERROR, "Error %d parsing ID_RESP: %s\n", rc, msgb_hexdump(msg)); + goto out_err; + } + if (!iafp->ipa_unit->unit_name) { + LOGPFSML(fi, LOGL_NOTICE, "No Unit Name specified by client\n"); + goto out_err; + } + as = osmo_ss7_as_find_by_name(asp->inst, iafp->ipa_unit->unit_name); + if (!as) { + LOGPFSML(fi, LOGL_NOTICE, "Cannot find any definition for IPA Unit Name '%s'\n", + iafp->ipa_unit->unit_name); + goto out_err; + } + osmo_ss7_as_add_asp(as, asp->cfg.name); + /* TODO: OAP Authentication? */ + /* Send ID_ACK */ + if (fd >= 0) { + ipaccess_send_id_ack(fd); + osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_ACK2, 10, T_WAIT_ID_ACK); + } + break; + } + return; +out_err: + osmo_ss7_asp_disconnect(asp); + return; +} + +/* Server: We're waiting for an ID ACK */ +static void ipa_asp_fsm_wait_id_ack2(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case IPA_ASP_E_ID_ACK: + /* ACK received, we can go to active state now. The + * ACTIVE onenter function will inform the AS */ + osmo_fsm_inst_state_chg(fi, IPA_ASP_S_ACTIVE, 0, 0); + break; + } +} + +/* Client: We're waiting for an ID GET */ +static void ipa_asp_fsm_wait_id_get(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ipa_asp_fsm_priv *iafp = fi->priv; + struct osmo_ss7_asp *asp = iafp->asp; + struct msgb *msg_get, *msg_resp; + const uint8_t *req_data; + int data_len; + + switch (event) { + case IPA_ASP_E_ID_GET: + msg_get = data; + req_data = msgb_l2(msg_get)+1; + data_len = msgb_l2len(msg_get)-1; + LOGPFSM(fi, "Received IPA CCM IDENTITY REQUEST for IEs %s\n", + osmo_hexdump(req_data, data_len)); + /* Send ID_RESP to server */ + msg_resp = ipa_ccm_make_id_resp_from_req(iafp->ipa_unit, req_data, data_len); + if (!msg_resp) { + LOGPFSML(fi, LOGL_ERROR, "Error building IPA CCM IDENTITY RESPONSE\n"); + break; + } + osmo_ss7_asp_send(asp, msg_resp); + osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_ACK, 10, T_WAIT_ID_ACK); + break; + } +} + +/* Client: We're waiting for an ID ACK */ +static void ipa_asp_fsm_wait_id_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ipa_asp_fsm_priv *iafp = fi->priv; + int fd; + + switch (event) { + case IPA_ASP_E_ID_ACK: + /* Send ACK2 to server */ + fd = get_fd_from_iafp(iafp); + if (fd >= 0) { + ipaccess_send_id_ack(fd); + osmo_fsm_inst_state_chg(fi, IPA_ASP_S_ACTIVE, 0, 0); + } + break; + } +} + + +/* Server + Client: We're actively transmitting user data */ +static void ipa_asp_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case XUA_ASP_E_M_ASP_DOWN_REQ: + case XUA_ASP_E_M_ASP_INACTIVE_REQ: + /* FIXME: kill ASP and (wait for) re-connect */ + break; + } +} + +static void ipa_asp_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ipa_asp_fsm_priv *iafp = fi->priv; + int fd; + + switch (event) { + case XUA_ASP_E_SCTP_COMM_DOWN_IND: + case XUA_ASP_E_SCTP_RESTART_IND: + osmo_fsm_inst_state_chg(fi, IPA_ASP_S_DOWN, 0, 0); + send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN, + PRIM_OP_INDICATION); + break; + case XUA_ASP_E_ASPSM_BEAT: + /* PING -> PONG */ + fd = get_fd_from_iafp(iafp); + if (fd >= 0) + ipaccess_send_pong(fd); + break; + case XUA_ASP_E_ASPSM_BEAT_ACK: + /* stop timer, if any */ + osmo_timer_del(&iafp->pong_timer); + break; + default: + break; + } +} + +static void ipa_asp_fsm_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + dispatch_to_all_as(fi, XUA_ASPAS_ASP_INACTIVE_IND); + dispatch_to_all_as(fi, XUA_ASPAS_ASP_ACTIVE_IND); +} + +static void ipa_pong_timer_cb(void *_fi) +{ + struct osmo_fsm_inst *fi = _fi; + struct ipa_asp_fsm_priv *iafp = fi->priv; + + LOGPFSML(fi, LOGL_NOTICE, "Peer didn't respond to PING? with PONG!\n"); + /* kill ASP and (wait for) re-connect */ + osmo_ss7_asp_disconnect(iafp->asp); +} + +static int ipa_asp_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct ipa_asp_fsm_priv *iafp = fi->priv; + + LOGPFSML(fi, LOGL_ERROR, "Timeout waiting for peer response\n"); + /* kill ASP and (wait for) re-connect */ + osmo_ss7_asp_disconnect(iafp->asp); + return -1; +} + +static const struct osmo_fsm_state ipa_asp_states[] = { + [IPA_ASP_S_DOWN] = { + .in_event_mask = S(XUA_ASP_E_M_ASP_UP_REQ) | + S(XUA_ASP_E_SCTP_EST_IND), + .out_state_mask = S(IPA_ASP_S_WAIT_ID_GET) | + S(IPA_ASP_S_WAIT_ID_RESP), + .name = "ASP_DOWN", + .action = ipa_asp_fsm_down, + .onenter = xua_asp_fsm_down_onenter, + }, + /* Server Side */ + [IPA_ASP_S_WAIT_ID_RESP] = { + .in_event_mask = S(IPA_ASP_E_ID_RESP), + .out_state_mask = S(IPA_ASP_S_WAIT_ID_ACK2) | + S(IPA_ASP_S_DOWN), + .name = "WAIT_ID_RESP", + .action = ipa_asp_fsm_wait_id_resp, + }, + /* Server Side */ + [IPA_ASP_S_WAIT_ID_ACK2] = { + .in_event_mask = S(IPA_ASP_E_ID_ACK), + .out_state_mask = S(IPA_ASP_S_ACTIVE) | + S(IPA_ASP_S_DOWN), + .name = "WAIT_ID_ACK2", + .action = ipa_asp_fsm_wait_id_ack2, + }, + /* Client Side */ + [IPA_ASP_S_WAIT_ID_GET] = { + .in_event_mask = S(IPA_ASP_E_ID_GET), + .out_state_mask = S(IPA_ASP_S_WAIT_ID_ACK), + .name = "WAIT_ID_GET", + .action = ipa_asp_fsm_wait_id_get, + }, + /* Client Side */ + [IPA_ASP_S_WAIT_ID_ACK] = { + .in_event_mask = S(IPA_ASP_E_ID_ACK), + .out_state_mask = S(IPA_ASP_S_ACTIVE) | + S(IPA_ASP_S_DOWN), + .name = "WAIT_ID_ACK", + .action = ipa_asp_fsm_wait_id_ack, + }, + [IPA_ASP_S_ACTIVE] = { + .in_event_mask = S(XUA_ASP_E_M_ASP_DOWN_REQ) | + S(XUA_ASP_E_M_ASP_INACTIVE_REQ), + .out_state_mask = S(XUA_ASP_S_INACTIVE) | + S(XUA_ASP_S_DOWN), + .name = "ASP_ACTIVE", + .action = ipa_asp_fsm_active, + .onenter = ipa_asp_fsm_active_onenter, + }, +}; + + +struct osmo_fsm ipa_asp_fsm = { + .name = "IPA_ASP", + .states = ipa_asp_states, + .num_states = ARRAY_SIZE(ipa_asp_states), + .timer_cb = ipa_asp_fsm_timer_cb, + .log_subsys = DLSS7, + .event_names = xua_asp_event_names, + .allstate_event_mask = S(XUA_ASP_E_SCTP_COMM_DOWN_IND) | + S(XUA_ASP_E_SCTP_RESTART_IND) | + S(XUA_ASP_E_ASPSM_BEAT) | + S(XUA_ASP_E_ASPSM_BEAT_ACK), + .allstate_action = ipa_asp_allstate, +}; + + +/*! \brief Start a new ASP finite stae machine for given ASP + * \param[in] asp Application Server Process for which to start FSM + * \param[in] role Role (ASP, SG, IPSP) of this FSM + * \param[in] log_level Logging Level for ASP FSM logging + * \returns FSM instance on success; NULL on error */ +static struct osmo_fsm_inst *ipa_asp_fsm_start(struct osmo_ss7_asp *asp, + enum xua_asp_role role, int log_level) +{ + struct osmo_fsm_inst *fi; + struct ipa_asp_fsm_priv *iafp; + + /* allocate as child of AS? */ + fi = osmo_fsm_inst_alloc(&ipa_asp_fsm, asp, NULL, log_level, asp->cfg.name); + + iafp = talloc_zero(fi, struct ipa_asp_fsm_priv); + if (!iafp) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; + } + iafp->role = role; + iafp->asp = asp; + iafp->ipa_unit = talloc_zero(iafp, struct ipaccess_unit); + iafp->ipa_unit->unit_name = talloc_strdup(iafp->ipa_unit, asp->cfg.name); + iafp->pong_timer.cb = ipa_pong_timer_cb; + iafp->pong_timer.data = fi; + + fi->priv = iafp; + + if (role == XUA_ASPFSM_ROLE_ASP) + osmo_fsm_inst_dispatch(fi, XUA_ASP_E_M_ASP_UP_REQ, NULL); + + return fi; +} diff --git a/src/xua_asp_fsm.h b/src/xua_asp_fsm.h new file mode 100644 index 0000000..32749ec --- /dev/null +++ b/src/xua_asp_fsm.h @@ -0,0 +1,49 @@ +#pragma once + +enum xua_asp_state { + XUA_ASP_S_DOWN, + XUA_ASP_S_INACTIVE, + XUA_ASP_S_ACTIVE, +}; + +enum xua_asp_event { + XUA_ASP_E_M_ASP_UP_REQ, + XUA_ASP_E_M_ASP_ACTIVE_REQ, + XUA_ASP_E_M_ASP_DOWN_REQ, + XUA_ASP_E_M_ASP_INACTIVE_REQ, + + XUA_ASP_E_SCTP_COMM_DOWN_IND, + XUA_ASP_E_SCTP_RESTART_IND, + XUA_ASP_E_SCTP_EST_IND, + + XUA_ASP_E_ASPSM_ASPUP, + XUA_ASP_E_ASPSM_ASPUP_ACK, + XUA_ASP_E_ASPTM_ASPAC, + XUA_ASP_E_ASPTM_ASPAC_ACK, + XUA_ASP_E_ASPSM_ASPDN, + XUA_ASP_E_ASPSM_ASPDN_ACK, + XUA_ASP_E_ASPTM_ASPIA, + XUA_ASP_E_ASPTM_ASPIA_ACK, + + XUA_ASP_E_ASPSM_BEAT, + XUA_ASP_E_ASPSM_BEAT_ACK, + + /* IPA specific */ + IPA_ASP_E_ID_RESP, + IPA_ASP_E_ID_ACK, + IPA_ASP_E_ID_GET, + + _NUM_XUA_ASP_E +}; + +enum xua_asp_role { + XUA_ASPFSM_ROLE_ASP, + XUA_ASPFSM_ROLE_SG, + XUA_ASPFSM_ROLE_IPSP, +}; + +extern struct osmo_fsm xua_asp_fsm; +extern struct osmo_fsm ipa_asp_fsm; + +struct osmo_fsm_inst *xua_asp_fsm_start(struct osmo_ss7_asp *asp, + enum xua_asp_role role, int log_level); diff --git a/src/xua_default_lm_fsm.c b/src/xua_default_lm_fsm.c new file mode 100644 index 0000000..64f26c3 --- /dev/null +++ b/src/xua_default_lm_fsm.c @@ -0,0 +1,387 @@ +/* Default XUA Layer Manager */ +/* (C) 2017 by Harald Welte + * 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 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, see . + */ + +/* The idea of this default Layer Manager is as follows: + * - we wait until a SCTP connection is established + * - we issue the ASP-UP request and wait for the ASP being in UP state + * - we wait if we receive a M-NOTIFY indication about any AS in this ASP + * - if that's not received, we use RKM to register a routing context + * for our locally configured ASP and expect a positive registration + * result as well as a NOTIFY indication about AS-ACTIVE afterwards. + */ + +#include + +#include +#include +#include +#include +#include + +#include "xua_internal.h" +#include "xua_asp_fsm.h" + +#define S(x) (1 << (x)) + +enum lm_state { + /* idle state, SCTP not connected */ + S_IDLE, + /* we're waiting for the ASP-UP to be confirmed */ + S_WAIT_ASP_UP, + /* we are waiting for any NOTIFY about an AS in this ASP */ + S_WAIT_NOTIFY, + /* we've sent a RK REG REQ and wait for the result */ + S_RKM_REG, + /* all systems up, we're communicating */ + S_ACTIVE, +}; + +enum lm_event { + LM_E_SCTP_EST_IND, + LM_E_ASP_UP_CONF, + LM_E_NOTIFY_IND, + LM_E_AS_INACTIVE_IND, + LM_E_AS_ACTIVE_IND, + LM_E_AS_STATUS_IND, + LM_E_RKM_REG_CONF, + LM_E_SCTP_DISC_IND, +}; + +static const struct value_string lm_event_names[] = { + { LM_E_SCTP_EST_IND, "SCTP-ESTABLISH.ind" }, + { LM_E_ASP_UP_CONF, "ASP-UP.conf" }, + { LM_E_NOTIFY_IND, "NOTIFY.ind" }, + { LM_E_AS_INACTIVE_IND, "AS-INACTIVE.ind" }, + { LM_E_AS_ACTIVE_IND, "AS-ACTIVE.ind" }, + { LM_E_AS_STATUS_IND, "AS-STATUS.ind" }, + { LM_E_RKM_REG_CONF, "RKM_REG.conf" }, + { LM_E_SCTP_DISC_IND, "SCTP-RELEASE.ind" }, + { 0, NULL } +}; + +enum lm_timer { + T_WAIT_ASP_UP, + T_WAIT_NOTIFY, + T_WAIT_NOTIFY_RKM, + T_WAIT_RK_REG_RESP, +}; + +struct lm_fsm_priv { + struct osmo_ss7_asp *asp; +}; + +static struct osmo_ss7_as *find_first_as_in_asp(struct osmo_ss7_asp *asp) +{ + struct osmo_ss7_as *as; + + llist_for_each_entry(as, &asp->inst->as_list, list) { + unsigned int i; + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + if (as->cfg.asps[i] == asp) + return as; + } + } + + return NULL; +} + +/* handle an incoming RKM registration response */ +static int handle_reg_conf(struct osmo_fsm_inst *fi, uint32_t l_rk_id, uint32_t rctx) +{ + struct lm_fsm_priv *lmp = fi->priv; + struct osmo_ss7_asp *asp = lmp->asp; + struct osmo_ss7_as *as; + + /* update the application server with the routing context as + * allocated/registered by the SG */ + as = osmo_ss7_as_find_by_l_rk_id(asp->inst, l_rk_id); + if (!as) { + LOGPFSM(fi, "RKM Result for unknown l_rk_id %u\n", l_rk_id); + return -EINVAL; + } + as->cfg.routing_key.context = rctx; + + return 0; +} + +static void restart_asp(struct osmo_fsm_inst *fi) +{ + struct lm_fsm_priv *lmp = fi->priv; + struct osmo_ss7_asp *asp = lmp->asp; + int log_level = fi->log_level; + + osmo_ss7_asp_restart(asp); + osmo_ss7_asp_use_default_lm(asp, log_level); +} + + +static void lm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lm_fsm_priv *lmp = fi->priv; + + switch (event) { + case LM_E_SCTP_EST_IND: + /* Try to transition to ASP-UP, wait for 20s */ + osmo_fsm_inst_state_chg(fi, S_WAIT_ASP_UP, 20, T_WAIT_ASP_UP); + osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_UP_REQ, NULL); + break; + } +} + +static void lm_wait_asp_up(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case LM_E_ASP_UP_CONF: + /* ASP is sup, wait for some time if any NOTIFY + * indications about AS in this ASP are received */ + osmo_fsm_inst_state_chg(fi, S_WAIT_NOTIFY, 2, T_WAIT_NOTIFY); + break; + } +} + + +static int lm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct lm_fsm_priv *lmp = fi->priv; + struct osmo_xlm_prim *prim; + struct osmo_ss7_as *as; + + switch (fi->T) { + case T_WAIT_ASP_UP: + /* we have been waiting for the ASP to come up, but it + * failed to do so */ + restart_asp(fi); + break; + case T_WAIT_NOTIFY: + /* No AS has reported via NOTIFY that is was + * (statically) configured at the SG for this ASP, so + * let's dynamically register */ + osmo_fsm_inst_state_chg(fi, S_RKM_REG, 10, T_WAIT_RK_REG_RESP); + prim = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_REQUEST); + as = find_first_as_in_asp(lmp->asp); + if (!as) { + LOGPFSML(fi, LOGL_ERROR, "Unable to find AS!\n"); + restart_asp(fi); + return 0; + } + /* Fill in settings from first AS (TODO: multiple AS support) */ + prim->u.rk_reg.key = as->cfg.routing_key; + osmo_xlm_sap_down(lmp->asp, &prim->oph); + break; + case T_WAIT_NOTIFY_RKM: + /* No AS has reported via NOTIFY even after dynamic RKM + * configuration */ + restart_asp(fi); + break; + case T_WAIT_RK_REG_RESP: + /* timeout of registration of routing key */ + restart_asp(fi); + break; + } + return 0; +} + +static void lm_wait_notify(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lm_fsm_priv *lmp = fi->priv; + struct osmo_xlm_prim *oxp = data; + + switch (event) { + case LM_E_NOTIFY_IND: + OSMO_ASSERT(oxp->oph.primitive == OSMO_XLM_PRIM_M_NOTIFY); + OSMO_ASSERT(oxp->oph.operation == PRIM_OP_INDICATION); + if (oxp->u.notify.status_type == M3UA_NOTIFY_T_STATCHG && + (oxp->u.notify.status_info == M3UA_NOTIFY_I_AS_INACT || + oxp->u.notify.status_info == M3UA_NOTIFY_I_AS_PEND)) { + osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL); + } + break; + case LM_E_AS_INACTIVE_IND: + /* we now know that an AS is associated with this ASP at + * the SG, and that this AS is currently inactive */ + /* request the ASP to go into active state (which + * hopefully will bring the AS to active, too) */ + osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL); + break; + } +}; + +static void lm_rkm_reg(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_xlm_prim *oxp; + int rc; + + switch (event) { + case LM_E_RKM_REG_CONF: + oxp = data; + if (oxp->u.rk_reg.status != M3UA_RKM_REG_SUCCESS) { + LOGPFSML(fi, LOGL_NOTICE, "Received RKM_REG_RSP with negative result\n"); + restart_asp(fi); + } else { + rc = handle_reg_conf(fi, oxp->u.rk_reg.key.l_rk_id, oxp->u.rk_reg.key.context); + if (rc < 0) + restart_asp(fi); + /* RKM registration was successful, we can + * transition to WAIT_NOTIFY state and assume + * that an NOTIFY/AS-INACTIVE arrives within 20 + * seconds */ + osmo_fsm_inst_state_chg(fi, S_WAIT_NOTIFY, 20, T_WAIT_NOTIFY_RKM); + } + break; + } +} + +static void lm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lm_fsm_priv *lmp = fi->priv; + struct osmo_xlm_prim *oxp; + + switch (event) { + case LM_E_AS_INACTIVE_IND: + /* request the ASP to go into active state */ + osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL); + break; + case LM_E_NOTIFY_IND: + oxp = data; + OSMO_ASSERT(oxp->oph.primitive == OSMO_XLM_PRIM_M_NOTIFY); + OSMO_ASSERT(oxp->oph.operation == PRIM_OP_INDICATION); + if (oxp->u.notify.status_type == M3UA_NOTIFY_T_STATCHG && + oxp->u.notify.status_info != M3UA_NOTIFY_I_AS_ACT) + restart_asp(fi); + break; + } +} + +static void lm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case LM_E_SCTP_DISC_IND: + restart_asp(fi); + break; + } +} + +static const struct osmo_fsm_state lm_states[] = { + [S_IDLE] = { + .in_event_mask = S(LM_E_SCTP_EST_IND), + .out_state_mask = S(S_WAIT_ASP_UP), + .name = "IDLE", + .action = lm_idle, + }, + [S_WAIT_ASP_UP] = { + .in_event_mask = S(LM_E_ASP_UP_CONF), + .out_state_mask = S(S_WAIT_NOTIFY), + .name = "WAIT_ASP_UP", + .action = lm_wait_asp_up, + }, + [S_WAIT_NOTIFY] = { + .in_event_mask = S(LM_E_AS_INACTIVE_IND) | S(LM_E_NOTIFY_IND), + .out_state_mask = S(S_RKM_REG) | S(S_ACTIVE), + .name = "WAIT_NOTIFY", + .action = lm_wait_notify, + }, + [S_RKM_REG] = { + .in_event_mask = S(LM_E_RKM_REG_CONF), + .out_state_mask = S(S_WAIT_NOTIFY), + .name = "RKM_REG", + .action = lm_rkm_reg, + }, + [S_ACTIVE] = { + .in_event_mask = S(LM_E_AS_INACTIVE_IND) | S(LM_E_NOTIFY_IND), + .name = "ACTIVE", + .action = lm_active, + }, +}; + +/* Map from incoming XLM SAP primitives towards FSM events */ +static const struct osmo_prim_event_map lm_event_map[] = { + { XUA_SAP_LM, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION, LM_E_SCTP_EST_IND }, + { XUA_SAP_LM, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION, LM_E_SCTP_DISC_IND }, + { XUA_SAP_LM, OSMO_XLM_PRIM_M_ASP_UP, PRIM_OP_CONFIRM, LM_E_ASP_UP_CONF }, + { XUA_SAP_LM, OSMO_XLM_PRIM_M_AS_STATUS, PRIM_OP_INDICATION, LM_E_AS_STATUS_IND }, + { XUA_SAP_LM, OSMO_XLM_PRIM_M_NOTIFY, PRIM_OP_INDICATION, LM_E_NOTIFY_IND }, + { XUA_SAP_LM, OSMO_XLM_PRIM_M_AS_INACTIVE, PRIM_OP_INDICATION, LM_E_AS_INACTIVE_IND }, + { XUA_SAP_LM, OSMO_XLM_PRIM_M_AS_ACTIVE, PRIM_OP_INDICATION, LM_E_AS_ACTIVE_IND }, + { XUA_SAP_LM, OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_CONFIRM, LM_E_RKM_REG_CONF }, + { 0, 0, 0, OSMO_NO_EVENT }, +}; + + +struct osmo_fsm xua_default_lm_fsm = { + .name = "xua_default_lm", + .states = lm_states, + .num_states = ARRAY_SIZE(lm_states), + .timer_cb = lm_timer_cb, + .event_names = lm_event_names, + .allstate_event_mask = S(LM_E_SCTP_DISC_IND), + .allstate_action = lm_allstate, + .log_subsys = DLSS7, +}; + + +/* layer manager primitive call-back function, registered osmo_ss7 */ +static int default_lm_prim_cb(struct osmo_prim_hdr *oph, void *_asp) +{ + struct osmo_ss7_asp *asp = _asp; + struct osmo_fsm_inst *fi = asp->lm_priv; + uint32_t event = osmo_event_for_prim(oph, lm_event_map); + char *prim_name = osmo_xlm_prim_name(oph); + + LOGPFSM(fi, "Received primitive %s\n", prim_name); + + if (event == OSMO_NO_EVENT) { + LOGPFSML(fi, LOGL_NOTICE, "Ignoring primitive %s\n", prim_name); + return 0; + } + + osmo_fsm_inst_dispatch(fi, event, oph); + + return 0; +} + +static const struct osmo_xua_layer_manager default_layer_manager = { + .prim_cb = default_lm_prim_cb, +}; + +int osmo_ss7_asp_use_default_lm(struct osmo_ss7_asp *asp, int log_level) +{ + struct lm_fsm_priv *lmp; + struct osmo_fsm_inst *fi; + + if (asp->lm_priv) { + osmo_fsm_inst_term(asp->lm_priv, OSMO_FSM_TERM_ERROR, NULL); + asp->lm_priv = NULL; + } + + fi = osmo_fsm_inst_alloc(&xua_default_lm_fsm, asp, NULL, log_level, asp->cfg.name); + + lmp = talloc_zero(fi, struct lm_fsm_priv); + if (!lmp) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return -ENOMEM; + } + lmp->asp = asp; + fi->priv = lmp; + + asp->lm = &default_layer_manager; + asp->lm_priv = fi; + + return 0; +} diff --git a/src/xua_internal.h b/src/xua_internal.h new file mode 100644 index 0000000..96bd153 --- /dev/null +++ b/src/xua_internal.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +struct osmo_sccp_addr; +struct m3ua_data_hdr; + +int sua_addr_parse_part(struct osmo_sccp_addr *out, + const struct xua_msg_part *param); +int sua_addr_parse(struct osmo_sccp_addr *out, struct xua_msg *xua, uint16_t iei); + +int sua_parse_gt(struct osmo_sccp_gt *gt, const uint8_t *data, unsigned int datalen); + +struct xua_msg *osmo_sccp_to_xua(struct msgb *msg); +struct msgb *osmo_sua_to_sccp(struct xua_msg *xua); + +int sua_rx_msg(struct osmo_ss7_asp *asp, struct msgb *msg); + +int sua_tx_xua_as(struct osmo_ss7_as *as, struct xua_msg *xua); + +struct osmo_mtp_prim *m3ua_to_xfer_ind(struct xua_msg *xua); +int m3ua_hmdc_rx_from_l2(struct osmo_ss7_instance *inst, struct xua_msg *xua); +int m3ua_tx_xua_as(struct osmo_ss7_as *as, struct xua_msg *xua); +int m3ua_rx_msg(struct osmo_ss7_asp *asp, struct msgb *msg); + +struct msgb *m3ua_msgb_alloc(const char *name); +struct xua_msg *m3ua_xfer_from_data(const struct m3ua_data_hdr *data_hdr, + const uint8_t *data, unsigned int data_len); +struct m3ua_data_hdr *data_hdr_from_m3ua(struct xua_msg *xua); +void m3ua_dh_to_xfer_param(struct osmo_mtp_transfer_param *param, + const struct m3ua_data_hdr *mdh); +void mtp_xfer_param_to_m3ua_dh(struct m3ua_data_hdr *mdh, + const struct osmo_mtp_transfer_param *param); + + +extern const struct xua_msg_class m3ua_msg_class_mgmt; +extern const struct xua_msg_class m3ua_msg_class_snm; +extern const struct xua_msg_class m3ua_msg_class_rkm; +extern const struct xua_msg_class m3ua_msg_class_aspsm; +extern const struct xua_msg_class m3ua_msg_class_asptm; + +extern const struct value_string m3ua_err_names[]; +extern const struct value_string m3ua_ntfy_type_names[]; +extern const struct value_string m3ua_ntfy_stchg_names[]; +extern const struct value_string m3ua_ntfy_other_names[]; + +struct xua_msg *m3ua_encode_notify(const struct osmo_xlm_prim_notify *npar); +int m3ua_decode_notify(struct osmo_xlm_prim_notify *npar, void *ctx, + const struct xua_msg *xua); +int m3ua_rx_rkm(struct osmo_ss7_asp *asp, struct xua_msg *xua); +void xua_rkm_cleanup_dyn_as_for_asp(struct osmo_ss7_asp *asp); + +struct osmo_xlm_prim *xua_xlm_prim_alloc(enum osmo_xlm_prim_type prim_type, + enum osmo_prim_operation op); + +void xua_asp_send_xlm_prim(struct osmo_ss7_asp *asp, struct osmo_xlm_prim *prim); +void xua_asp_send_xlm_prim_simple(struct osmo_ss7_asp *asp, + enum osmo_xlm_prim_type prim_type, + enum osmo_prim_operation op); + +extern struct osmo_fsm xua_default_lm_fsm; +extern const struct value_string m3ua_rkm_reg_status_vals[]; +extern const struct value_string m3ua_rkm_dereg_status_vals[]; + +#define CS7_STR "ITU-T Signaling System 7\n" +#define PC_STR "Point Code\n" +#define INST_STR "An instance of the SS7 stack\n" + +int xua_as_transmit_msg(struct osmo_ss7_as *as, struct msgb *msg); + + +int ipa_tx_xua_as(struct osmo_ss7_as *as, struct xua_msg *xua); +int ipa_rx_msg(struct osmo_ss7_asp *asp, struct msgb *msg); + +int osmo_isup_party_parse(char *out_digits, const uint8_t *in, + unsigned int in_num_bytes, bool odd); +int osmo_sccp_addr_parse(struct osmo_sccp_addr *out, + const uint8_t *addr, unsigned int addrlen); +int osmo_sccp_addr_encode(struct msgb *msg, const struct osmo_sccp_addr *in); diff --git a/src/xua_msg.c b/src/xua_msg.c new file mode 100644 index 0000000..05430a4 --- /dev/null +++ b/src/xua_msg.c @@ -0,0 +1,506 @@ +/* Routines for generating and parsing messages */ +/* (C) 2011 by Holger Hans Peter Freyther + * (C) 2016-2017 by Harald Welte + * + * 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, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +static void *tall_xua; + +struct xua_msg *xua_msg_alloc(void) +{ + struct xua_msg *msg; + + msg = talloc_zero(tall_xua, struct xua_msg); + if (!msg) + return NULL; + + INIT_LLIST_HEAD(&msg->headers); + return msg; +} + +void xua_msg_free(struct xua_msg *msg) +{ + talloc_free(msg); +} + +int xua_msg_add_data(struct xua_msg *msg, uint16_t tag, + uint16_t len, uint8_t *dat) +{ + struct xua_msg_part *part; + + part = talloc_zero(msg, struct xua_msg_part); + if (!part) + return -1; + + part->tag = tag; + part->len = len; + + /* do we have any data? */ + if (part->len != 0) { + part->dat = talloc_memdup(part, dat, len); + if (!part->dat) { + talloc_free(part); + return -1; + } + } + + llist_add_tail(&part->entry, &msg->headers); + return 0; +} + +struct xua_msg_part *xua_msg_find_tag(const struct xua_msg *xua, uint16_t tag) +{ + struct xua_msg_part *part; + + llist_for_each_entry(part, &xua->headers, entry) + if (part->tag == tag) + return part; + + return NULL; +} + +int xua_msg_free_tag(struct xua_msg *xua, uint16_t tag) +{ + struct xua_msg_part *part; + + llist_for_each_entry(part, &xua->headers, entry) { + if (part->tag == tag) { + llist_del(&part->entry); + talloc_free(part); + return 1; + } + } + return 0; +} + +int xua_msg_copy_part(struct xua_msg *xua_out, uint16_t tag_out, + const struct xua_msg *xua_in, uint16_t tag_in) +{ + const struct xua_msg_part *part; + + part = xua_msg_find_tag(xua_in, tag_in); + if (!part) + return -1; + + return xua_msg_add_data(xua_out, tag_out, part->len, part->dat); +} + +static int xua_from_msg_common(struct xua_msg *msg, const uint8_t *data, uint16_t pos, uint16_t len) +{ + struct xua_parameter_hdr *par; + uint16_t par_len, padding; + int rc; + + while (pos + sizeof(*par) < len) { + par = (struct xua_parameter_hdr *) &data[pos]; + par_len = ntohs(par->len); + + if (pos + par_len > len || par_len < 4) + return -1; + + rc = xua_msg_add_data(msg, ntohs(par->tag), + par_len - 4, par->data); + if (rc != 0) + return -1; + + pos += par_len; + + /* move over the padding */ + padding = (4 - (par_len % 4)) & 0x3; + pos += padding; + } + + return 0; +} + +struct xua_msg *xua_from_msg(const int version, uint16_t len, uint8_t *data) +{ + struct xua_common_hdr *hdr; + struct xua_msg *msg; + uint16_t pos; + int rc; + + msg = xua_msg_alloc(); + if (!msg) + return NULL; + + if (len < sizeof(*hdr)) + goto fail; + + hdr = (struct xua_common_hdr *) data; + if (hdr->version != version) + goto fail; + if (ntohl(hdr->msg_length) > len) + goto fail; + + msg->hdr = *hdr; + pos = sizeof(*hdr); + + rc = xua_from_msg_common(msg, data, pos, len); + if (rc < 0) + goto fail; + + return msg; + +fail: + xua_msg_free(msg); + return NULL; + +} + +struct xua_msg *xua_from_nested(struct xua_msg_part *outer) +{ + struct xua_msg *msg = xua_msg_alloc(); + int rc; + + if (!msg) + return NULL; + + rc = xua_from_msg_common(msg, outer->dat, 0, outer->len); + if (rc < 0) { + xua_msg_free(msg); + return NULL; + } + + return msg; +} + +struct msgb *xua_to_msg(const int version, struct xua_msg *xua) +{ + struct xua_msg_part *part; + struct xua_common_hdr *hdr; + struct msgb *msg; + uint8_t rest; + + msg = msgb_alloc_headroom(2048, 512, "xua msg"); + if (!msg) + return NULL; + + msg->l2h = msgb_put(msg, sizeof(*hdr)); + hdr = (struct xua_common_hdr *) msg->l2h; + memcpy(hdr, &xua->hdr, sizeof(*hdr)); + + /* make sure that is right */ + hdr->version = version; + hdr->spare = 0; + + llist_for_each_entry(part, &xua->headers, entry) { + msgb_put_u16(msg, part->tag); + msgb_put_u16(msg, part->len + 4); + if (part->dat) { + uint8_t *dat = msgb_put(msg, part->len); + memcpy(dat, part->dat, part->len); + + /* padding */ + rest = (4 - (part->len % 4)) & 0x3; + if (rest > 0) { + dat = msgb_put(msg, rest); + memset(dat, 0, rest); + } + } + } + + /* update the size of the data */ + hdr->msg_length = htonl(msgb_l2len(msg)); + return msg; +} + +/*********************************************************************** + * Message encoding helper functions + ***********************************************************************/ + +int msgb_t16l16vp_put(struct msgb *msg, uint16_t tag, uint16_t len, const uint8_t *data) +{ + uint8_t *cur; + unsigned int rest; + unsigned int tlv_len = 4 + len + (4 - (len % 4)); + + if (msgb_tailroom(msg) < tlv_len) + return -ENOMEM; + + /* tag */ + msgb_put_u16(msg, tag); + /* length */ + msgb_put_u16(msg, len + 4); + /* value */ + cur = msgb_put(msg, len); + memcpy(cur, data, len); + /* padding */ + rest = (4 - (len % 4)) & 0x3; + if (rest > 0) { + cur = msgb_put(msg, rest); + memset(cur, 0, rest); + } + + return 0; +} + +int msgb_t16l16vp_put_u32(struct msgb *msg, uint16_t tag, uint32_t val) +{ + uint32_t val_n = htonl(val); + + return msgb_t16l16vp_put(msg, tag, sizeof(val_n), (uint8_t *)&val_n); +} + +int xua_msg_add_u32(struct xua_msg *xua, uint16_t iei, uint32_t val) +{ + uint32_t val_n = htonl(val); + return xua_msg_add_data(xua, iei, sizeof(val_n), (uint8_t *) &val_n); +} + +uint32_t xua_msg_part_get_u32(struct xua_msg_part *part) +{ + OSMO_ASSERT(part->len >= 4); + return ntohl(*(uint32_t *)part->dat); +} + +uint32_t xua_msg_get_u32(struct xua_msg *xua, uint16_t iei) +{ + struct xua_msg_part *part = xua_msg_find_tag(xua, iei); + if (!part) + return 0; + return xua_msg_part_get_u32(part); +} + +void xua_part_add_gt(struct msgb *msg, const struct osmo_sccp_gt *gt) +{ + uint16_t *len_ptr; + unsigned int num_digits = strlen(gt->digits); + unsigned int num_digit_bytes; + unsigned int i, j; + + /* Tag + Length */ + msgb_put_u16(msg, SUA_IEI_GT); + len_ptr = (uint16_t *) msgb_put(msg, sizeof(uint16_t)); + + /* first dword: padding + GT */ + msgb_put_u32(msg, gt->gti); + + /* second header dword */ + msgb_put_u8(msg, strlen(gt->digits)); + msgb_put_u8(msg, gt->tt); + msgb_put_u8(msg, gt->npi); + msgb_put_u8(msg, gt->nai); + + /* actual digits */ + num_digit_bytes = num_digits / 2; + if (num_digits & 1) + num_digit_bytes++; + for (i = 0, j = 0; i < num_digit_bytes; i++) { + uint8_t byte; + byte = osmo_char2bcd(gt->digits[j++]); + if (j < num_digits) { + byte |= osmo_char2bcd(gt->digits[j++]) << 4; + } + msgb_put_u8(msg, byte); + } + /* pad to 32bit */ + if (num_digit_bytes % 4) + msgb_put(msg, 4 - (num_digit_bytes % 4)); + *len_ptr = htons(msg->tail - (uint8_t *)len_ptr + 2); +} + +int xua_msg_add_sccp_addr(struct xua_msg *xua, uint16_t iei, const struct osmo_sccp_addr *addr) +{ + struct msgb *tmp = msgb_alloc(128, "SCCP Address"); + uint16_t addr_ind = 0; + int rc; + + if (!tmp) + return -ENOMEM; + + switch (addr->ri) { + case OSMO_SCCP_RI_GT: + msgb_put_u16(tmp, SUA_RI_GT); + break; + case OSMO_SCCP_RI_SSN_PC: + msgb_put_u16(tmp, SUA_RI_SSN_PC); + break; + case OSMO_SCCP_RI_SSN_IP: + msgb_put_u16(tmp, SUA_RI_SSN_IP); + break; + default: + return -EINVAL; + } + if (addr->presence & OSMO_SCCP_ADDR_T_SSN) + addr_ind |= 0x0001; + if (addr->presence & OSMO_SCCP_ADDR_T_PC) + addr_ind |= 0x0002; + if (addr->presence & OSMO_SCCP_ADDR_T_GT) + addr_ind |= 0x0004; + + msgb_put_u16(tmp, addr_ind); + + if (addr->presence & OSMO_SCCP_ADDR_T_GT) { + xua_part_add_gt(tmp, &addr->gt); + } + if (addr->presence & OSMO_SCCP_ADDR_T_PC) { + msgb_t16l16vp_put_u32(tmp, SUA_IEI_PC, addr->pc); + } + if (addr->presence & OSMO_SCCP_ADDR_T_SSN) { + msgb_t16l16vp_put_u32(tmp, SUA_IEI_SSN, addr->ssn); + } + if (addr->presence & OSMO_SCCP_ADDR_T_IPv4) { + msgb_t16l16vp_put_u32(tmp, SUA_IEI_IPv4, ntohl(addr->ip.v4.s_addr)); + } else if (addr->presence & OSMO_SCCP_ADDR_T_IPv6) { + /* FIXME: IPv6 address */ + } + rc = xua_msg_add_data(xua, iei, msgb_length(tmp), tmp->data); + msgb_free(tmp); + + return rc; +} + +/*! \brief Map from a xua_msg (class+type) to an event + * \param[in] xua xUA message which is to be mapped + * \param[in] maps Table containing msg type+class -> event maps + * \[aram[in] num_maps number of entries in \ref maps + * \returns event >= 0; negative on error (no map found) */ +int xua_msg_event_map(const struct xua_msg *xua, + const struct xua_msg_event_map *maps, + unsigned int num_maps) +{ + int i; + + for (i= 0; i < num_maps; i++) { + const struct xua_msg_event_map *map = &maps[i]; + if (xua->hdr.msg_class == map->msg_class && + xua->hdr.msg_type == map->msg_type) { + return map->event; + } + } + return -1; +} + +const char *xua_class_msg_name(const struct xua_msg_class *xmc, uint16_t msg_type) +{ + static char class_buf[64]; + + if (xmc && xmc->msgt_names) + return get_value_string(xmc->msgt_names, msg_type); + else { + snprintf(class_buf, sizeof(class_buf), "Unknown 0x%04x", msg_type); + return class_buf; + } +} + +const char *xua_class_iei_name(const struct xua_msg_class *xmc, uint16_t iei) +{ + static char iei_buf[64]; + + if (xmc && xmc->iei_names) + return get_value_string(xmc->iei_names, iei); + else { + snprintf(iei_buf, sizeof(iei_buf), "Unknown 0x%04x", iei); + return iei_buf; + } +} + +char *xua_hdr_dump(struct xua_msg *xua, const struct xua_dialect *dialect) +{ + const struct xua_msg_class *xmc = NULL; + static char buf[128]; + + if (dialect) + xmc = dialect->class[xua->hdr.msg_class]; + if (!xmc) + snprintf(buf, sizeof(buf), "%u:%u", xua->hdr.msg_class, xua->hdr.msg_type); + else + snprintf(buf, sizeof(buf), "%s:%s", xmc->name, + xua_class_msg_name(xmc, xua->hdr.msg_type)); + return buf; +} + +int xua_dialect_check_all_mand_ies(const struct xua_dialect *dialect, struct xua_msg *xua) +{ + uint8_t msg_class = xua->hdr.msg_class; + uint8_t msg_type = xua->hdr.msg_type; + const struct xua_msg_class *xmc = dialect->class[msg_class]; + const uint16_t *ies; + uint16_t ie; + + /* unknown class? */ + if (!xmc) + return 1; + + ies = xmc->mand_ies[msg_type]; + /* no mandatory IEs? */ + if (!ies) + return 1; + + for (ie = *ies; ie; ie = *ies++) { + if (!xua_msg_find_tag(xua, ie)) { + LOGP(dialect->log_subsys, LOGL_ERROR, + "%s Message %s:%s should " + "contain IE %s, but doesn't\n", + dialect->name, xmc->name, + xua_class_msg_name(xmc, msg_type), + xua_class_iei_name(xmc, ie)); + return 0; + } + } + + return 1; +} + +static void append_to_buf(char *buf, bool *comma, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (!comma || *comma == true) { + strcat(buf, ","); + } else if (comma) + *comma = true; + vsprintf(buf+strlen(buf), fmt, ap); + va_end(ap); +} + +char *xua_msg_dump(struct xua_msg *xua, const struct xua_dialect *dialect) +{ + static char buf[1024]; + struct xua_msg_part *part; + const struct xua_msg_class *xmc = NULL; + + if (dialect) + xmc = dialect->class[xua->hdr.msg_class]; + + buf[0] = '\0'; + + append_to_buf(buf, NULL, "HDR=(%s,V=%u,LEN=%u)", + xua_hdr_dump(xua, dialect), + xua->hdr.version, xua->hdr.msg_length); + buf[0] = ' '; + llist_for_each_entry(part, &xua->headers, entry) + append_to_buf(buf, NULL, "\n\tPART(T=%s,L=%u,D=%s)", + xua_class_iei_name(xmc, part->tag), part->len, + osmo_hexdump_nospc(part->dat, part->len)); + return buf; +} diff --git a/src/xua_rkm.c b/src/xua_rkm.c new file mode 100644 index 0000000..d2971bc --- /dev/null +++ b/src/xua_rkm.c @@ -0,0 +1,564 @@ +/* xUA Routing Key Management (RKM) as per RFC 4666 */ +/* (C) 2017 by Harald Welte + * 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 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, see . + */ + +#include +#include + +#include +#include + +#include +#include +#include + +#include "xua_internal.h" +#include "xua_as_fsm.h" + +const struct value_string m3ua_rkm_reg_status_vals[] = { + { M3UA_RKM_REG_SUCCESS, "SUCCESS" }, + { M3UA_RKM_REG_ERR_UNKNOWN, "Unknown Error" }, + { M3UA_RKM_REG_ERR_INVAL_DPC, "Invalid Destination Pointcode" }, + { M3UA_RKM_REG_ERR_INVAL_NET_APPEAR, "Invalid Network Appearance" }, + { M3UA_RKM_REG_ERR_INVAL_RKEY, "Invalid Routing Key" }, + { M3UA_RKM_REG_ERR_PERM_DENIED, "Permission Denied" }, + { M3UA_RKM_REG_ERR_CANT_SUPP_UNQ_RT, "Cannot Support Unique Routing" }, + { M3UA_RKM_REG_ERR_RKEY_NOT_PROVD, "Routing Key Not Provided" }, + { M3UA_RKM_REG_ERR_INSUFF_RESRC, "Insufficient Resources" }, + { M3UA_RKM_REG_ERR_UNSUPP_RK_PARAM, "Unsupported Routing Key Parameter" }, + { M3UA_RKM_REG_ERR_UNSUPP_TRAF_MODE, "Unsupported Traffic Mode Type" }, + { M3UA_RKM_REG_ERR_RKEY_CHG_REFUSED, "Routing Key Change Refused" }, + { M3UA_RKM_REG_ERR_RKEY_ALRDY_REGD, "Routing Key Already Registered" }, + { 0, NULL } +}; + +const struct value_string m3ua_rkm_dereg_status_vals[] = { + { M3UA_RKM_DEREG_SUCCESS, "SUCCSS" }, + { M3UA_RKM_DEREG_ERR_UNKNOWN, "Unknown Error" }, + { M3UA_RKM_DEREG_ERR_INVAL_RCTX, "Invalid Routing Context" }, + { M3UA_RKM_DEREG_ERR_PERM_DENIED, "Permission Denied" }, + { M3UA_RKM_DEREG_ERR_NOT_REGD, "Error: Not Registered" }, + { M3UA_RKM_DEREG_ERR_ASP_ACTIVE, "Error: ASP Active" }, + { 0, NULL } +}; + +/* push a M3UA header to the front of the given message */ +static void msgb_push_m3ua_hdr(struct msgb *msg, uint8_t msg_class, uint8_t msg_type) +{ + struct xua_common_hdr *hdr; + + msg->l2h = msgb_push(msg, sizeof(*hdr)); + hdr = (struct xua_common_hdr *) msg->l2h; + + hdr->version = M3UA_VERSION; + hdr->spare = 0; + hdr->msg_class = msg_class; + hdr->msg_type = msg_type; + hdr->msg_length = htonl(msgb_l2len(msg)); +} + +/* SG: append a single registration result to given msgb */ +static int msgb_append_reg_res(struct msgb *msg, uint32_t local_rk_id, + uint32_t status, uint32_t rctx) +{ + uint8_t *old_tail = msg->tail; + + /* One individual Registration Result according to Chapter 3.6.2 */ + msgb_put_u16(msg, M3UA_IEI_REG_RESULT); /* outer IEI */ + msgb_put_u16(msg, 24 + 4); /* outer length */ + /* nested IEIs */ + msgb_t16l16vp_put_u32(msg, M3UA_IEI_LOC_RKEY_ID, local_rk_id); + msgb_t16l16vp_put_u32(msg, M3UA_IEI_REG_STATUS, status); + msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, rctx); + + return msg->tail - old_tail; +} + +/* SG: append a single de-registration result to given msgb */ +static int msgb_append_dereg_res(struct msgb *msg, + uint32_t status, uint32_t rctx) +{ + uint8_t *old_tail = msg->tail; + + /* One individual De-Registration Result according to Chapter 3.6.4 */ + msgb_put_u16(msg, M3UA_IEI_DEREG_RESULT); /* outer IEI */ + msgb_put_u16(msg, 16 + 4); /* outer length */ + /* nested IEIs */ + msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, rctx); + msgb_t16l16vp_put_u32(msg, M3UA_IEI_DEREG_STATUS, status); + + return msg->tail - old_tail; +} + +/* ASP: send a RKM Registration Request message for a single routing key */ +static void xua_rkm_send_reg_req(struct osmo_ss7_asp *asp, + const struct osmo_ss7_routing_key *rkey, + enum osmo_ss7_as_traffic_mode traf_mode) +{ + struct msgb *msg = m3ua_msgb_alloc(__func__); + int tmod = osmo_ss7_tmode_to_xua(traf_mode); + + /* One individual Registration Request according to Chapter 3.6.1 */ + msgb_put_u16(msg, M3UA_IEI_ROUT_KEY); /* outer IEI */ + msgb_put_u16(msg, 32 + 4); /* outer length */ + /* nested IEIs */ + msgb_t16l16vp_put_u32(msg, M3UA_IEI_LOC_RKEY_ID, rkey->l_rk_id); + msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, rkey->context); + msgb_t16l16vp_put_u32(msg, M3UA_IEI_TRAF_MODE_TYP, tmod); + msgb_t16l16vp_put_u32(msg, M3UA_IEI_DEST_PC, rkey->pc); + + msgb_push_m3ua_hdr(msg, M3UA_MSGC_RKM, M3UA_RKM_REG_REQ); + + osmo_ss7_asp_send(asp, msg); +} + +/* ASP: send a RKM De-Registration Request message for a single routing context */ +static void xua_rkm_send_dereg_req(struct osmo_ss7_asp *asp, uint32_t route_ctx) +{ + struct msgb *msg = m3ua_msgb_alloc(__func__); + + /* One individual De-Registration Request according to Chapter 3.6.3 */ + msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, route_ctx); + + msgb_push_m3ua_hdr(msg, M3UA_MSGC_RKM, M3UA_RKM_DEREG_REQ); + + osmo_ss7_asp_send(asp, msg); +} + +/* maximum number of newly-assigned Application Servers in one dynamic + * RKM REG request */ +#define MAX_NEW_AS 16 + +/* SG: handle a single registration request IE (nested IEs in 'innner' */ +static int handle_rkey_reg(struct osmo_ss7_asp *asp, struct xua_msg *inner, + struct msgb *resp, struct osmo_ss7_as **newly_assigned_as, + unsigned int max_nas_idx, unsigned int *nas_idx) +{ + uint32_t rk_id, rctx, _tmode, dpc; + enum osmo_ss7_as_traffic_mode tmode; + struct osmo_ss7_as *as; + struct osmo_ss7_route *rt; + char namebuf[32]; + + /* mandatory local routing key ID */ + rk_id = xua_msg_get_u32(inner, M3UA_IEI_LOC_RKEY_ID); + /* ASP may already include a routing context value here */ + rctx = xua_msg_get_u32(inner, M3UA_IEI_ROUTE_CTX); + + /* traffic mode type (0 = undefined) */ + _tmode = xua_msg_get_u32(inner, M3UA_IEI_TRAF_MODE_TYP); + if (xua_msg_find_tag(inner, M3UA_IEI_TRAF_MODE_TYP) && _tmode != M3UA_TMOD_OVERRIDE && + _tmode != M3UA_TMOD_LOADSHARE && _tmode != M3UA_TMOD_BCAST) { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Invalid Traffic Mode %u\n", _tmode); + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_UNSUPP_TRAF_MODE, 0); + return -1; + } + + tmode = osmo_ss7_tmode_from_xua(_tmode); + + /* destination point code (mandatory) */ + dpc = xua_msg_get_u32(inner, M3UA_IEI_DEST_PC); + + /* We don't support routing keys with the following criteria, so + * we have to reject those */ + /* TODO: network appearance (optional) */ + /* TODO: service indicators (optional) */ + /* TODO: originating point code list (optional) */ + if (xua_msg_find_tag(inner, M3UA_IEI_NET_APPEAR) || + xua_msg_find_tag(inner, M3UA_IEI_SVC_IND) || + xua_msg_find_tag(inner, M3UA_IEI_ORIG_PC)) { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Unsupported Routing Key\n"); + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_UNSUPP_RK_PARAM, 0); + return -1; + } + + /* if the ASP did not include a routing context number, allocate + * one locally (will be part of response) */ + if (!rctx) + rctx = osmo_ss7_find_free_rctx(asp->inst); + + LOGPASP(asp, DLSS7, LOGL_INFO, "RKM: Registering routing key %u for DPC %s\n", + rctx, osmo_ss7_pointcode_print(asp->inst, dpc)); + + /* We have two cases here: + * a) pre-configured routing context on both ASP and SG: We will + * find the AS based on the RCTX send by the client, check if + * the routing key matches, associated AS with ASP and return + * success. + * b) no routing context set on ASP, no pre-existing AS + * definition on SG. We have to create the AS, set the RK, + * allocate the RCTX and return that RCTX to the client. This + * is a slightly non-standard interpretation of M3UA RKM + * which requires the SG to not have a-priori-knowledge of + * all AS/RK in situations where the ASP are trusted. + */ + + /* check if there is already an AS for this routing key */ + as = osmo_ss7_as_find_by_rctx(asp->inst, rctx); + if (as) { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Found existing AS for RCTX %u\n", rctx); + if (as->cfg.routing_key.pc != dpc) { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: DPC doesn't match (%u != %u)\n", + as->cfg.routing_key.pc, dpc); + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_INVAL_RKEY, 0); + return -1; + } + } else if (asp->inst->cfg.permit_dyn_rkm_alloc) { + /* Create an AS for this routing key */ + snprintf(namebuf, sizeof(namebuf), "as-rkm-%u", rctx); + as = osmo_ss7_as_find_or_create(asp->inst, namebuf, OSMO_SS7_ASP_PROT_M3UA); + if (!as) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "RKM: Cannot create AS %s\n", namebuf); + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_INSUFF_RESRC, 0); + return -1; + } + + as->cfg.description = talloc_strdup(as, "Auto-generated by RKM"); + as->rkm_dyn_allocated = true; + as->cfg.mode = tmode; + /* fill routing key */ + as->cfg.routing_key.pc = dpc; + as->cfg.routing_key.context = rctx; + + /* add route for that routing key */ + rt = osmo_ss7_route_create(as->inst->rtable_system, dpc, 0xFFFFFF, namebuf); + if (!rt) { + LOGPASP(asp, DLSS7, LOGL_ERROR, "RKM: Cannot insert route for DPC %s / as %s\n", + osmo_ss7_pointcode_print(asp->inst, dpc), namebuf); + osmo_ss7_as_destroy(as); + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_CANT_SUPP_UNQ_RT, 0); + return -1; + } + + /* append to list of newly assigned as */ + if (*nas_idx >= max_nas_idx) { + osmo_ss7_route_destroy(rt); + osmo_ss7_as_destroy(as); + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_INSUFF_RESRC, 0); + return -1; + } + newly_assigned_as[(*nas_idx)++] = as; + } else { + /* not permitted to create dynamic RKM entries */ + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_PERM_DENIED, 0); + return -1; + } + + /* Success: Add just-create AS to connected ASP + report success */ + osmo_ss7_as_add_asp(as, asp->cfg.name); + msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_SUCCESS, rctx); + return 0; +} + +/* SG: receive a registration request from ASP */ +static int m3ua_rx_rkm_reg_req(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct xua_msg_part *part; + struct msgb *resp = m3ua_msgb_alloc(__func__); + struct osmo_ss7_as *newly_assigned_as[MAX_NEW_AS]; + unsigned int i, nas_idx = 0; + + memset(newly_assigned_as, 0, sizeof(newly_assigned_as)); + + /* iterate over all routing key IEs in message */ + llist_for_each_entry(part, &xua->headers, entry) { + struct xua_msg *inner; + + if (part->tag != M3UA_IEI_ROUT_KEY) + continue; + + inner = xua_from_nested(part); + if (!inner) { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Unable to parse " + "nested IE for Routing Key\n"); + continue; + } + /* handle single registration and append result to + * 'resp' */ + handle_rkey_reg(asp, inner, resp, newly_assigned_as, + ARRAY_SIZE(newly_assigned_as), &nas_idx); + + xua_msg_free(inner); + } + /* now first send the RKM REG Response */ + msgb_push_m3ua_hdr(resp, M3UA_MSGC_RKM, M3UA_RKM_REG_RSP); + osmo_ss7_asp_send(asp, resp); + + /* and *after* the RKM REG Response inform the newly assigned + * ASs about the fact that there's an INACTIVE ASP for them, + * which will cause them to send NOTIFY to the client */ + for (i = 0; i < ARRAY_SIZE(newly_assigned_as); i++) { + struct osmo_ss7_as *as = newly_assigned_as[i]; + if (!as) + continue; + /* Notify AS that it has an INACTIVE ASP */ + osmo_fsm_inst_dispatch(as->fi, XUA_ASPAS_ASP_INACTIVE_IND, asp); + } + + return 0; +} + +/* SG: handle a single routing key de-registration IE */ +static int handle_rkey_dereg(struct osmo_ss7_asp *asp, uint32_t rctx, + struct msgb *resp) +{ + struct osmo_ss7_instance *inst = asp->inst; + struct osmo_ss7_as *as; + struct osmo_ss7_route *rt; + + as = osmo_ss7_as_find_by_rctx(inst, rctx); + if (!as) { + msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_INVAL_RCTX, 0); + return -1; + } + + /* Reject if ASP is not even part of AS */ + if (!osmo_ss7_as_has_asp(as, asp)) { + msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_INVAL_RCTX, 0); + return -1; + } + + /* FIXME Reject if any ASP stillactively using this RCTX */ + + rt = osmo_ss7_route_find_dpc(inst->rtable_system, as->cfg.routing_key.pc); + if (!rt) { + msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_UNKNOWN, 0); + return -1; + } + + LOGPASP(asp, DLSS7, LOGL_INFO, "RKM: De-Registering rctx %u for DPC %s\n", + rctx, osmo_ss7_pointcode_print(inst, as->cfg.routing_key.pc)); + + /* remove ASP from AS */ + osmo_ss7_as_del_asp(as, asp->cfg.name); + /* FIXME: Rather than spoofing teh ASP-DOWN.ind to the AS here, + * we should refuse RKM DEREG if the ASP is still ACTIVE */ + osmo_fsm_inst_dispatch(as->fi, XUA_ASPAS_ASP_DOWN_IND, asp); + + /* if we were dynamically allocated, release the associated + * route and destroy the AS */ + if (as->rkm_dyn_allocated) { + /* remove route + AS definition */ + osmo_ss7_route_destroy(rt); + osmo_ss7_as_destroy(as); + } + /* report success */ + msgb_append_dereg_res(resp, M3UA_RKM_DEREG_SUCCESS, rctx); + + return 0; +} + +/* SG: receive a De-Registration request from ASP */ +static int m3ua_rx_rkm_dereg_req(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct xua_msg_part *part = xua_msg_find_tag(xua, M3UA_IEI_ROUTE_CTX); + struct msgb *resp = m3ua_msgb_alloc(__func__); + uint32_t *rctx; + + if (!part) + return -1; + + for (rctx = (uint32_t *)part->dat; (uint8_t *)rctx < part->dat + part->len; rctx++) + handle_rkey_dereg(asp, ntohl(*rctx), resp); + + msgb_push_m3ua_hdr(resp, M3UA_MSGC_RKM, M3UA_RKM_DEREG_RSP); + osmo_ss7_asp_send(asp, resp); + + return 0; +} + +/* ASP: handle a single registration response IE (nested IEs in 'inner') */ +static int handle_rkey_reg_resp(struct osmo_ss7_asp *asp, struct xua_msg *inner) +{ + struct osmo_xlm_prim *oxp; + + if (!xua_msg_find_tag(inner, M3UA_IEI_LOC_RKEY_ID) || + !xua_msg_find_tag(inner, M3UA_IEI_REG_STATUS) || + !xua_msg_find_tag(inner, M3UA_IEI_ROUTE_CTX)) { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "Missing Inner IE in REG RESP\n"); + /* FIXME: ERROR to peer */ + return -1; + } + + oxp = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_CONFIRM); + if (!oxp) + return -1; + + oxp->u.rk_reg.key.l_rk_id = xua_msg_get_u32(inner, M3UA_IEI_LOC_RKEY_ID); + oxp->u.rk_reg.key.context = xua_msg_get_u32(inner, M3UA_IEI_ROUTE_CTX); + oxp->u.rk_reg.status = xua_msg_get_u32(inner, M3UA_IEI_REG_STATUS); + + LOGPASP(asp, DLSS7, LOGL_INFO, "Received RKM REG RES rctx=%u status=%s\n", + oxp->u.rk_reg.key.context, + get_value_string(m3ua_rkm_reg_status_vals, oxp->u.rk_reg.status)); + + /* Send primitive to LM */ + xua_asp_send_xlm_prim(asp, oxp); + + return 0; +} + +/* ASP: receive a registration response (ASP role) */ +static int m3ua_rx_rkm_reg_rsp(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct xua_msg_part *part; + struct xua_msg *inner = NULL; + + llist_for_each_entry(part, &xua->headers, entry) { + /* skip other IEs and/or short REG_RES IEs */ + if (part->tag != M3UA_IEI_REG_RESULT || part->len < 24) + continue; + + /* we leave the above loop at the first valid + * registration result (we only support one AS per ASP + * for now) */ + inner = xua_from_nested(part); + if (!inner) + continue; + + handle_rkey_reg_resp(asp, inner); + } + return 0; +} + +/* ASP: handle a single De-Registration response IE (nested IEs in 'inner' */ +static int handle_rkey_dereg_resp(struct osmo_ss7_asp *asp, struct xua_msg *inner) +{ + struct osmo_xlm_prim *oxp; + + if (!xua_msg_find_tag(inner, M3UA_IEI_DEREG_STATUS) || + !xua_msg_find_tag(inner, M3UA_IEI_ROUTE_CTX)) { + LOGPASP(asp, DLSS7, LOGL_NOTICE, "Missing Inner IE in DEREG RESP\n"); + /* FIXME: ERROR to peer */ + return -1; + } + + oxp = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_RK_DEREG, PRIM_OP_CONFIRM); + if (!oxp) + return -1; + + oxp->u.rk_dereg.route_ctx = xua_msg_get_u32(inner, M3UA_IEI_ROUTE_CTX); + oxp->u.rk_dereg.status = xua_msg_get_u32(inner, M3UA_IEI_DEREG_STATUS); + + LOGPASP(asp, DLSS7, LOGL_INFO, "Received RKM DEREG RES rctx=%u status=%s\n", + oxp->u.rk_reg.key.context, + get_value_string(m3ua_rkm_dereg_status_vals, oxp->u.rk_dereg.status)); + + /* Send primitive to LM */ + xua_asp_send_xlm_prim(asp, oxp); + + return 0; +} + +/* ASP: receive a De-Registration response */ +static int m3ua_rx_rkm_dereg_rsp(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + struct xua_msg_part *part; + struct xua_msg *inner = NULL; + + llist_for_each_entry(part, &xua->headers, entry) { + /* skip other IEs and/or short REG_RES IEs */ + if (part->tag != M3UA_IEI_DEREG_RESULT || part->len < 16) + continue; + + /* we leave the above loop at the first valid + * registration result (we only support one AS per ASP + * for now) */ + inner = xua_from_nested(part); + if (!inner) + continue; + + handle_rkey_dereg_resp(asp, inner); + } + return 0; +} + +/* process an incoming RKM message in xua format */ +int m3ua_rx_rkm(struct osmo_ss7_asp *asp, struct xua_msg *xua) +{ + int rc; + + switch (xua->hdr.msg_type) { + /* SG Side */ + case M3UA_RKM_REG_REQ: + /* TOOD: ensure we are role SG */ + rc = m3ua_rx_rkm_reg_req(asp, xua); + break; + case M3UA_RKM_DEREG_REQ: + /* TOOD: ensure we are role SG */ + rc = m3ua_rx_rkm_dereg_req(asp, xua); + break; + /* ASP Side */ + case M3UA_RKM_REG_RSP: + /* TOOD: ensure we are role ASP */ + rc = m3ua_rx_rkm_reg_rsp(asp, xua); + break; + case M3UA_RKM_DEREG_RSP: + /* TOOD: ensure we are role ASP */ + rc = m3ua_rx_rkm_dereg_rsp(asp, xua); + break; + default: + LOGPASP(asp, DLSS7, LOGL_ERROR, "Received unknown RKM msg_type %u\n", + xua->hdr.msg_type); + rc = -1; + break; + } + return rc; +} + +/* process a primitive from the xUA Layer Manager (LM) */ +int osmo_xlm_sap_down(struct osmo_ss7_asp *asp, struct osmo_prim_hdr *oph) +{ + struct osmo_xlm_prim *prim = (struct osmo_xlm_prim *) oph; + + LOGPASP(asp, DLSS7, LOGL_DEBUG, "Received XUA Layer Manager Primitive: %s)\n", + osmo_xlm_prim_name(&prim->oph)); + + switch (OSMO_PRIM_HDR(&prim->oph)) { + case OSMO_PRIM(OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_REQUEST): + /* Layer Manager asks us to send a Routing Key Reg Request */ + xua_rkm_send_reg_req(asp, &prim->u.rk_reg.key, prim->u.rk_reg.traf_mode); + break; + case OSMO_PRIM(OSMO_XLM_PRIM_M_RK_DEREG, PRIM_OP_REQUEST): + /* Layer Manager asks us to send a Routing Key De-Reg Request */ + xua_rkm_send_dereg_req(asp, prim->u.rk_dereg.route_ctx); + break; + default: + LOGPASP(asp, DLSS7, LOGL_ERROR, "Unknown XUA Layer Manager Primitive: %s\n", + osmo_xlm_prim_name(&prim->oph)); + break; + } + + return 0; +} + +/* clean-up any dynamically created ASs + routes */ +void xua_rkm_cleanup_dyn_as_for_asp(struct osmo_ss7_asp *asp) +{ + struct osmo_ss7_instance *inst = asp->inst; + struct osmo_ss7_as *as, *as2; + + llist_for_each_entry_safe(as, as2, &inst->as_list, list) { + if (!osmo_ss7_as_has_asp(as, asp)) + continue; + /* FIXME: check if there are no other ASPs! */ + if (!as->rkm_dyn_allocated) + continue; + + osmo_ss7_as_destroy(as); + } +} diff --git a/stp/Makefile.am b/stp/Makefile.am new file mode 100644 index 0000000..ae37487 --- /dev/null +++ b/stp/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMONETIF_CFLAGS) $(COVERAGE_FLAGS) +AM_LDFLAGS=$(COVERAGE_LDFLAGS) + +bin_PROGRAMS = osmo-stp + +osmo_stp_SOURCES = stp_main.c +osmo_stp_LDADD = $(top_builddir)/src/libosmo-sigtran.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) diff --git a/stp/stp_main.c b/stp/stp_main.c new file mode 100644 index 0000000..a33045a --- /dev/null +++ b/stp/stp_main.c @@ -0,0 +1,212 @@ +/* Osmocom STP (Signal Transfer Point) */ + +/* (C) 2015-2017 by Harald Welte + * 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 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, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static void *tall_stp_ctx; + +/* we only use logging sub-systems of the various libraries so far */ +static const struct log_info_cat log_info_cat[] = { +}; + +static const struct log_info log_info = { + .cat = log_info_cat, + .num_cat = ARRAY_SIZE(log_info_cat), +}; + +static const char stp_copyright[] = + "Copyright (C) 2015-2017 by Harald Welte \r\n" + "Contributions by Holger Freyther, Neels Hofmeyr\r\n" + "License GPLv2+: GNU GPL Version 2 or later \r\n" + "This is free software: you are free ot change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n\r\n" + "Free Software lives by contribution. If you use this, please contribute!\r\n"; + +static struct vty_app_info vty_info = { + .name = "osmo-stp", + .copyright = stp_copyright, + .version = PACKAGE_VERSION, + .go_parent_cb = osmo_ss7_vty_go_parent, + .is_config_node = osmo_ss7_is_config_node, +}; + +static struct { + bool daemonize; + const char *config_file; +} cmdline_config = { + .daemonize = false, + .config_file = "osmo-stp.cfg", +}; + +static void print_help(void) +{ + printf(" -h --help This text.\n"); + printf(" -D --daemonize Fork teh process into a background daemon\n"); + printf(" -c --config-file filename The config file to use. Default: ./osmo-stp.cfg\n"); + printf(" -V --version Print the version of OsmoSTP\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static const struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "daemonize", 0, 0, 'D' }, + { "config-file", 1, 0, 'c' }, + { "version", 0, 0, 'V' }, + { NULL, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hDc:V", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + break; + case 'D': + cmdline_config.daemonize = true; + break; + case 'c': + cmdline_config.config_file = optarg; + break; + case 'V': + print_version(1); + exit(0); + break; + default: + fprintf(stderr, "Error in command line options. Exiting\n"); + exit(1); + break; + } + } +} + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + /* FIXME: handle the signal somewhere else and gracefully shut down + * SIGTRAN links + osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); + sleep(1); */ + exit(0); + break; + case SIGABRT: + osmo_generate_backtrace(); + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_stp_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + int rc; + + tall_stp_ctx = talloc_named_const(NULL, 1, "osmo-stp"); + + osmo_init_logging(&log_info); + vty_init(&vty_info); + + handle_options(argc, argv); + + fputs(stp_copyright, stdout); + fputs("\n", stdout); + + osmo_ss7_init(); + osmo_fsm_log_addr(false); + logging_vty_add_cmds(&log_info); + osmo_ss7_vty_init_sg(tall_stp_ctx); + osmo_fsm_vty_add_cmds(); + + rc = vty_read_config_file(cmdline_config.config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file '%s'\n", + cmdline_config.config_file); + exit(1); + } + + rc = telnet_init_dynif(NULL, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_STP); + if (rc < 0) { + perror("Erro binding VTY port\n"); + exit(1); + } + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (cmdline_config.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + rc = osmo_select_main(0); + if (rc < 0) + exit(3); + } +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..70e8a00 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,40 @@ +SUBDIRS = xua sccp mtp m2ua ss7 + +# 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) +TESTSUITE = $(srcdir)/testsuite +DISTCLEANFILES = atconfig + +check-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) + +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/m2ua/Makefile.am b/tests/m2ua/Makefile.am new file mode 100644 index 0000000..33618ef --- /dev/null +++ b/tests/m2ua/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Wall +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) + +EXTRA_DIST = m2ua_test.ok + +noinst_PROGRAMS = m2ua_test +m2ua_test_SOURCES = m2ua_test.c +m2ua_test_LDADD = $(top_builddir)/src/libxua.a $(LIBOSMOCORE_LIBS) diff --git a/tests/m2ua/m2ua_test.c b/tests/m2ua/m2ua_test.c new file mode 100644 index 0000000..12d5d7c --- /dev/null +++ b/tests/m2ua/m2ua_test.c @@ -0,0 +1,116 @@ +/* (C) 2011 by Holger Hans Peter Freyther + * + * 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 . + * + */ + +#include +#include + +#include +#include + +#include +#include +#include + +#define FAIL(msg) \ + do { \ + fprintf(stderr, "FAILURE: %s on line %d\n", msg, __LINE__); \ + abort(); \ + } while(0); + +static uint8_t asp_up[] = { + 0x01, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x11, 0x00, 0x08, 0xac, 0x10, 0x01, 0x51, +}; + +static uint8_t data[] = { + 0x01, 0x00, 0x06, 0x01, 0x00, 0x00, 0x00, 0x2c, + 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x1a, 0x81, 0x5c, 0x00, 0x07, + 0x00, 0x11, 0xf0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0x00, 0x00 +}; + +static void test_asp_up(void) +{ + struct xua_msg_part *part; + struct xua_msg *m2u = xua_from_msg(M2UA_VERSION, ARRAY_SIZE(asp_up), asp_up); + struct msgb *msg = xua_to_msg(M2UA_VERSION, m2u); + const uint8_t res[] = { 0xac, 0x10, 0x01, 0x51 }; + + printf("Testing ASP UP parsing.\n"); + + if (msg->len != ARRAY_SIZE(asp_up)) { + printf("Got %d wanted %zu\n", msg->len, ARRAY_SIZE(asp_up)); + FAIL("Wrong size"); + } + + if (memcmp(msg->data, asp_up, msg->len) != 0) { + printf("Got '%s'\n", osmo_hexdump(msg->data, msg->len)); + FAIL("Wrong memory"); + } + + part = xua_msg_find_tag(m2u, 0x11); + if (!part) + FAIL("Could not find part"); + if (part->len != 4) + FAIL("Part is not of length four\n"); + if (memcmp(part->dat, res, 4) != 0) + FAIL("Wrong result for the tag\n"); + + xua_msg_free(m2u); + msgb_free(msg); +} + +static void test_data(void) +{ + struct xua_msg_part *part; + struct xua_msg *m2u = xua_from_msg(M2UA_VERSION, ARRAY_SIZE(data), data); + struct msgb *msg = xua_to_msg(M2UA_VERSION, m2u); + + printf("Testing parsing of data.\n"); + + if (msg->len != ARRAY_SIZE(data)) { + printf("Got %d wanted %zu\n", msg->len, ARRAY_SIZE(data)); + FAIL("Wrong size"); + } + + if (memcmp(msg->data, data, msg->len) != 0) { + printf("Got '%s'\n", osmo_hexdump(msg->data, msg->len)); + FAIL("Wrong memory"); + } + + part = xua_msg_find_tag(m2u, 0x300); + if (!part) + FAIL("Could not find part"); + if (part->len != 22) { + printf("Got the length %d\n", part->len); + FAIL("Part is not of length 22\n"); + } + + xua_msg_free(m2u); + msgb_free(msg); +} + +int main(int argc, char **argv) +{ + test_asp_up(); + test_data(); + + printf("All tests passed.\n"); + return 0; +} diff --git a/tests/m2ua/m2ua_test.ok b/tests/m2ua/m2ua_test.ok new file mode 100644 index 0000000..ab74b86 --- /dev/null +++ b/tests/m2ua/m2ua_test.ok @@ -0,0 +1,3 @@ +Testing ASP UP parsing. +Testing parsing of data. +All tests passed. diff --git a/tests/mtp/Makefile.am b/tests/mtp/Makefile.am new file mode 100644 index 0000000..21cc2c0 --- /dev/null +++ b/tests/mtp/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(LIBOSMOCORE_CFLAGS) -Wall +noinst_PROGRAMS = mtp_parse_test + +EXTRA_DIST = mtp_parse_test.ok + +mtp_parse_test_SOURCES = mtp_parse_test.c diff --git a/tests/mtp/mtp_parse_test.c b/tests/mtp/mtp_parse_test.c new file mode 100644 index 0000000..75650a3 --- /dev/null +++ b/tests/mtp/mtp_parse_test.c @@ -0,0 +1,643 @@ +/* MTP Layer3 parsing tests */ +#include + +#include + +#include +#include +#include + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +struct mtp_test { + const uint8_t *input; + const uint16_t length; + struct mtp_level_3_hdr hdr; + + int has_mng; + struct mtp_level_3_mng mng; + + int has_prohib; + struct mtp_level_3_prohib prohib; +}; + +static const unsigned char pkt1[] = { +0x81, 0x88, 0xc0, 0x16, 0x00, 0x11, 0xe0, 0x62, +0x61, 0x72, 0x62, 0x61, 0x6e, 0x6f, 0x62, 0x61, +0x72, 0x6e, 0x61, 0x62, 0x6f }; + +static const unsigned char pkt3[] = { +0x81, 0x88, 0xc0, 0x16, 0x00, 0x21, 0xe0, 0x41, +0x6d, 0x69, 0x74, 0x20, 0x43, 0x68, 0x61, 0x6e, +0x64, 0x72, 0x61, 0x00, 0x00 }; + +static const unsigned char pkt7[] = { +0x80, 0x88, 0xc0, 0x16, 0x00, 0x14, 0x56, 0x00 }; + +#if 0 +static const unsigned char pkt2[] = { +0x81, 0x5b, 0x00, 0x22, 0x00, 0x11, 0xe0, 0x41, +0x6d, 0x69, 0x74, 0x20, 0x43, 0x68, 0x61, 0x6e, +0x64, 0x72, 0x61, 0x00, 0x00 }; + +static const unsigned char pkt4[] = { +0x81, 0x5b, 0x00, 0x22, 0x00, 0x21, 0xe0, 0x62, +0x61, 0x72, 0x62, 0x61, 0x6e, 0x6f, 0x62, 0x61, +0x72, 0x6e, 0x61, 0x62, 0x6f }; + +static const unsigned char pkt5[] = { +0x81, 0x88, 0xc0, 0x16, 0x00, 0x21, 0xe0, 0x62, +0x61, 0x72, 0x62, 0x61, 0x6e, 0x6f, 0x62, 0x61, +0x72, 0x6e, 0x61, 0x62, 0x6f }; + +static const unsigned char pkt6[] = { +0x80, 0x5b, 0x00, 0x22, 0x00, 0x17 }; + +static const unsigned char pkt8[] = { +0x80, 0x88, 0xc0, 0x16, 0x00, 0x14, 0x00, 0x00 }; + +static const unsigned char pkt9[] = { +0x80, 0x88, 0xc0, 0x16, 0x00, 0x17 }; + +static const unsigned char pkt10[] = { +0x83, 0x5b, 0x00, 0x22, 0x20, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0x01, 0x04, +0xc3, 0x88, 0x00, 0x01, 0x05, 0x03, 0xfe, 0x5b, +0x00, 0x01 }; + +static const unsigned char pkt11[] = { +0x83, 0x88, 0xc0, 0x16, 0xd0, 0x09, 0x00, 0x03, +0x05, 0x07, 0x02, 0x42, 0x01, 0x02, 0x42, 0x01, +0x05, 0x01, 0xfe, 0x5b, 0x00, 0x00 }; + +static const unsigned char pkt12[] = { +0x83, 0x5b, 0x00, 0x22, 0x30, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x30, +0x04, 0x01, 0x20 }; + +static const unsigned char pkt13[] = { +0x83, 0x5b, 0x00, 0x22, 0x40, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x0f, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt14[] = { +0x83, 0x5b, 0x00, 0x22, 0x50, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x10, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt15[] = { +0x83, 0x5b, 0x00, 0x22, 0x60, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x11, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt16[] = { +0x83, 0x5b, 0x00, 0x22, 0x70, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x12, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt17[] = { +0x83, 0x5b, 0x00, 0x22, 0x80, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x13, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt18[] = { +0x83, 0x5b, 0x00, 0x22, 0x90, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x14, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt19[] = { +0x83, 0x5b, 0x00, 0x22, 0xa0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x15, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt20[] = { +0x83, 0x5b, 0x00, 0x22, 0xb0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x16, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt21[] = { +0x83, 0x5b, 0x00, 0x22, 0xc0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x17, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt22[] = { +0x83, 0x5b, 0x00, 0x22, 0xd0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x18, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt23[] = { +0x83, 0x5b, 0x00, 0x22, 0xe0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x19, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt24[] = { +0x83, 0x5b, 0x00, 0x22, 0xf0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1a, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt25[] = { +0x83, 0x5b, 0x00, 0x22, 0x00, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1b, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt26[] = { +0x83, 0x5b, 0x00, 0x22, 0x10, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1c, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt27[] = { +0x83, 0x5b, 0x00, 0x22, 0x20, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1d, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt28[] = { +0x83, 0x5b, 0x00, 0x22, 0x30, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1e, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt29[] = { +0x83, 0x88, 0xc0, 0x16, 0x60, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x0f }; + +static const unsigned char pkt30[] = { +0x83, 0x88, 0xc0, 0x16, 0x70, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x10 }; + +static const unsigned char pkt31[] = { +0x83, 0x88, 0xc0, 0x16, 0x80, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x11 }; + +static const unsigned char pkt32[] = { +0x83, 0x88, 0xc0, 0x16, 0x90, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x12 }; + +static const unsigned char pkt33[] = { +0x83, 0x88, 0xc0, 0x16, 0xa0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x13 }; + +static const unsigned char pkt34[] = { +0x83, 0x88, 0xc0, 0x16, 0xb0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x14 }; + +static const unsigned char pkt35[] = { +0x83, 0x88, 0xc0, 0x16, 0xc0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x15 }; + +static const unsigned char pkt36[] = { +0x83, 0x88, 0xc0, 0x16, 0xd0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x16 }; + +static const unsigned char pkt37[] = { +0x83, 0x88, 0xc0, 0x16, 0xe0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x17 }; + +static const unsigned char pkt38[] = { +0x83, 0x88, 0xc0, 0x16, 0xf0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x18 }; + +static const unsigned char pkt39[] = { +0x83, 0x88, 0xc0, 0x16, 0x00, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x19 }; + +static const unsigned char pkt40[] = { +0x83, 0x88, 0xc0, 0x16, 0x10, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1a }; + +static const unsigned char pkt41[] = { +0x83, 0x88, 0xc0, 0x16, 0x20, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1b }; + +static const unsigned char pkt42[] = { +0x83, 0x88, 0xc0, 0x16, 0x30, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1c }; + +static const unsigned char pkt43[] = { +0x83, 0x88, 0xc0, 0x16, 0x40, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1d }; + +static const unsigned char pkt44[] = { +0x83, 0x88, 0xc0, 0x16, 0x50, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1e }; + +static const unsigned char pkt45[] = { +0x83, 0x5b, 0x00, 0x22, 0x40, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x0f, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt46[] = { +0x83, 0x5b, 0x00, 0x22, 0x50, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x10, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt47[] = { +0x83, 0x5b, 0x00, 0x22, 0x60, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x11, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt48[] = { +0x83, 0x5b, 0x00, 0x22, 0x70, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x12, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt49[] = { +0x83, 0x5b, 0x00, 0x22, 0x80, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x13, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt50[] = { +0x83, 0x5b, 0x00, 0x22, 0x90, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x14, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt51[] = { +0x83, 0x5b, 0x00, 0x22, 0xa0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x15, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt52[] = { +0x83, 0x5b, 0x00, 0x22, 0xb0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x16, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt53[] = { +0x83, 0x5b, 0x00, 0x22, 0xc0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x17, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt54[] = { +0x83, 0x5b, 0x00, 0x22, 0xd0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x18, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt55[] = { +0x83, 0x5b, 0x00, 0x22, 0xe0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x19, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt56[] = { +0x83, 0x5b, 0x00, 0x22, 0xf0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1a, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt57[] = { +0x83, 0x5b, 0x00, 0x22, 0x00, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1b, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt58[] = { +0x83, 0x5b, 0x00, 0x22, 0x10, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1c, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt59[] = { +0x83, 0x5b, 0x00, 0x22, 0x20, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1d, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt60[] = { +0x83, 0x5b, 0x00, 0x22, 0x30, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0xc3, 0x5b, 0x00, 0xfe, 0x04, +0x43, 0x88, 0x00, 0xfe, 0x09, 0x00, 0x07, 0x40, +0x01, 0x00, 0x1e, 0x04, 0x01, 0x07 }; + +static const unsigned char pkt61[] = { +0x83, 0x88, 0xc0, 0x16, 0x60, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x0f }; + +static const unsigned char pkt62[] = { +0x83, 0x88, 0xc0, 0x16, 0x70, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x10 }; + +static const unsigned char pkt63[] = { +0x83, 0x88, 0xc0, 0x16, 0x80, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x11 }; + +static const unsigned char pkt64[] = { +0x83, 0x88, 0xc0, 0x16, 0x90, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x12 }; + +static const unsigned char pkt65[] = { +0x83, 0x88, 0xc0, 0x16, 0xa0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x13 }; + +static const unsigned char pkt66[] = { +0x83, 0x88, 0xc0, 0x16, 0xb0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x14 }; + +static const unsigned char pkt67[] = { +0x83, 0x88, 0xc0, 0x16, 0xc0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x15 }; + +static const unsigned char pkt68[] = { +0x83, 0x88, 0xc0, 0x16, 0xd0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x16 }; + +static const unsigned char pkt69[] = { +0x83, 0x88, 0xc0, 0x16, 0xe0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x17 }; + +static const unsigned char pkt70[] = { +0x83, 0x88, 0xc0, 0x16, 0xf0, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x18 }; + +static const unsigned char pkt71[] = { +0x83, 0x88, 0xc0, 0x16, 0x00, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x19 }; + +static const unsigned char pkt72[] = { +0x83, 0x88, 0xc0, 0x16, 0x10, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1a }; + +static const unsigned char pkt73[] = { +0x83, 0x88, 0xc0, 0x16, 0x20, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1b }; + +static const unsigned char pkt74[] = { +0x83, 0x88, 0xc0, 0x16, 0x30, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1c }; + +static const unsigned char pkt75[] = { +0x83, 0x88, 0xc0, 0x16, 0x40, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1d }; + +static const unsigned char pkt76[] = { +0x83, 0x88, 0xc0, 0x16, 0x50, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x06, 0x00, 0x04, 0x48, +0x01, 0x00, 0x1e }; + +static const unsigned char pkt77[] = { +0x83, 0x88, 0xc0, 0x16, 0x60, 0x09, 0x00, 0x03, +0x07, 0x0b, 0x04, 0x43, 0x88, 0x00, 0xfe, 0x04, +0x43, 0x5b, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x31 }; +#endif + +static struct mtp_test tests[] = { + { + .input = pkt1, + .length = sizeof(pkt1), + .hdr = { + .ni = 0x02, + .spare = 0x00, + .ser_ind = 0x01, + }, + .has_mng = 1, + .mng = { + .cmn = { + .h0 = 0x01, + .h1 = 0x01, + }, + .length = 14, + }, + }, + + { + .input = pkt3, + .length = sizeof(pkt3), + .hdr = { + .ni = 0x02, + .spare = 0x00, + .ser_ind = 0x01, + }, + .has_mng = 1, + .mng = { + .cmn = { + .h0 = 0x01, + .h1 = 0x02, + }, + + .length = 14, + }, + }, + + { + .input = pkt7, + .length = sizeof(pkt7), + .hdr = { + .ni = 2, + .spare = 0, + .ser_ind = 0, + }, + + .has_prohib = 1, + .prohib = { + .cmn = { + .h0 = 0x04, + .h1 = 0x1, + }, + + }, + } +}; + +static void check_hdr(const uint8_t *data, const struct mtp_level_3_hdr *t_hdr) +{ + struct mtp_level_3_hdr *hdr; + hdr = (struct mtp_level_3_hdr *) data; + if (memcmp(hdr, t_hdr, sizeof(*hdr)) == 0) + return; + + if (hdr->ni != t_hdr->ni) + fprintf(stderr, "NI failed.\n"); + if (hdr->spare != t_hdr->spare) + fprintf(stderr, "spare not equal\n"); + if (hdr->ser_ind != t_hdr->ser_ind) + fprintf(stderr, "ser_ind not equal\n"); + if (hdr->addr != t_hdr->addr) + fprintf(stderr, "routing data not equal\n"); + + fprintf(stderr, "FAIL: Comparing headers failed.\n"); + abort(); +} + +static void check_mng(const uint8_t *data, const struct mtp_level_3_mng *t_mng) +{ + struct mtp_level_3_hdr *hdr = (struct mtp_level_3_hdr *) data; + struct mtp_level_3_mng *mng = (struct mtp_level_3_mng *) &hdr->data[0]; + + if (memcmp(mng, t_mng, sizeof(*mng)) == 0) + return; + + if (mng->cmn.h0 != t_mng->cmn.h0) + fprintf(stderr, "h0 not equal.\n"); + if (mng->cmn.h1 != t_mng->cmn.h1) + fprintf(stderr, "h1 not equal.\n"); + if (mng->length != t_mng->length) + fprintf(stderr, "length not euqal.\n"); + fprintf(stderr, "FAIL: Comparing the mtp_level_3_mng\n"); + abort(); +} + +static void check_prohib(const uint8_t *data, const struct mtp_level_3_prohib *t_prohib) +{ + struct mtp_level_3_hdr *hdr = (struct mtp_level_3_hdr *) data; + struct mtp_level_3_prohib *prohib = (struct mtp_level_3_prohib *) &hdr->data[0]; + + if (memcmp(prohib, t_prohib, sizeof(*prohib)) == 0) + return; + + if (prohib->cmn.h0 != t_prohib->cmn.h0) + fprintf(stderr, "h0 not equal.\n"); + if (prohib->cmn.h1 != t_prohib->cmn.h1) + fprintf(stderr, "h1 not equal.\n"); + if (ntohs(prohib->apoc) != t_prohib->apoc) + fprintf(stderr, "apoc not euqal.\n"); + fprintf(stderr, "FAIL: Comparing the mtp_level_3_prohib\n"); + abort(); +} + +int main(int argc, char **argv) +{ + uint32_t addr; + int i; + + printf("Basic MTP Structure testing.\n"); + + /* set the addresses here due big endian MTP_ADDRESS macro */ + tests[0].hdr.addr = MTP_ADDR(0x00, 136, 91); + tests[1].hdr.addr = MTP_ADDR(0x00, 136, 91); + tests[2].hdr.addr = MTP_ADDR(0x00, 136, 91); + tests[2].prohib.apoc = MTP_MAKE_APOC(86); + + for (i = 0; i < ARRAY_SIZE(tests); ++i) { + check_hdr(tests[i].input, &tests[i].hdr); + if (tests[i].has_mng) + check_mng(tests[i].input, &tests[i].mng); + if (tests[i].has_prohib) + check_prohib(tests[i].input, &tests[i].prohib); + } + + if (MTP_READ_OPC(tests[0].hdr.addr) != 91) { + fprintf(stderr, "Failed to read OPC address\n"); + abort(); + } + + if (MTP_READ_DPC(tests[1].hdr.addr) != 136) { + fprintf(stderr, "Failed to read DPC address\n"); + abort(); + } + + /* check the SCCP unitdata */ + { + struct sccp_con_ctrl_prt_mgt prt = { + .sst = 0x03, + .assn = 254, + .apoc = MTP_MAKE_APOC(91), + .mul_ind = 1, + }; + + uint8_t data[] = { 0x03, 0xfe, 0x5b, 0x00, 0x01 }; + if (memcmp(&prt, data, 5) != 0) { + uint8_t *d = (uint8_t *) &prt; + fprintf(stderr, "GOT: 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x\n", + d[0], d[1], d[2], d[3], d[4]); + abort(); + } + } + + /* verify decoding of the sls */ + for (i = 0; i < 16; ++i) { + addr = MTP_ADDR(i, 136, 91); + if (MTP_LINK_SLS(addr) != i) { + fprintf(stderr, "0x%x/0x%x does not match 0x%x\n", addr, MTP_LINK_SLS(addr), i); + abort(); + } + } + + printf("All tests passed.\n"); + return 0; +} diff --git a/tests/mtp/mtp_parse_test.ok b/tests/mtp/mtp_parse_test.ok new file mode 100644 index 0000000..cee2aeb --- /dev/null +++ b/tests/mtp/mtp_parse_test.ok @@ -0,0 +1,2 @@ +Basic MTP Structure testing. +All tests passed. diff --git a/tests/sccp/Makefile.am b/tests/sccp/Makefile.am new file mode 100644 index 0000000..8cce20c --- /dev/null +++ b/tests/sccp/Makefile.am @@ -0,0 +1,10 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) + +EXTRA_DIST = sccp_test.ok + +noinst_PROGRAMS = sccp_test + +sccp_test_SOURCES = sccp_test.c $(top_srcdir)/src/sccp.c +sccp_test_LDADD = $(LIBOSMOCORE_LIBS) + diff --git a/tests/sccp/sccp_test.c b/tests/sccp/sccp_test.c new file mode 100644 index 0000000..6043cff --- /dev/null +++ b/tests/sccp/sccp_test.c @@ -0,0 +1,1027 @@ +/* + * SCCP testing code + * + * (C) 2009,2011 by Holger Hans Peter Freyther + * (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 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include + +#include + +#include +#include +#include + +#include + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +/* BSC -> MSC */ +static const uint8_t bssmap_reset[] = { + 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe, + 0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04, + 0x01, 0x20, +}; + +/* MSC -> BSC reset ack */ +static const uint8_t bssmap_reset_ack[] = { + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03, + 0x00, 0x01, 0x31, +}; + +/* MSC -> BSC paging, connection less */ +static const uint8_t bssmap_paging[] = { + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10, + 0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10, + 0x02, 0x01, 0x31, 0x97, 0x61, 0x1a, 0x01, 0x06, +}; + +/* MSC -> BSC paging, UDT without PC */ +static const uint8_t bssmap_udt[] = { + 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe, + 0x02, 0x42, 0xfe, 0x10, 0x00, 0x0e, 0x52, 0x08, + 0x08, 0x29, 0x47, 0x10, 0x02, 0x01, 0x31, 0x97, + 0x61, 0x1a, 0x01, 0x06, +}; + +/* BSC -> MSC connection open */ +static const uint8_t bssmap_cr[] = { + 0x01, 0x01, 0x02, 0x03, 0x02, 0x02, 0x04, 0x02, + 0x42, 0xfe, 0x0f, 0x1f, 0x00, 0x1d, 0x57, 0x05, + 0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x12, 0xc3, + 0x50, 0x17, 0x10, 0x05, 0x24, 0x11, 0x03, 0x33, + 0x19, 0xa2, 0x08, 0x29, 0x47, 0x10, 0x02, 0x01, + 0x31, 0x97, 0x61, 0x00 +}; + +/* MSC -> BSC connection confirm */ +static const uint8_t bssmap_cc[] = { + 0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, +}; + +/* MSC -> BSC DTAP + * + * we fake a bit and make it BSC -> MSC... so the + * payload does not make any sense.. + */ +static const uint8_t bssmap_dtap[] = { + 0x06, 0x00, 0x00, 0x03, 0x00, 0x01, 0x0f, 0x01, 0x00, 0x0c, + 0x03, 0x05, 0x5c, 0x08, 0x11, 0x81, 0x33, 0x66, 0x02, 0x13, + 0x45, 0xf4, +}; + +/* MSC -> BSC clear command */ +static const uint8_t bssmap_clear[] = { + 0x06, 0x00, 0x00, 0x03, 0x00, 0x01, 0x06, 0x00, 0x04, 0x20, + 0x04, 0x01, 0x09, +}; + +/* MSC -> BSC released */ +static const uint8_t bssmap_released[] = { + 0x04, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00, 0x01, 0x0f, + 0x02, 0x23, 0x42, 0x00, +}; + +/* BSC -> MSC released */ +static const uint8_t bssmap_release_complete[] = { + 0x05, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03 +}; + +/* message with a SCCP global title */ +static const uint8_t tcap_global_title[] = { + 0x09, + 0x81, 0x03, 0x0d, 0x18, 0x0a, 0x12, 0x07, 0x00, + 0x12, 0x04, 0x53, 0x84, 0x09, 0x00, 0x17, 0x0b, + 0x12, 0x06, 0x00, 0x12, 0x04, 0x44, 0x87, 0x20, + 0x00, 0x20, 0x65, 0x9a, 0x65, 0x81, 0x97, 0x48, + 0x04, 0x26, 0x00, 0x01, 0x98, 0x49, 0x04, 0x51, + 0x01, 0x03, 0xdf, 0x6c, 0x81, 0x88, 0xa1, 0x81, + 0x85, 0x02, 0x01, 0x44, 0x02, 0x01, 0x07, 0x30, + 0x80, 0xa7, 0x80, 0xa0, 0x80, 0x04, 0x01, 0x2b, + 0x30, 0x80, 0x30, 0x12, 0x83, 0x01, 0x10, 0x84, + 0x01, 0x07, 0x85, 0x07, 0x91, 0x44, 0x57, 0x76, + 0x67, 0x16, 0x97, 0x86, 0x01, 0x20, 0x30, 0x06, + 0x82, 0x01, 0x18, 0x84, 0x01, 0x04, 0x00, 0x00, + 0x00, 0x00, 0xa3, 0x06, 0x04, 0x01, 0x42, 0x84, + 0x01, 0x05, 0xa3, 0x06, 0x04, 0x01, 0x51, 0x84, + 0x01, 0x05, 0xa3, 0x06, 0x04, 0x01, 0x31, 0x84, + 0x01, 0x05, 0xa3, 0x09, 0x04, 0x01, 0x12, 0x84, + 0x01, 0x05, 0x82, 0x01, 0x02, 0xa3, 0x09, 0x04, + 0x01, 0x11, 0x84, 0x01, 0x05, 0x81, 0x01, 0x01, + 0xa3, 0x06, 0x04, 0x01, 0x14, 0x84, 0x01, 0x00, + 0xa3, 0x0b, 0x04, 0x01, 0x41, 0x84, 0x01, 0x04, + 0x30, 0x03, 0x83, 0x01, 0x10, 0xa3, 0x0b, 0x04, + 0x01, 0x41, 0x84, 0x01, 0x04, 0x30, 0x03, 0x82, + 0x01, 0x18, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8_t tcap_global_dst_gti[] = { + 0x00, 0x12, 0x04, 0x53, 0x84, 0x09, 0x00, 0x17, +}; + +static const uint8_t tcap_global_src_gti[] = { + 0x00, 0x12, 0x04, 0x44, 0x87, 0x20, 0x00, 0x20, 0x65, +}; + + +struct test_data { + int length; + const uint8_t *data; + int payload_start; + int payload_length; + uint8_t first_byte; + + /* in case it should trigger a sccp response */ + int write; + const uint8_t *response; + int response_length; +}; + +static const struct test_data test_data[] = { + { + .length = ARRAY_SIZE(bssmap_reset), + .data = &bssmap_reset[0], + .payload_start = 12, + .payload_length = ARRAY_SIZE(bssmap_reset) - 12, + .first_byte = 0x0, + }, + { + .length = ARRAY_SIZE(bssmap_reset_ack), + .data = &bssmap_reset_ack[0], + .payload_start = 16, + .payload_length = ARRAY_SIZE(bssmap_reset_ack) - 16, + .first_byte = 0x0, + }, + { + .length = ARRAY_SIZE(bssmap_paging), + .data = &bssmap_paging[0], + .payload_start = 16, + .payload_length = ARRAY_SIZE(bssmap_paging) - 16, + .first_byte = 0x0, + }, + { + .length = ARRAY_SIZE(bssmap_cr), + .data = &bssmap_cr[0], + .payload_start = 12, + /* 0x00 is end of optional data, subtract this byte */ + .payload_length = 31, + .first_byte = 0x0, + + /* the connection request should trigger a connection confirm */ + .write = 1, + .response = &bssmap_cc[0], + .response_length= ARRAY_SIZE(bssmap_cc), + }, + { + .length = ARRAY_SIZE(bssmap_dtap), + .data = &bssmap_dtap[0], + .payload_start = 7, + .payload_length = 15, + .first_byte = 0x01, + }, + { + .length = ARRAY_SIZE(bssmap_clear), + .data = &bssmap_clear[0], + .payload_start = 7, + .payload_length = 6, + .first_byte = 0x00, + }, + { + .length = ARRAY_SIZE(bssmap_released), + .data = &bssmap_released[0], + .payload_length = 2, + .payload_start = 11, + .first_byte = 0x23, + + .write = 1, + .response = &bssmap_release_complete[0], + .response_length= ARRAY_SIZE(bssmap_release_complete), + }, +}; + +/* we will send UDTs and verify they look like this */ +static const struct test_data send_data[] = { + { + .length = ARRAY_SIZE(bssmap_udt), + .data = &bssmap_udt[0], + .payload_start = 12, + .payload_length = ARRAY_SIZE(bssmap_udt) - 12, + .first_byte = 0x0, + }, + { + .length = ARRAY_SIZE(bssmap_reset), + .data = &bssmap_reset[0], + .payload_start = 12, + .payload_length = ARRAY_SIZE(bssmap_reset) - 12, + .first_byte = 0x0, + }, +}; + +struct connection_test { + /* should the connection be refused? */ + int refuse; + + int with_data; + + /* on which side to close the connection? */ + int close_side; + int close_cause; +}; + +/* sccp connection handling we want to test */ +static const struct connection_test connection_tests[] = { + { + .refuse = 1, + }, + { + .refuse = 1, + .with_data = 1, + }, + { + .refuse = 0, + .close_side = 0, + .close_cause = 5, + }, + { + .refuse = 0, + .close_side = 0, + .close_cause = 5, + .with_data = 1, + }, + { + .refuse = 0, + .close_side = 1, + .close_cause = 5, + }, + { + .refuse = 0, + .close_side = 1, + .close_cause = 5, + .with_data = 1, + }, +}; + +struct sccp_parse_header_result { + /* results */ + int msg_type; + int wanted_len; + int src_ssn; + int dst_ssn; + + int has_src_ref, has_dst_ref; + struct sccp_source_reference src_ref; + struct sccp_source_reference dst_ref; + + /* global title len */ + int src_gti_len; + const uint8_t *src_gti_data; + int dst_gti_len; + const uint8_t *dst_gti_data; + + /* the input */ + const uint8_t *input; + int input_len; +}; + +static const uint8_t it_test[] = { +0x10, 0x01, 0x07, +0x94, 0x01, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00 }; + +static const uint8_t proto_err[] = { +0x0f, 0x0c, 0x04, 0x00, 0x00, +}; + +static const struct sccp_parse_header_result parse_result[] = { + { + .msg_type = SCCP_MSG_TYPE_IT, + .wanted_len = 0, + .src_ssn = -1, + .dst_ssn = -1, + .has_src_ref = 1, + .has_dst_ref = 1, + + .src_ref = { + .octet1 = 0x01, + .octet2 = 0x04, + .octet3 = 0x00 + }, + .dst_ref = { + .octet1 = 0x01, + .octet2 = 0x07, + .octet3 = 0x94, + }, + + .input = it_test, + .input_len = sizeof(it_test), + }, + { + .msg_type = SCCP_MSG_TYPE_ERR, + .wanted_len = 0, + .src_ssn = -1, + .dst_ssn = -1, + .has_src_ref = 0, + .has_dst_ref = 1, + .dst_ref = { + .octet1 = 0x0c, + .octet2 = 0x04, + .octet3 = 0x00, + }, + .input = proto_err, + .input_len = sizeof(proto_err), + }, + { + .msg_type = SCCP_MSG_TYPE_UDT, + .input = tcap_global_title, + .input_len = sizeof(tcap_global_title), + .wanted_len = 154, + .dst_ssn = SCCP_SSN_VLR, + .dst_gti_data = tcap_global_dst_gti, + .dst_gti_len = 8, + .src_ssn = SCCP_SSN_HLR, + .src_gti_data = tcap_global_src_gti, + .src_gti_len = 9, + }, +}; + + +/* testing procedure: + * - we will use sccp_write and see what will be set in the + * outgoing callback + * - we will call sccp_system_incoming and see which calls + * are made. And then compare it to the ones we expect. We + * want the payload to arrive, or callbacks to be called. + * - we will use sccp_connection_socket and sccp_connection_write + * and verify state handling of connections + */ + +static int current_test; + +/* + * test state... + */ +static int called = 0; +static int matched = 0; +static int write_called = 0; + +#define FAIL(x, args...) do { \ + printf("FAILURE in %s:%d: " x, __FILE__, __LINE__, ## args); \ + abort(); } while (0) + +/* + * writing these packets and expecting a result + */ +int sccp_read_cb(struct msgb *data, unsigned len, void *gctx) +{ + uint16_t payload_length = test_data[current_test].payload_length; + const uint8_t *got, *wanted; + int i; + + called = 1; + + if (msgb_l3len(data) < len) { + /* this should never be reached */ + FAIL("Something horrible happened.. invalid packet..\n"); + } + + if (len == 0 || len != payload_length) { + FAIL("length mismatch: got: %d wanted: %d\n", msgb_l3len(data), payload_length); + } + + if (data->l3h[0] != test_data[current_test].first_byte) { + FAIL("The first bytes of l3 do not match: 0x%x 0x%x\n", + data->l3h[0], test_data[current_test].first_byte); + } + + got = &data->l3h[0]; + wanted = test_data[current_test].data + test_data[current_test].payload_start; + + for (i = 0; i < len; ++i) { + if (got[i] != wanted[i]) { + FAIL("Failed to compare byte. Got: 0x%x Wanted: 0x%x at %d\n", + got[i], wanted[i], i); + } + } + + matched = 1; + return 0; +} + +void sccp_write_cb(struct sccp_connection *conn, struct msgb *data, void *gctx, void *ctx) +{ + int i = 0; + const uint8_t *got, *wanted; + + if (test_data[current_test].response == NULL) { + FAIL("Didn't expect write callback\n"); + } else if (test_data[current_test].response_length != msgb_l2len(data)) { + FAIL("Size does not match. Got: %d Wanted: %d\n", + msgb_l2len(data), test_data[current_test].response_length); + } + + got = &data->l2h[0]; + wanted = test_data[current_test].response; + + for (i = 0; i < msgb_l2len(data); ++i) { + if (got[i] != wanted[i]) { + FAIL("Failed to compare byte. Got: 0x%x Wanted: 0x%x at %d\n", + got[i], wanted[i], i); + } + } + + write_called = 1; +} + +void sccp_c_read(struct sccp_connection *connection, struct msgb *msgb, unsigned int len) +{ + sccp_read_cb(msgb, len, connection->data_ctx); +} + +void sccp_c_state(struct sccp_connection *connection, int old_state) +{ + if (connection->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) + sccp_connection_free(connection); +} + +int sccp_accept_cb(struct sccp_connection *connection, void *user_data) +{ + called = 1; + unsigned int ref = 0; + ref |= connection->destination_local_reference.octet1 << 24; + ref |= connection->destination_local_reference.octet2 << 16; + ref |= connection->destination_local_reference.octet3 << 8; + ref = ntohl(ref); + + connection->data_cb = sccp_c_read; + connection->state_cb = sccp_c_state; + + /* accept this */ + return 0; +} + +static void sccp_udt_write_cb(struct sccp_connection *conn, struct msgb *data, void *gtx, void *ctx) +{ + const uint8_t *got, *wanted; + int i; + + write_called = 1; + + if (send_data[current_test].length != msgb_l2len(data)) { + FAIL("Size does not match. Got: %d Wanted: %d\n", + msgb_l2len(data), send_data[current_test].length); + } + + got = &data->l2h[0]; + wanted = send_data[current_test].data; + + for (i = 0; i < msgb_l2len(data); ++i) { + if (got[i] != wanted[i]) { + FAIL("Failed to compare byte. Got: 0x%x Wanted: 0x%x at %d\n", + got[i], wanted[i], i); + } + } + + matched = 1; +} + +static void test_sccp_system(void) +{ + printf("Testing SCCP System\n"); + + sccp_system_init(sccp_write_cb, NULL); + sccp_set_read(&sccp_ssn_bssap, sccp_read_cb, NULL); + sccp_connection_set_incoming(&sccp_ssn_bssap, sccp_accept_cb, NULL); + + for (current_test = 0; current_test < ARRAY_SIZE(test_data); ++current_test) { + unsigned int length = test_data[current_test].length; + struct msgb *msg = msgb_alloc_headroom(length + 2, 2, __func__); + msg->l2h = msgb_put(msg, length); + memcpy(msg->l2h, test_data[current_test].data, length); + + called = matched = write_called = 0; + printf("Testing packet: %d\n", current_test); + sccp_system_incoming(msg); + + if (!called || !matched || (test_data[current_test].write != write_called)) + FAIL("current test: %d called: %d matched: %d write: %d\n", + current_test, called, matched, write_called); + + msgb_free(msg); + } +} + +/* test sending of udt */ +static void test_sccp_send_udt(void) +{ + printf("Testing send UDT\n"); + + sccp_system_init(sccp_udt_write_cb, NULL); + sccp_set_read(NULL, NULL, NULL); + sccp_connection_set_incoming(NULL, NULL, NULL); + + for (current_test = 0; current_test < ARRAY_SIZE(send_data); ++current_test) { + const struct test_data *test = &send_data[current_test]; + + struct msgb *msg = msgb_alloc(test->payload_length, __func__); + msg->l3h = msgb_put(msg, test->payload_length); + memcpy(msg->l3h, test->data + test->payload_start, test->payload_length); + + matched = write_called = 0; + printf("Testing packet: %d\n", current_test); + sccp_write(msg, &sccp_ssn_bssap, &sccp_ssn_bssap, 0, NULL); + + if (!matched || !write_called) + FAIL("current test: %d matched: %d write: %d\n", + current_test, matched, write_called); + + msgb_free(msg); + } +} + +/* send udt from one end to another */ +static unsigned int test_value = 0x2442; +static int sccp_udt_read(struct msgb *data, unsigned int len, void *gctx) +{ + unsigned int *val; + + if (len != 4) { + FAIL("Wrong size: %d\n", msgb_l3len(data)); + } + + val = (unsigned int*)data->l3h; + matched = test_value == *val; + + return 0; +} + +static void sccp_write_loop(struct sccp_connection *conn, struct msgb *data, void *gctx, void *ctx) +{ + /* send it back to us */ + sccp_system_incoming(data); + msgb_free(data); +} + +static void test_sccp_udt_communication(void) +{ + struct msgb *data; + unsigned int *val; + + printf("Testing UDT Communication.\n"); + + sccp_system_init(sccp_write_loop, NULL); + sccp_set_read(&sccp_ssn_bssap, sccp_udt_read, NULL); + sccp_connection_set_incoming(NULL, NULL, NULL); + + + data = msgb_alloc(4, "test data"); + data->l3h = &data->data[0]; + val = (unsigned int *)msgb_put(data, 4); + *val = test_value; + + matched = 0; + sccp_write(data, &sccp_ssn_bssap, &sccp_ssn_bssap, 0, NULL); + + if (!matched) + FAIL("Talking with us didn't work\n"); + + msgb_free(data); +} + + +/* connection testing... open, send, close */ +static const struct connection_test *current_con_test; +static struct sccp_connection *outgoing_con; +static struct sccp_connection *incoming_con; +static int outgoing_data, incoming_data, incoming_state, outgoing_state; + +static struct msgb *test_data1, *test_data2, *test_data3; + +static void sccp_conn_in_state(struct sccp_connection *conn, int old_state) +{ + printf("\tincome: %d -> %d\n", old_state, conn->connection_state); + if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) { + if (conn == incoming_con) { + sccp_connection_free(conn); + incoming_con = NULL; + } + } +} + +static void sccp_conn_in_data(struct sccp_connection *conn, struct msgb *msg, unsigned int len) +{ + /* compare the data */ + ++incoming_data; + printf("\tincoming data: %d\n", len); + + /* compare the data */ + if (len != 4) { + FAIL("Length of packet is wrong: %u %u\n", msgb_l3len(msg), len); + } + + if (incoming_data == 1) { + if (memcmp(msg->l3h, test_data1->l3h, len) != 0) { + FAIL("Comparing the data failed: %d\n", incoming_data); + + } + } else if (incoming_data == 2) { + if (memcmp(msg->l3h, test_data2->l3h, len) != 0) { + FAIL("Comparing the data failed: %d\n", incoming_data); + } + } + + /* sending out data */ + if (incoming_data == 2) { + printf("\tReturning data3\n"); + sccp_connection_write(conn, test_data3); + } +} + +static int sccp_conn_accept(struct sccp_connection *conn, void *ctx) +{ + printf("\taccept: srcref(%u)\n", + sccp_src_ref_to_int(&conn->source_local_reference)); + conn->state_cb = sccp_conn_in_state; + conn->data_cb = sccp_conn_in_data; + + if (current_con_test->refuse) + return -1; + + incoming_con = conn; + return 0; +} + +/* callbacks for the outgoing side */ +static void sccp_conn_out_state(struct sccp_connection *conn, int old_state) +{ + printf("\toutgoing: dstref(%u) %d -> %d\n", + sccp_src_ref_to_int(&conn->destination_local_reference), + old_state, conn->connection_state); + + if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) { + if (conn == outgoing_con) { + sccp_connection_free(conn); + outgoing_con = NULL; + } + } +} + +static void sccp_conn_out_data(struct sccp_connection *conn, struct msgb *msg, unsigned int len) +{ + ++outgoing_data; + printf("\toutgoing data: dstref(%u) %d\n", + sccp_src_ref_to_int(&conn->destination_local_reference), len); + + if (len != 4) + FAIL("Length of packet is wrong: %u %u\n", msgb_l3len(msg), len); + + if (outgoing_data == 1) { + if (memcmp(msg->l3h, test_data3->l3h, len) != 0) { + FAIL("Comparing the data failed\n"); + } + } +} + +static void do_test_sccp_connection(const struct connection_test *test) +{ + int ret; + + current_con_test = test; + outgoing_con = incoming_con = 0; + + outgoing_con = sccp_connection_socket(); + if (!outgoing_con) { + FAIL("Connection is NULL\n"); + } + + outgoing_con->state_cb = sccp_conn_out_state; + outgoing_con->data_cb = sccp_conn_out_data; + outgoing_data = incoming_data = 0; + incoming_state = outgoing_state = 1; + + /* start testing */ + if (test->with_data) { + if (sccp_connection_connect(outgoing_con, &sccp_ssn_bssap, test_data1) != 0) + FAIL("Binding failed\n"); + } else { + ++incoming_data; + if (sccp_connection_connect(outgoing_con, &sccp_ssn_bssap, NULL) != 0) + FAIL("Binding failed\n"); + } + + if (test->refuse) { + if (outgoing_con) + FAIL("Outgoing connection should have been refused.\n"); + } else { + if (!incoming_con) + FAIL("Creating incoming didn't work.\n"); + + printf("\tWriting test data2\n"); + sccp_connection_write(outgoing_con, test_data2); + sccp_connection_send_it(outgoing_con); + + /* closing connection */ + if (test->close_side == 0) + ret = sccp_connection_close(outgoing_con, 0); + else + ret = sccp_connection_close(incoming_con, 0); + + if (ret != 0) + FAIL("Closing the connection failed\n"); + } + + /* outgoing should be gone now */ + if (outgoing_con) + FAIL("Outgoing connection was not properly closed\n"); + + if (incoming_con) + FAIL("Incoming connection was not propery closed.\n"); + + if (test->refuse == 0) { + if (outgoing_data != 1 || incoming_data != 2) { + FAIL("Data sending failed: %d/%d %d/%d\n", + outgoing_data, 1, + incoming_data, 2); + } + } + + if (!incoming_state || !outgoing_state) + FAIL("Failure with the state transition. %d %d\n", + outgoing_state, incoming_state); +} + +static void test_sccp_connection(void) +{ + printf("Testing SCCP connection.\n"); + + sccp_system_init(sccp_write_loop, NULL); + sccp_set_read(NULL, NULL, NULL); + sccp_connection_set_incoming(&sccp_ssn_bssap, sccp_conn_accept, NULL); + + test_data1 = msgb_alloc(4, "data1"); + test_data1->l3h = msgb_put(test_data1, 4); + *((unsigned int*)test_data1->l3h) = 0x23421122; + + test_data2 = msgb_alloc(4, "data2"); + test_data2->l3h = msgb_put(test_data2, 4); + *((unsigned int*)test_data2->l3h) = 0x42232211; + + test_data3 = msgb_alloc(4, "data3"); + test_data3->l3h = msgb_put(test_data3, 4); + *((unsigned int*)test_data3->l3h) = 0x2323ff55; + + + for (current_test = 0; current_test < ARRAY_SIZE(connection_tests); ++current_test) { + printf("Testing %d refuse: %d with_data: %d\n", + current_test, connection_tests[current_test].refuse, + connection_tests[current_test].with_data); + do_test_sccp_connection(&connection_tests[current_test]); + } + + msgb_free(test_data1); + msgb_free(test_data2); + msgb_free(test_data3); +} + +/* invalid input */ +static void test_sccp_system_crash(void) +{ + printf("trying to provoke a crash with invalid input\n"); + sccp_set_read(&sccp_ssn_bssap, sccp_read_cb, NULL); + sccp_connection_set_incoming(&sccp_ssn_bssap, sccp_accept_cb, NULL); + + for (current_test = 0; current_test < ARRAY_SIZE(test_data); ++current_test) { + int original_length = test_data[current_test].length; + int length = original_length + 2; + int i; + + printf("Testing packet: %d\n", current_test); + + for (i = length; i >= 0; --i) { + unsigned int length = MIN(test_data[current_test].length, i); + struct msgb *msg = msgb_alloc_headroom(length + 2, 2, __func__); + msg->l2h = msgb_put(msg, length); + memcpy(msg->l2h, test_data[current_test].data, length); + sccp_system_incoming(msg); + msgb_free(msg); + } + } + + printf("survived\n"); +} + +static void test_sccp_parsing(void) +{ + printf("Test SCCP Parsing.\n"); + + for (current_test = 0; current_test < ARRAY_SIZE(parse_result); ++current_test) { + struct msgb *msg; + struct sccp_parse_result result; + + msg = msgb_alloc_headroom(1024, 128, "parse-test"); + msgb_put(msg, 1); + msg->l2h = msgb_put(msg, parse_result[current_test].input_len); + memcpy(msg->l2h, parse_result[current_test].input, msgb_l2len(msg)); + + memset(&result, 0, sizeof(result)); + if (sccp_parse_header(msg, &result) != 0) { + FAIL("Failed to sccp parse test: %d\n", current_test); + } else { + if (parse_result[current_test].wanted_len != result.data_len) { + FAIL("Unexpected data length. Got: %d\n", result.data_len); + } + + if (parse_result[current_test].has_src_ref) { + if (memcmp(result.source_local_reference, + &parse_result[current_test].src_ref, + sizeof(struct sccp_source_reference)) != 0) { + FAIL("SRC REF did not match\n"); + } + } + + if (parse_result[current_test].has_dst_ref) { + if (memcmp(result.destination_local_reference, + &parse_result[current_test].dst_ref, + sizeof(struct sccp_source_reference)) != 0) { + FAIL("DST REF did not match\n"); + } + } + + if (parse_result[current_test].src_ssn != -1 && + parse_result[current_test].src_ssn != result.calling.ssn) { + FAIL("Calling SSN is wrong..\n"); + } + + if (parse_result[current_test].dst_ssn != -1 && + parse_result[current_test].dst_ssn != result.called.ssn) { + FAIL("Called SSN is wrong..\n"); + } + + if (parse_result[current_test].src_gti_len != result.calling.gti_len) { + FAIL("GTI length is wrong: %d\n", result.calling.gti_len); + } + + if (parse_result[current_test].dst_gti_len != result.called.gti_len) { + FAIL("GTI length is wrong: %d\n", result.called.gti_len); + } + + if (memcmp(&parse_result[current_test].dst_gti_data[0], + result.called.gti_data, result.called.gti_len) != 0) { + FAIL("GTI data is wrong: %d '%s'\n", + result.called.gti_len, + osmo_hexdump(result.called.gti_data, result.called.gti_len)); + } + + if (memcmp(&parse_result[current_test].src_gti_data[0], + result.calling.gti_data, result.calling.gti_len) != 0) { + FAIL("GTI data is wrong: %d\n", result.calling.gti_len); + } + } + + msgb_free(msg); + } +} + +/* + * Test the creation of SCCP addresses + */ +int sccp_create_sccp_addr(struct msgb *msg, const struct sockaddr_sccp *sock); + +struct sccp_addr_tst { + const struct sockaddr_sccp *addr; + + const uint8_t *output; + const int output_len; +}; + +static uint8_t ssn_out[] = { + 0x02, 0x42, 0xfe, +}; + +const struct sockaddr_sccp sccp_poi_bssap = { + .sccp_family = 0, + .sccp_ssn = SCCP_SSN_BSSAP, + .poi = {0x01, 0x00}, + .use_poi = 1, +}; + +static uint8_t poi_out[] = { + 0x04, 0x43, 0x01, 0x00, 0xfe, +}; + +static uint8_t gti_dat[] = { + 0x00, 0x12, 0x04, 0x53, 0x84, 0x09, 0x00, 0x17, +}; + +const struct sockaddr_sccp sccp_gti_bssap = { + .sccp_family = 0, + .sccp_ssn = 7, + .gti_ind = 4, + .gti_len = ARRAY_SIZE(gti_dat), + .gti = gti_dat, +}; + +static uint8_t gti_out[] = { + 0x0a, 0x12, 0x07, 0x00, 0x12, 0x04, 0x53, 0x84, 0x09, 0x00, 0x17, +}; + +static struct sccp_addr_tst sccp_addr_tst[] = { + { + .addr = &sccp_ssn_bssap, + .output = ssn_out, + .output_len = ARRAY_SIZE(ssn_out), + }, + { + .addr = &sccp_poi_bssap, + .output = poi_out, + .output_len = ARRAY_SIZE(poi_out), + }, + { + .addr = &sccp_gti_bssap, + .output = gti_out, + .output_len = ARRAY_SIZE(gti_out), + }, +}; + +static void test_sccp_address(void) +{ + int i, ret; + struct msgb *msg = msgb_alloc(128, "sccp-addr"); + + printf("Test SCCP Address\n"); + + for (i = 0; i < ARRAY_SIZE(sccp_addr_tst); ++i) { + msgb_reset(msg); + ret = sccp_create_sccp_addr(msg, sccp_addr_tst[i].addr); + if (ret != sccp_addr_tst[i].output_len) { + FAIL("Length is from for %d\n", i); + } + + if (memcmp(msg->data, sccp_addr_tst[i].output, ret) != 0) { + FAIL("Unexpected data for %d '%s'\n", i, + osmo_hexdump(msg->data, ret)); + } + } +} + +static const struct log_info_cat default_categories[] = { + [0] = { + .name = "DSCCP", + .description = "DSCP", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +static int null_flt(const struct log_context *ctx, struct log_target *target) +{ + return 1; +} + +const struct log_info log_info = { + .filter_fn = null_flt, + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +int main(int argc, char **argv) +{ + struct log_target *stderr_target; + log_init(&log_info, NULL); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + + printf("Testing SCCP handling.\n"); + + sccp_set_log_area(0); + + test_sccp_system(); + test_sccp_send_udt(); + test_sccp_udt_communication(); + test_sccp_connection(); + test_sccp_system_crash(); + test_sccp_parsing(); + test_sccp_address(); + printf("All tests passed.\n"); + return 0; +} + +void db_store_counter(void) {} diff --git a/tests/sccp/sccp_test.ok b/tests/sccp/sccp_test.ok new file mode 100644 index 0000000..2b55a16 --- /dev/null +++ b/tests/sccp/sccp_test.ok @@ -0,0 +1,86 @@ +Testing SCCP handling. +Testing SCCP System +Testing packet: 0 +Testing packet: 1 +Testing packet: 2 +Testing packet: 3 +Testing packet: 4 +Testing packet: 5 +Testing packet: 6 +Testing send UDT +Testing packet: 0 +Testing packet: 1 +Testing UDT Communication. +Testing SCCP connection. +Testing 0 refuse: 1 with_data: 0 + outgoing: dstref(0) 0 -> 1 + accept: srcref(0) + outgoing: dstref(0) 1 -> 6 + income: 0 -> 6 +Testing 1 refuse: 1 with_data: 1 + outgoing: dstref(0) 0 -> 1 + accept: srcref(0) + outgoing: dstref(0) 1 -> 6 + income: 0 -> 6 +Testing 2 refuse: 0 with_data: 0 + outgoing: dstref(0) 0 -> 1 + accept: srcref(0) + outgoing: dstref(196612) 1 -> 3 + income: 0 -> 3 + Writing test data2 + incoming data: 4 + Returning data3 + outgoing data: dstref(196612) 4 + outgoing: dstref(196612) 3 -> 4 + outgoing: dstref(196612) 4 -> 5 + income: 3 -> 5 +Testing 3 refuse: 0 with_data: 1 + outgoing: dstref(0) 0 -> 1 + accept: srcref(0) + outgoing: dstref(196614) 1 -> 3 + income: 0 -> 3 + incoming data: 4 + Writing test data2 + incoming data: 4 + Returning data3 + outgoing data: dstref(196614) 4 + outgoing: dstref(196614) 3 -> 4 + outgoing: dstref(196614) 4 -> 5 + income: 3 -> 5 +Testing 4 refuse: 0 with_data: 0 + outgoing: dstref(0) 0 -> 1 + accept: srcref(0) + outgoing: dstref(196616) 1 -> 3 + income: 0 -> 3 + Writing test data2 + incoming data: 4 + Returning data3 + outgoing data: dstref(196616) 4 + income: 3 -> 4 + income: 4 -> 5 + outgoing: dstref(196616) 3 -> 5 +Testing 5 refuse: 0 with_data: 1 + outgoing: dstref(0) 0 -> 1 + accept: srcref(0) + outgoing: dstref(196618) 1 -> 3 + income: 0 -> 3 + incoming data: 4 + Writing test data2 + incoming data: 4 + Returning data3 + outgoing data: dstref(196618) 4 + income: 3 -> 4 + income: 4 -> 5 + outgoing: dstref(196618) 3 -> 5 +trying to provoke a crash with invalid input +Testing packet: 0 +Testing packet: 1 +Testing packet: 2 +Testing packet: 3 +Testing packet: 4 +Testing packet: 5 +Testing packet: 6 +survived +Test SCCP Parsing. +Test SCCP Address +All tests passed. diff --git a/tests/ss7/Makefile.am b/tests/ss7/Makefile.am new file mode 100644 index 0000000..3b6cb2c --- /dev/null +++ b/tests/ss7/Makefile.am @@ -0,0 +1,12 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Wall +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) + +AM_LDFLAGS = -static +LDADD = $(top_builddir)/src/libosmo-sigtran.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS) + +EXTRA_DIST = ss7_test.ok ss7_test.err + +noinst_PROGRAMS = ss7_test + +ss7_test_SOURCES = ss7_test.c diff --git a/tests/ss7/ss7_test.c b/tests/ss7/ss7_test.c new file mode 100644 index 0000000..7c51767 --- /dev/null +++ b/tests/ss7/ss7_test.c @@ -0,0 +1,322 @@ +#include "../src/xua_internal.h" +#include "../src/xua_asp_fsm.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static struct osmo_ss7_instance *s7i; + +static void test_pc_transcode(uint32_t pc) +{ + const char *pc_str = osmo_ss7_pointcode_print(s7i, pc); + uint32_t pc_reenc = osmo_ss7_pointcode_parse(s7i, pc_str); + + printf("%s(%u) -> %s -> %u\n", __func__, pc, pc_str, pc_reenc); + OSMO_ASSERT(pc == pc_reenc); +} + +static void test_pc_defaults(void) +{ + /* ensure the default point code format settings apply */ + OSMO_ASSERT(s7i->cfg.pc_fmt.component_len[0] == 3); + OSMO_ASSERT(s7i->cfg.pc_fmt.component_len[1] == 8); + OSMO_ASSERT(s7i->cfg.pc_fmt.component_len[2] == 3); + OSMO_ASSERT(s7i->cfg.pc_fmt.delimiter == '.'); +} + +static void parse_print_mask(const char *in) +{ + uint32_t mask = osmo_ss7_pointcode_parse_mask_or_len(s7i, in); + const char *pc_str = osmo_ss7_pointcode_print(s7i, mask); + printf("mask %s => %u (0x%x) %s\n", in, mask, mask, pc_str); +} + +static void test_pc_parser_itu(void) +{ + /* ITU Style */ + printf("Testing ITU-style point code format\n"); + osmo_ss7_instance_set_pc_fmt(s7i, 3, 8, 3); + test_pc_transcode(0); + test_pc_transcode(1); + test_pc_transcode(1 << 3); + test_pc_transcode(1 << (3+8)); + test_pc_transcode(7 << (3+8)); + test_pc_transcode(100); + test_pc_transcode(2342); + test_pc_transcode((1 << 14)-1); + + parse_print_mask("/1"); + parse_print_mask("7.0.0"); + parse_print_mask("/14"); +} + +static void test_pc_parser_ansi(void) +{ + /* ANSI Style */ + printf("Testing ANSI-style point code format\n"); + osmo_ss7_instance_set_pc_fmt(s7i, 8, 8, 8); + s7i->cfg.pc_fmt.delimiter = '-'; + test_pc_transcode(0); + test_pc_transcode(1); + test_pc_transcode(1 << 8); + test_pc_transcode(1 << 16); + test_pc_transcode(1 << (3+8)); + test_pc_transcode((1 << 24)-1); + test_pc_transcode(100); + test_pc_transcode(2342); + + parse_print_mask("/1"); + parse_print_mask("/16"); + parse_print_mask("/24"); + + /* re-set to default (ITU) */ + osmo_ss7_instance_set_pc_fmt(s7i, 3, 8, 3); + s7i->cfg.pc_fmt.delimiter = '.'; +} + +static int test_user_prim_cb(struct osmo_prim_hdr *oph, void *priv) +{ + OSMO_ASSERT(priv == (void *) 0x1234); + + return 23; +} + +static void test_user(void) +{ + struct osmo_ss7_user user, user2; + struct osmo_mtp_prim omp = { + .oph = { + .sap = MTP_SAP_USER, + .primitive = OSMO_MTP_PRIM_TRANSFER, + .operation = PRIM_OP_INDICATION, + }, + .u.transfer = { + .sio = 1, + }, + }; + + printf("Testing SS7 user\n"); + + user.name = "testuser"; + user.priv = (void *) 0x1234; + user.prim_cb = test_user_prim_cb; + + /* registration */ + OSMO_ASSERT(osmo_ss7_user_register(s7i, 1, &user) == 0); + OSMO_ASSERT(osmo_ss7_user_register(s7i, 1, NULL) == -EBUSY); + OSMO_ASSERT(osmo_ss7_user_register(s7i, 255, NULL) == -EINVAL); + + /* primitive delivery */ + OSMO_ASSERT(osmo_ss7_mtp_to_user(s7i, &omp) == 23); + + /* cleanup */ + OSMO_ASSERT(osmo_ss7_user_unregister(s7i, 255, NULL) == -EINVAL); + OSMO_ASSERT(osmo_ss7_user_unregister(s7i, 10, NULL) == -ENODEV); + OSMO_ASSERT(osmo_ss7_user_unregister(s7i, 1, &user2) == -EINVAL); + OSMO_ASSERT(osmo_ss7_user_unregister(s7i, 1, &user) == 0); + + /* primitive delivery should fail now */ + OSMO_ASSERT(osmo_ss7_mtp_to_user(s7i, &omp) == -ENODEV); + + /* wrong primitive delivery should also fail */ + omp.oph.primitive = OSMO_MTP_PRIM_PAUSE; + OSMO_ASSERT(osmo_ss7_mtp_to_user(s7i, &omp) == -EINVAL); +} + +static void test_route(void) +{ + struct osmo_ss7_route_table *rtbl; + struct osmo_ss7_linkset *lset_a, *lset_b; + struct osmo_ss7_route *rt, *rt12, *rtdef; + + printf("Testing SS7 routing\n"); + + /* creation / destruction */ + OSMO_ASSERT(osmo_ss7_route_table_find(s7i, "foobar") == NULL); + rtbl = osmo_ss7_route_table_find_or_create(s7i, "foobar"); + OSMO_ASSERT(rtbl); + OSMO_ASSERT(osmo_ss7_route_table_find_or_create(s7i, "foobar") == rtbl); + osmo_ss7_route_table_destroy(rtbl); + OSMO_ASSERT(osmo_ss7_route_table_find(s7i, "foobar") == NULL); + + /* we now work with system route table */ + rtbl = osmo_ss7_route_table_find(s7i, "system"); + OSMO_ASSERT(rtbl && rtbl == s7i->rtable_system); + + lset_a = osmo_ss7_linkset_find_or_create(s7i, "a", 100); + OSMO_ASSERT(lset_a); + lset_b = osmo_ss7_linkset_find_or_create(s7i, "b", 200); + OSMO_ASSERT(lset_b); + + /* route with full mask */ + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 12) == NULL); + rt = osmo_ss7_route_create(rtbl, 12, 0xffff, "a"); + OSMO_ASSERT(rt); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 12) == rt); + osmo_ss7_route_destroy(rt); + + /* route with partial mask */ + rt = osmo_ss7_route_create(rtbl, 8, 0xfff8, "a"); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 8) == rt); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 9) == rt); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 12) == rt); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 15) == rt); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 16) == NULL); + /* insert more specific route for 12, must have higher priority + * than existing one */ + rt12 = osmo_ss7_route_create(rtbl, 12, 0xffff, "b"); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 12) == rt12); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 15) == rt); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 16) == NULL); + /* add a default route, which should have lowest precedence */ + rtdef = osmo_ss7_route_create(rtbl, 0, 0, "a"); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 12) == rt12); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 15) == rt); + OSMO_ASSERT(osmo_ss7_route_find_dpc(rtbl, 16) == rtdef); + + osmo_ss7_route_destroy(rtdef); + osmo_ss7_route_destroy(rt12); + osmo_ss7_route_destroy(rt); + + osmo_ss7_linkset_destroy(lset_a); + osmo_ss7_linkset_destroy(lset_b); +} + +static void test_linkset(void) +{ + struct osmo_ss7_linkset *lset_a, *lset_b; + struct osmo_ss7_link *l_a1, *l_a2; + + printf("Testing SS7 linkset/link\n"); + + OSMO_ASSERT(osmo_ss7_linkset_find_by_name(s7i, "a") == NULL); + OSMO_ASSERT(osmo_ss7_linkset_find_by_name(s7i, "b") == NULL); + + lset_a = osmo_ss7_linkset_find_or_create(s7i, "a", 100); + OSMO_ASSERT(lset_a); + OSMO_ASSERT(osmo_ss7_linkset_find_by_name(s7i, "a") == lset_a); + + lset_b = osmo_ss7_linkset_find_or_create(s7i, "b", 200); + OSMO_ASSERT(lset_b); + OSMO_ASSERT(osmo_ss7_linkset_find_by_name(s7i, "b") == lset_b); + + l_a1 = osmo_ss7_link_find_or_create(lset_a, 1); + OSMO_ASSERT(l_a1); + l_a2 = osmo_ss7_link_find_or_create(lset_a, 2); + OSMO_ASSERT(l_a2); + + /* ID too high */ + OSMO_ASSERT(osmo_ss7_link_find_or_create(lset_a, 1000) == NULL); + /* already exists */ + OSMO_ASSERT(osmo_ss7_link_find_or_create(lset_a, 1) == l_a1); + + osmo_ss7_link_destroy(l_a1); + osmo_ss7_link_destroy(l_a2); + + osmo_ss7_linkset_destroy(lset_a); + osmo_ss7_linkset_destroy(lset_b); +} + +static void test_as(void) +{ + struct osmo_ss7_as *as; + struct osmo_ss7_asp *asp; + + OSMO_ASSERT(osmo_ss7_as_find_by_name(s7i, "as1") == NULL); + as = osmo_ss7_as_find_or_create(s7i, "as1", OSMO_SS7_ASP_PROT_M3UA); + OSMO_ASSERT(as); + OSMO_ASSERT(osmo_ss7_as_find_by_name(s7i, "as1") == as); + OSMO_ASSERT(osmo_ss7_as_find_by_rctx(s7i, 2342) == NULL); + as->cfg.routing_key.context = 2342; + OSMO_ASSERT(osmo_ss7_as_find_by_rctx(s7i, 2342) == as); + OSMO_ASSERT(osmo_ss7_as_add_asp(as, "asp1") == -ENODEV); + + asp = osmo_ss7_asp_find_or_create(s7i, "asp1", 0, M3UA_PORT, OSMO_SS7_ASP_PROT_M3UA); + OSMO_ASSERT(asp); + + OSMO_ASSERT(osmo_ss7_as_has_asp(as, asp) == false); + OSMO_ASSERT(osmo_ss7_as_add_asp(as, "asp1") == 0); + + osmo_ss7_asp_restart(asp); + + /* ask FSM to send ASP-UP.req */ + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_M_ASP_UP_REQ, NULL); + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_ASPSM_ASPUP_ACK, NULL); + osmo_fsm_inst_dispatch(asp->fi, XUA_ASP_E_ASPTM_ASPAC_ACK, NULL); + + OSMO_ASSERT(osmo_ss7_as_del_asp(as, "asp1") == 0); + OSMO_ASSERT(osmo_ss7_as_del_asp(as, "asp2") == -ENODEV); + OSMO_ASSERT(osmo_ss7_as_del_asp(as, "asp1") == -EINVAL); + + osmo_ss7_asp_destroy(asp); + osmo_ss7_as_destroy(as); + OSMO_ASSERT(osmo_ss7_as_find_by_name(s7i, "as1") == NULL); +} + +/*********************************************************************** + * Initialization + ***********************************************************************/ + +static const struct log_info_cat log_info_cat[] = { +}; + +static const struct log_info log_info = { + .cat = log_info_cat, + .num_cat = ARRAY_SIZE(log_info_cat), +}; + +static void init_logging(void) +{ + const int log_cats[] = { DLSS7, DLSUA, DLM3UA, DLSCCP, DLINP }; + unsigned int i; + + osmo_init_logging(&log_info); + + log_set_print_filename(osmo_stderr_target, 0); + + for (i = 0; i < ARRAY_SIZE(log_cats); i++) + log_set_category_filter(osmo_stderr_target, log_cats[i], 1, LOGL_DEBUG); +} + +int main(int argc, char **argv) +{ + init_logging(); + osmo_fsm_log_addr(false); + + /* init */ + osmo_ss7_init(); + s7i = osmo_ss7_instance_find_or_create(NULL, 0); + OSMO_ASSERT(osmo_ss7_instance_find(0) == s7i); + OSMO_ASSERT(osmo_ss7_instance_find(23) == NULL); + + /* test osmo_ss7_pc_is_local() */ + s7i->cfg.primary_pc = 55; + OSMO_ASSERT(osmo_ss7_pc_is_local(s7i, 55) == true); + OSMO_ASSERT(osmo_ss7_pc_is_local(s7i, 23) == false); + + /* further tests */ + test_pc_defaults(); + test_pc_parser_itu(); + test_pc_parser_ansi(); + test_user(); + test_route(); + test_linkset(); + test_as(); + + /* destroy */ + osmo_ss7_instance_destroy(s7i); + OSMO_ASSERT(osmo_ss7_instance_find(0) == NULL); + + exit(0); +} diff --git a/tests/ss7/ss7_test.err b/tests/ss7/ss7_test.err new file mode 100644 index 0000000..8823d1c --- /dev/null +++ b/tests/ss7/ss7_test.err @@ -0,0 +1,49 @@ +0: Creating SS7 Instance +0: Creating Route Table system +0: Point Code Format 8-8-8 is longer than 14 bits, odd? +registering user=testuser for SI 1 with priv 0x1234 +delivering MTP-TRANSFER.ind to user testuser, priv=0x1234 +No MTP-User for SI 1 +Unsupported Primitive +0: Creating Route Table foobar +0: Creating Linkset a +0: Creating Linkset b +0: Destroying Linkset a +0: Destroying Linkset b +0: Creating Linkset a +0: Creating Linkset b +0: Creating Link a:1 +0: Creating Link a:2 +0: Destroying Link a:1 +0: Destroying Link a:2 +0: Destroying Linkset a +0: Destroying Linkset b +0: Creating AS as1 +XUA_AS(as1){AS_DOWN}: Allocated +0: Adding ASP asp1 to AS as1 +0: Restarting ASP asp1 +unable to connect/bind socket: (null):0: Connection refused +connection closed +retrying in 5 seconds... +0: Unable to open stream client for ASP asp1 +XUA_ASP(asp1){ASP_DOWN}: Allocated +XUA_ASP(asp1){ASP_DOWN}: Received Event M-ASP_UP.req +XUA_ASP(asp1){ASP_DOWN}: Received Event ASPSM-ASP_UP_ACK +XUA_ASP(asp1){ASP_DOWN}: T(ack) stopped +XUA_ASP(asp1){ASP_DOWN}: state_chg to ASP_INACTIVE +XUA_ASP(asp1){ASP_INACTIVE}: Received Event M-ASP_ACTIVE.req +XUA_ASP(asp1){ASP_INACTIVE}: Received Event ASPTM-ASP_AC_ACK +XUA_ASP(asp1){ASP_INACTIVE}: T(ack) stopped +XUA_ASP(asp1){ASP_INACTIVE}: state_chg to ASP_ACTIVE +0: Removing ASP asp1 from AS as1 +0: Removing ASP asp1 from AS as1 +0: Destroying ASP asp1 +XUA_ASP(asp1){ASP_ACTIVE}: Terminating (cause = OSMO_FSM_TERM_REQUEST) +XUA_ASP(asp1){ASP_ACTIVE}: Freeing instance +XUA_ASP(asp1){ASP_ACTIVE}: Deallocated +0: Destroying AS as1 +XUA_AS(as1){AS_DOWN}: Terminating (cause = OSMO_FSM_TERM_REQUEST) +XUA_AS(as1){AS_DOWN}: Freeing instance +XUA_AS(as1){AS_DOWN}: Deallocated +0: Destroying SS7 Instance + \ No newline at end of file diff --git a/tests/ss7/ss7_test.ok b/tests/ss7/ss7_test.ok new file mode 100644 index 0000000..8aea63d --- /dev/null +++ b/tests/ss7/ss7_test.ok @@ -0,0 +1,27 @@ +Testing ITU-style point code format +test_pc_transcode(0) -> 0.0.0 -> 0 +test_pc_transcode(1) -> 0.0.1 -> 1 +test_pc_transcode(8) -> 0.1.0 -> 8 +test_pc_transcode(2048) -> 1.0.0 -> 2048 +test_pc_transcode(14336) -> 7.0.0 -> 14336 +test_pc_transcode(100) -> 0.12.4 -> 100 +test_pc_transcode(2342) -> 1.36.6 -> 2342 +test_pc_transcode(16383) -> 7.255.7 -> 16383 +mask /1 => 8192 (0x2000) 4.0.0 +mask 7.0.0 => 14336 (0x3800) 7.0.0 +mask /14 => 16383 (0x3fff) 7.255.7 +Testing ANSI-style point code format +test_pc_transcode(0) -> 0-0-0 -> 0 +test_pc_transcode(1) -> 0-0-1 -> 1 +test_pc_transcode(256) -> 0-1-0 -> 256 +test_pc_transcode(65536) -> 1-0-0 -> 65536 +test_pc_transcode(2048) -> 0-8-0 -> 2048 +test_pc_transcode(16777215) -> 255-255-255 -> 16777215 +test_pc_transcode(100) -> 0-0-100 -> 100 +test_pc_transcode(2342) -> 0-9-38 -> 2342 +mask /1 => 8388608 (0x800000) 128-0-0 +mask /16 => 16776960 (0xffff00) 255-255-0 +mask /24 => 16777215 (0xffffff) 255-255-255 +Testing SS7 user +Testing SS7 routing +Testing SS7 linkset/link diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..ebc43e7 --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,33 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +AT_SETUP([m2ua]) +AT_KEYWORDS([m2ua]) +cat $abs_srcdir/m2ua/m2ua_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/m2ua/m2ua_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([mtp]) +AT_KEYWORDS([mtp]) +cat $abs_srcdir/mtp/mtp_parse_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/mtp/mtp_parse_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([sccp]) +AT_KEYWORDS([sccp]) +cat $abs_srcdir/sccp/sccp_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/sccp/sccp_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([xua]) +AT_KEYWORDS([xua]) +cat $abs_srcdir/xua/xua_test.ok > expout +cat $abs_srcdir/xua/xua_test.err > experr +AT_CHECK([$abs_top_builddir/tests/xua/xua_test], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([ss7]) +AT_KEYWORDS([ss7]) +cat $abs_srcdir/ss7/ss7_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/ss7/ss7_test], [], [expout], [ignore]) +AT_CLEANUP diff --git a/tests/xua/Makefile.am b/tests/xua/Makefile.am new file mode 100644 index 0000000..f56692b --- /dev/null +++ b/tests/xua/Makefile.am @@ -0,0 +1,13 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Wall +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) + +AM_LDFLAGS = -static +LDADD = $(top_builddir)/src/libosmo-sigtran.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS) + +EXTRA_DIST = xua_test.ok xua_test.err + +noinst_HEADERS = sccp_test_data.h +noinst_PROGRAMS = xua_test + +xua_test_SOURCES = xua_test.c sccp_test_data.c diff --git a/tests/xua/sccp_test_data.c b/tests/xua/sccp_test_data.c new file mode 100644 index 0000000..c7c8f27 --- /dev/null +++ b/tests/xua/sccp_test_data.c @@ -0,0 +1,102 @@ +#include + +/* BSC -> MSC */ +const uint8_t bssmap_reset[18] = { + 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe, + 0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04, + 0x01, 0x20, +}; + +/* MSC -> BSC reset ack */ +const uint8_t bssmap_reset_ack[19] = { + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03, + 0x00, 0x01, 0x31, +}; + +/* MSC -> BSC paging, connection less */ +const uint8_t bssmap_paging[32] = { + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10, + 0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10, + 0x02, 0x01, 0x31, 0x97, 0x61, 0x1a, 0x01, 0x06, +}; + +/* MSC -> BSC paging, UDT without PC */ +const uint8_t bssmap_udt[28] = { + 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe, + 0x02, 0x42, 0xfe, 0x10, 0x00, 0x0e, 0x52, 0x08, + 0x08, 0x29, 0x47, 0x10, 0x02, 0x01, 0x31, 0x97, + 0x61, 0x1a, 0x01, 0x06, +}; + +/* BSC -> MSC connection open */ +const uint8_t bssmap_cr[44] = { + 0x01, 0x01, 0x02, 0x03, 0x02, 0x02, 0x04, 0x02, + 0x42, 0xfe, 0x0f, 0x1f, 0x00, 0x1d, 0x57, 0x05, + 0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x12, 0xc3, + 0x50, 0x17, 0x10, 0x05, 0x24, 0x11, 0x03, 0x33, + 0x19, 0xa2, 0x08, 0x29, 0x47, 0x10, 0x02, 0x01, + 0x31, 0x97, 0x61, 0x00 +}; + +/* MSC -> BSC connection confirm */ +const uint8_t bssmap_cc[10] = { + 0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, +}; + +/* MSC -> BSC DTAP + * + * we fake a bit and make it BSC -> MSC... so the + * payload does not make any sense.. + */ +const uint8_t bssmap_dtap[22] = { + 0x06, 0x00, 0x00, 0x03, 0x00, 0x01, 0x0f, 0x01, 0x00, 0x0c, + 0x03, 0x05, 0x5c, 0x08, 0x11, 0x81, 0x33, 0x66, 0x02, 0x13, + 0x45, 0xf4, +}; + +/* MSC -> BSC clear command */ +const uint8_t bssmap_clear[13] = { + 0x06, 0x00, 0x00, 0x03, 0x00, 0x01, 0x06, 0x00, 0x04, 0x20, + 0x04, 0x01, 0x09, +}; + +/* MSC -> BSC released */ +const uint8_t bssmap_released[14] = { + 0x04, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00, 0x01, 0x0f, + 0x02, 0x23, 0x42, 0x00, +}; + +/* BSC -> MSC released */ +const uint8_t bssmap_release_complete[7] = { + 0x05, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03 +}; + +/* message with a SCCP global title */ +const uint8_t tcap_global_title[183] = { + 0x09, + 0x81, 0x03, 0x0d, 0x18, 0x0a, 0x12, 0x07, 0x00, + 0x12, 0x04, 0x53, 0x84, 0x09, 0x00, 0x17, 0x0b, + 0x12, 0x06, 0x00, 0x12, 0x04, 0x44, 0x87, 0x20, + 0x00, 0x20, 0x65, 0x9a, 0x65, 0x81, 0x97, 0x48, + 0x04, 0x26, 0x00, 0x01, 0x98, 0x49, 0x04, 0x51, + 0x01, 0x03, 0xdf, 0x6c, 0x81, 0x88, 0xa1, 0x81, + 0x85, 0x02, 0x01, 0x44, 0x02, 0x01, 0x07, 0x30, + 0x80, 0xa7, 0x80, 0xa0, 0x80, 0x04, 0x01, 0x2b, + 0x30, 0x80, 0x30, 0x12, 0x83, 0x01, 0x10, 0x84, + 0x01, 0x07, 0x85, 0x07, 0x91, 0x44, 0x57, 0x76, + 0x67, 0x16, 0x97, 0x86, 0x01, 0x20, 0x30, 0x06, + 0x82, 0x01, 0x18, 0x84, 0x01, 0x04, 0x00, 0x00, + 0x00, 0x00, 0xa3, 0x06, 0x04, 0x01, 0x42, 0x84, + 0x01, 0x05, 0xa3, 0x06, 0x04, 0x01, 0x51, 0x84, + 0x01, 0x05, 0xa3, 0x06, 0x04, 0x01, 0x31, 0x84, + 0x01, 0x05, 0xa3, 0x09, 0x04, 0x01, 0x12, 0x84, + 0x01, 0x05, 0x82, 0x01, 0x02, 0xa3, 0x09, 0x04, + 0x01, 0x11, 0x84, 0x01, 0x05, 0x81, 0x01, 0x01, + 0xa3, 0x06, 0x04, 0x01, 0x14, 0x84, 0x01, 0x00, + 0xa3, 0x0b, 0x04, 0x01, 0x41, 0x84, 0x01, 0x04, + 0x30, 0x03, 0x83, 0x01, 0x10, 0xa3, 0x0b, 0x04, + 0x01, 0x41, 0x84, 0x01, 0x04, 0x30, 0x03, 0x82, + 0x01, 0x18, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/tests/xua/sccp_test_data.h b/tests/xua/sccp_test_data.h new file mode 100644 index 0000000..3d70549 --- /dev/null +++ b/tests/xua/sccp_test_data.h @@ -0,0 +1,14 @@ +#pragma once +#include + +extern const uint8_t bssmap_reset[18]; +extern const uint8_t bssmap_reset_ack[19]; +extern const uint8_t bssmap_paging[32]; +extern const uint8_t bssmap_udt[28]; +extern const uint8_t bssmap_cr[44]; +extern const uint8_t bssmap_cc[10]; +extern const uint8_t bssmap_dtap[22]; +extern const uint8_t bssmap_clear[13]; +extern const uint8_t bssmap_released[14]; +extern const uint8_t bssmap_release_complete[7]; +extern const uint8_t tcap_global_title[183]; diff --git a/tests/xua/xua_test.c b/tests/xua/xua_test.c new file mode 100644 index 0000000..5a9d0ab --- /dev/null +++ b/tests/xua/xua_test.c @@ -0,0 +1,601 @@ +/* (C) 2011 by Holger Hans Peter Freyther + * (C) 2017 by Harald Welte + * + * 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 . + * + */ + +#include "sccp_test_data.h" + +#include "../src/xua_internal.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +static void test_isup_parse(void) +{ + const uint8_t party0[] = { 0x10, 0x32, 0x54, 0x76 }; + char digits[23] = ""; + int rc; + + rc = osmo_isup_party_parse(digits, party0, ARRAY_SIZE(party0), false); + printf("digits='%s' (%d)\n", digits, rc); + OSMO_ASSERT(rc == 8); + OSMO_ASSERT(!strcmp(digits, "01234567")); + + rc = osmo_isup_party_parse(digits, party0, ARRAY_SIZE(party0), true); + printf("digits='%s' (%d)\n", digits, rc); + OSMO_ASSERT(rc == 7); + OSMO_ASSERT(!strcmp(digits, "0123456")); +} + +/* SCCP Address Parsing */ + +struct sccp_addr_testcase { + struct osmo_sccp_addr expected; + uint8_t *bin; + unsigned int bin_len; +}; + +static uint8_t addr_bin0[] = { 0x92, 0x06, 0x00, 0x12, 0x04, 0x19, 0x99, 0x96, 0x76, 0x39, 0x98 }; +static uint8_t addr_bin1[] = { 0x12, 0x08, 0x00, 0x12, 0x04, 0x19, 0x89, 0x96, 0x92, 0x99, 0x29 }; +static uint8_t addr_bin2[] = { 0x42, 0xfe }; + +static const struct sccp_addr_testcase sccp_addr_testcases[] = { + { + .expected = { + .presence = OSMO_SCCP_ADDR_T_GT | OSMO_SCCP_ADDR_T_SSN, + .ri = OSMO_SCCP_RI_GT, + .gt = { + .gti = OSMO_SCCP_GTI_TT_NPL_ENC_NAI, + .tt = 0, + .npi = OSMO_SCCP_NPI_E164_ISDN, + .nai = OSMO_SCCP_NAI_INTL, + .digits = "919969679389", + }, + .ssn = 6, + }, + .bin = addr_bin0, + .bin_len = ARRAY_SIZE(addr_bin0), + }, { + .expected = { + .presence = OSMO_SCCP_ADDR_T_GT | OSMO_SCCP_ADDR_T_SSN, + .ri = OSMO_SCCP_RI_GT, + .gt = { + .gti = OSMO_SCCP_GTI_TT_NPL_ENC_NAI, + .tt = 0, + .npi = OSMO_SCCP_NPI_E164_ISDN, + .nai = OSMO_SCCP_NAI_INTL, + .digits = "919869299992", + }, + .ssn = 8, + }, + .bin = addr_bin1, + .bin_len = ARRAY_SIZE(addr_bin1), + }, { + .expected = { + .presence = OSMO_SCCP_ADDR_T_SSN, + .ri = OSMO_SCCP_RI_SSN_PC, + .ssn = 254, + }, + .bin = addr_bin2, + .bin_len = ARRAY_SIZE(addr_bin2), + + }, +}; + +static int test_sccp_addr_parse(const struct osmo_sccp_addr *cmp, + const uint8_t *in, unsigned int in_len) +{ + struct osmo_sccp_addr osa; + int rc; + + memset(&osa, 0, sizeof(osa)); + rc = osmo_sccp_addr_parse(&osa, in, in_len); + if (rc < 0) + return rc; + + printf("expected: %s\n", osmo_sccp_addr_dump(cmp)); + printf("parsed: %s\n", osmo_sccp_addr_dump(&osa)); + + if (memcmp(&osa, cmp, sizeof(osa))) { + fprintf(stderr, "expected: %s\n", osmo_hexdump_nospc((uint8_t *)cmp, sizeof(*cmp))); + fprintf(stderr, "parsed: %s\n", osmo_hexdump_nospc((uint8_t *)&osa, sizeof(osa))); + } + + return 0; +} + +static void test_sccp_addr_parser(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sccp_addr_testcases); i++) { + const struct sccp_addr_testcase *tcase = &sccp_addr_testcases[i]; + printf("sccp_addr_parse test case %u\n", i); + test_sccp_addr_parse(&tcase->expected, tcase->bin, tcase->bin_len); + } +} + +struct sccp_addr_enc_testcase { + const char *name; + struct osmo_sccp_addr addr_in; + int rc; + char *exp_out; +}; + +static const struct sccp_addr_enc_testcase enc_cases[] = { + { + .name = "NOGT-PC1024", + .addr_in = { + .ri = OSMO_SCCP_RI_SSN_PC, + .presence = OSMO_SCCP_ADDR_T_PC, + .pc = 1024, + }, + .rc = 3, + .exp_out = "410004", + }, { + .name = "NOGT-PC16383", + .addr_in = { + .ri = OSMO_SCCP_RI_SSN_PC, + .presence = OSMO_SCCP_ADDR_T_PC, + .pc = 16383, + }, + .rc = 3, + .exp_out = "41ff3f", + }, { + .name = "NOGT-PC16383-SSN90", + .addr_in = { + .ri = OSMO_SCCP_RI_SSN_PC, + .presence = OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_SSN, + .pc = 16383, + .ssn = 0x5A, + }, + .rc = 4, + .exp_out = "43ff3f5a", + }, { + .name = "GT-PC16383-NAIONLY", + .addr_in = { + .ri = OSMO_SCCP_RI_SSN_PC, + .presence = OSMO_SCCP_ADDR_T_PC | OSMO_SCCP_ADDR_T_GT, + .pc = 16383, + .gt.gti = OSMO_SCCP_GTI_NAI_ONLY, + .gt.nai = 0x7f, + }, + .rc = 4, + .exp_out = "45ff3f7f", + }, { + .name = "GT-NOPC-NAIONLY", + .addr_in = { + .ri = OSMO_SCCP_RI_GT, + .presence = OSMO_SCCP_ADDR_T_GT, + .gt.gti = OSMO_SCCP_GTI_NAI_ONLY, + .gt.nai = 0x03, + }, + .rc = 2, + .exp_out = "0403", + }, { + .name = "GT-NOPC-TTONLY", + .addr_in = { + .ri = OSMO_SCCP_RI_GT, + .presence = OSMO_SCCP_ADDR_T_GT, + .gt.gti = OSMO_SCCP_GTI_TT_ONLY, + .gt.tt = 0x03, + }, + .rc = -EINVAL, + }, { + .name = "GT-NOPC-TT_NPL_ENC-ODD", + .addr_in = { + .ri = OSMO_SCCP_RI_GT, + .presence = OSMO_SCCP_ADDR_T_GT, + .gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC, + .gt.tt = 0x03, + .gt.npi = 1, + .gt.digits = "123", + }, + .rc = 5, + .exp_out = "0c03112103", + }, { + .name = "GT-NOPC-TT_NPL_ENC-EVEN", + .addr_in = { + .ri = OSMO_SCCP_RI_GT, + .presence = OSMO_SCCP_ADDR_T_GT, + .gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC, + .gt.tt = 0x03, + .gt.npi = 1, + .gt.digits = "1234", + }, + .rc = 5, + .exp_out = "0c03122143", + }, { + .name = "GT-NOPC-TT_NPL_ENC_NAI-EVEN", + .addr_in = { + .ri = OSMO_SCCP_RI_GT, + .presence = OSMO_SCCP_ADDR_T_GT, + .gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC_NAI, + .gt.tt = 0x03, + .gt.npi = 1, + .gt.nai = 4, + .gt.digits = "1234", + }, + .rc = 6, + .exp_out = "100312042143", + }, { + .name = "GT-NOPC-GTI_INVALID", + .addr_in = { + .ri = OSMO_SCCP_RI_GT, + .presence = OSMO_SCCP_ADDR_T_GT, + .gt.gti = 23, + .gt.tt = 0x03, + .gt.npi = 1, + .gt.nai = 4, + .gt.digits = "1234", + }, + .rc = -EINVAL, + }, { + .name = "GT-NOPC-TT_NPL_ENC_NAI-EVEN-NONNUM", + .addr_in = { + .ri = OSMO_SCCP_RI_GT, + .presence = OSMO_SCCP_ADDR_T_GT, + .gt.gti = OSMO_SCCP_GTI_TT_NPL_ENC_NAI, + .gt.tt = 0x03, + .gt.npi = 1, + .gt.nai = 4, + .gt.digits = "1ABF", + }, + .rc = 6, + .exp_out = "10031204a1fb", + }, + +}; + +static void testcase_sccp_addr_encdec(const struct sccp_addr_enc_testcase *tcase) +{ + struct msgb *msg = msgb_alloc(1024, "encdec"); + struct osmo_sccp_addr out; + char *str; + int rc; + + printf("\n=> %s\n", tcase->name); + + printf("input addr: %s\n", osmo_sccp_addr_dump(&tcase->addr_in)); + rc = osmo_sccp_addr_encode(msg, &tcase->addr_in); + printf("rc=%d, expected rc=%d\n", rc, tcase->rc); + OSMO_ASSERT(rc == tcase->rc); + + if (rc <= 0) { + msgb_free(msg); + return; + } + + str = osmo_hexdump_nospc(msg->data, msg->len); + printf("encoded addr: %s\n", str); + if (tcase->exp_out) { + printf("expected addr: %s\n", tcase->exp_out); + OSMO_ASSERT(!strcmp(tcase->exp_out, str)); + } + + rc = osmo_sccp_addr_parse(&out, msg->data, msg->len); + printf("decod addr: %s\n", osmo_sccp_addr_dump(&out)); + + OSMO_ASSERT(!memcmp(&out, &tcase->addr_in, sizeof(out))); + + msgb_free(msg); +} + +static void test_sccp_addr_encdec(void) +{ + int i; + + printf("Testing SCCP Address Encode/Decode\n"); + for (i = 0; i < ARRAY_SIZE(enc_cases); i++) { + testcase_sccp_addr_encdec(&enc_cases[i]); + } + printf("\n"); +} + +/* sccp_addr_testcases[0].expected.gt transcoded into a SUA Global Title IE */ +static const uint8_t expected_sua_gt[] = { + 0x80, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, + 0x0c, 0x00, 0x01, 0x04, 0x19, 0x99, 0x96, 0x76, + 0x39, 0x98, 0x00, 0x00 +}; + +static void test_helpers(void) +{ + struct msgb *msg = msgb_alloc(1024, "foo"); + const struct osmo_sccp_gt *gt_in = &sccp_addr_testcases[0].expected.gt; + struct osmo_sccp_gt gt_out = {}; + + printf("Testing Decoded GT -> SUA encoding\n"); + printf("IN: %s\n", osmo_sccp_gt_dump(gt_in)); + printf(" %s\n", osmo_hexdump_nospc((const unsigned char*)gt_in, sizeof(struct osmo_sccp_gt))); + + /* encode sccp_addr to SUA GT */ + xua_part_add_gt(msg, gt_in); + OSMO_ASSERT(msgb_length(msg) == sizeof(expected_sua_gt)); + OSMO_ASSERT(!memcmp(msg->data, expected_sua_gt, sizeof(expected_sua_gt))); + + /* pull the tag+length value */ + msgb_pull(msg, 4); + + /* parse + compare */ + sua_parse_gt(>_out, msgb_data(msg), msgb_length(msg)); + printf("OUT:%s\n", osmo_sccp_gt_dump(>_out)); + printf(" %s\n", osmo_hexdump_nospc((const unsigned char*)>_out, sizeof(struct osmo_sccp_gt))); + OSMO_ASSERT(!memcmp(gt_in, >_out, sizeof(gt_out))); + + msgb_free(msg); +} + +/* SCCP Message Transcoding */ + +struct sccp2sua_testcase { + const char *name; + struct { + const uint8_t *bin; + unsigned int length; + } sccp; + struct { + struct xua_common_hdr hdr; + const struct xua_msg_part parts[32]; + } sua; +}; + +#define PANDSIZ(x) { x, ARRAY_SIZE(x) } +#define PARTU32(x, data) { .tag = x, .len = 4, .dat = (uint8_t *) data } +#define PARTARR(x, data) { .tag = x, .len = ARRAY_SIZE(data), .dat = (uint8_t *) data } + +const uint32_t sua_proto_class0 = 0; +const uint32_t sua_proto_class2 = 2; +const uint32_t sua_loc_ref_bsc = 0x10203; +const uint32_t sua_loc_ref_msc = 0x00003; +const uint32_t sua_cause0 = 0x00003; +const uint8_t sua_addr_ssn_bssmap[] = { 0x00, 0x02, 0x00, 0x07, 0x80, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0xfe }; +const uint8_t sua_addr_ssn_bssmap_pc1[] = { 0x00, 0x01, 0x00, 0x07, 0x80, 0x02, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0xfe }; +const uint8_t sua_addr_ssn_bssmap_pc92[] = { 0x00, 0x01, 0x00, 0x07, 0x80, 0x02, 0x00, 0x08, 0x00, 0x00, 0x00, 0x5c, 0x80, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0xfe }; + +static const struct sccp2sua_testcase sccp2sua_testcases[] = { + { + .name = "BSSMAP-RESET", + .sccp = PANDSIZ(bssmap_reset), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT), + .parts = { + PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class0), + PARTARR(SUA_IEI_DEST_ADDR, &sua_addr_ssn_bssmap), + PARTARR(SUA_IEI_SRC_ADDR, &sua_addr_ssn_bssmap), + }, + }, + }, { + .name = "BSSMAP-RESET-ACK", + .sccp = PANDSIZ(bssmap_reset_ack), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT), + .parts = { + PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class0), + PARTARR(SUA_IEI_DEST_ADDR, &sua_addr_ssn_bssmap_pc1), + PARTARR(SUA_IEI_SRC_ADDR, &sua_addr_ssn_bssmap_pc92), + }, + }, + }, { + .name = "BSSMAP-PAGING", + .sccp = PANDSIZ(bssmap_paging), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT), + .parts = { + PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class0), + PARTARR(SUA_IEI_DEST_ADDR, &sua_addr_ssn_bssmap_pc1), + PARTARR(SUA_IEI_SRC_ADDR, &sua_addr_ssn_bssmap_pc92), + }, + }, + }, { + .name = "BSSMAP-UDT", + .sccp = PANDSIZ(bssmap_udt), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT), + .parts = { + PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class0), + PARTARR(SUA_IEI_DEST_ADDR, &sua_addr_ssn_bssmap), + PARTARR(SUA_IEI_SRC_ADDR, &sua_addr_ssn_bssmap), + }, + }, + }, { + .name = "BSSMAP-CR", + .sccp = PANDSIZ(bssmap_cr), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CORE), + .parts = { + PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class2), + PARTU32(SUA_IEI_SRC_REF, &sua_loc_ref_bsc), + PARTARR(SUA_IEI_DEST_ADDR, &sua_addr_ssn_bssmap), + }, + }, + }, { + .name = "BSSMAP-CC", + .sccp = PANDSIZ(bssmap_cc), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK), + .parts = { + PARTU32(SUA_IEI_PROTO_CLASS, &sua_proto_class2), + PARTU32(SUA_IEI_SRC_REF, &sua_loc_ref_msc), + PARTARR(SUA_IEI_DEST_ADDR, &sua_loc_ref_bsc), + }, + }, + }, { + .name = "BSSMAP-DTAP", + .sccp = PANDSIZ(bssmap_dtap), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT), + .parts = { + PARTU32(SUA_IEI_SRC_REF, &sua_loc_ref_msc), + }, + }, + }, { + .name = "BSSMAP-CLEAR", + .sccp = PANDSIZ(bssmap_clear), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT), + .parts = { + PARTU32(SUA_IEI_SRC_REF, &sua_loc_ref_msc), + }, + }, + }, { + .name = "BSSMAP-RELEASED", + .sccp = PANDSIZ(bssmap_released), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE), + .parts = { + PARTU32(SUA_IEI_DEST_REF, &sua_loc_ref_msc), + PARTU32(SUA_IEI_SRC_REF, &sua_loc_ref_bsc), + PARTU32(SUA_IEI_CAUSE, &sua_cause0), + }, + }, + }, { + .name = "BSSMAP-RELEASE_COMPLETE", + .sccp = PANDSIZ(bssmap_release_complete), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO), + .parts = { + PARTU32(SUA_IEI_DEST_REF, &sua_loc_ref_bsc), + PARTU32(SUA_IEI_SRC_REF, &sua_loc_ref_msc), + }, + }, + }, { + .name = "TCAP", + .sccp = PANDSIZ(tcap_global_title), + .sua = { + .hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT), + .parts = { + }, + }, + }, +}; + +static void test_sccp2sua_case(const struct sccp2sua_testcase *tcase) +{ + struct xua_msg *xua; + struct msgb *msg = msgb_alloc(300, "SCCP2SUA Test Input"); + struct msgb *msg2; + + printf("\n=> %s\n", tcase->name); + msg->l2h = msgb_put(msg, tcase->sccp.length); + memcpy(msg->l2h, tcase->sccp.bin, tcase->sccp.length); + printf("SCCP Input: %s\n", msgb_hexdump(msg)); + printf("Transcoding message SCCP -> XUA\n"); + xua = osmo_sccp_to_xua(msg); + OSMO_ASSERT(xua); + + printf("Decoded SUA: "); + printf("%s\n", xua_msg_dump(xua, &xua_dialect_sua)); + + printf("Re-Encoding decoded SUA to SCCP\n"); + msg2 = osmo_sua_to_sccp(xua); + OSMO_ASSERT(msg2); + /* Re-encode xUA to SCCP */ + printf("SCCP Output: %s\n", msgb_hexdump(msg2)); + + if (msgb_length(msg) != msgb_length(msg2) || + memcmp(msgb_data(msg), msgb_data(msg2), msgb_length(msg))) + printf("Input != re-encoded output!\n"); + + /* free related data */ + msgb_free(msg); + msgb_free(msg2); + xua_msg_free(xua); +} + +static void test_sccp2sua(void) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(sccp2sua_testcases); i++) { + test_sccp2sua_case(&sccp2sua_testcases[i]); + } +} + +/* M3UA message with RKM-REG contents */ +static const uint8_t rkm_reg[] = { + 0x01, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x04, 0x00, 0x0e, 0x4d, 0x33, 0x55, 0x41, + 0x20, 0x72, 0x6f, 0x63, 0x6b, 0x73, 0x00, 0x00, 0x02, 0x07, 0x00, 0x14, 0x02, 0x0a, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x0b, 0x00, 0x08, 0x00, 0x00, 0x00, 0x17, +}; + +static void test_rkm(void) +{ + struct xua_msg *xua, *nested; + struct xua_msg_part *rkey; + + printf("Parsing M3UA Message\n"); + xua = xua_from_msg(M3UA_VERSION, sizeof(rkm_reg), (uint8_t *)rkm_reg); + OSMO_ASSERT(xua); + OSMO_ASSERT(xua->hdr.msg_class == M3UA_MSGC_RKM); + OSMO_ASSERT(xua->hdr.msg_type == M3UA_RKM_REG_REQ); + OSMO_ASSERT(xua_msg_find_tag(xua, M3UA_IEI_INFO_STRING)); + rkey = xua_msg_find_tag(xua, M3UA_IEI_ROUT_KEY); + OSMO_ASSERT(rkey); + OSMO_ASSERT(rkey->len == 16); + + printf("Parsing Nested M3UA Routing Key IE\n"); + nested = xua_from_nested(rkey); + OSMO_ASSERT(nested); + OSMO_ASSERT(xua_msg_get_u32(nested, M3UA_IEI_LOC_RKEY_ID) == 1); + OSMO_ASSERT(xua_msg_get_u32(nested, M3UA_IEI_DEST_PC) == 23); +} + + +static const struct log_info_cat default_categories[] = { + [0] = { + .name = "DSCCP", + .description = "DSCP", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +static const struct log_info log_info = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +int main(int argc, char **argv) +{ + struct log_target *stderr_target; + log_init(&log_info, NULL); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_use_color(stderr_target, 0); + log_set_print_filename(stderr_target, 0); + + test_isup_parse(); + test_sccp_addr_parser(); + test_helpers(); + test_sccp2sua(); + test_rkm(); + test_sccp_addr_encdec(); + + printf("All tests passed.\n"); + return 0; +} diff --git a/tests/xua/xua_test.err b/tests/xua/xua_test.err new file mode 100644 index 0000000..17870ee --- /dev/null +++ b/tests/xua/xua_test.err @@ -0,0 +1,2 @@ +Unsupported Translation Type 2requested +Unsupported GTI 23 requested diff --git a/tests/xua/xua_test.ok b/tests/xua/xua_test.ok new file mode 100644 index 0000000..6b0cb33 --- /dev/null +++ b/tests/xua/xua_test.ok @@ -0,0 +1,208 @@ +digits='01234567' (8) +digits='0123456' (7) +sccp_addr_parse test case 0 +expected: RI=1,SSN=6,GTI=4,GT=(TT=0,NPL=1,NAI=4,DIG=919969679389) +parsed: RI=1,SSN=6,GTI=4,GT=(TT=0,NPL=1,NAI=4,DIG=919969679389) +sccp_addr_parse test case 1 +expected: RI=1,SSN=8,GTI=4,GT=(TT=0,NPL=1,NAI=4,DIG=919869299992) +parsed: RI=1,SSN=8,GTI=4,GT=(TT=0,NPL=1,NAI=4,DIG=919869299992) +sccp_addr_parse test case 2 +expected: RI=2,SSN=254,GTI=0 +parsed: RI=2,SSN=254,GTI=0 +Testing Decoded GT -> SUA encoding +IN: TT=0,NPL=1,NAI=4,DIG=919969679389 + 0400000001000000040000003931393936393637393338390000000000000000000000000000000000000000 +OUT:TT=0,NPL=1,NAI=4,DIG=919969679389 + 0400000001000000040000003931393936393637393338390000000000000000000000000000000000000000 + +=> BSSMAP-RESET +SCCP Input: [L2]> 09 00 03 05 07 02 42 fe 02 42 fe 06 00 04 30 04 01 20 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), + PART(T=Protocol Class,L=4,D=00000000), + PART(T=Destination Address,L=12,D=0002000180030008000000fe), + PART(T=Source Address,L=12,D=0002000180030008000000fe), + PART(T=Data,L=6,D=000430040120) +Re-Encoding decoded SUA to SCCP +SCCP Output: 09 00 03 05 07 02 42 fe 02 42 fe 06 00 04 30 04 01 20 + +=> BSSMAP-RESET-ACK +SCCP Input: [L2]> 09 00 03 07 0b 04 43 01 00 fe 04 43 5c 00 fe 03 00 01 31 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), + PART(T=Protocol Class,L=4,D=00000000), + PART(T=Destination Address,L=20,D=00020003800200080000000180030008000000fe), + PART(T=Source Address,L=20,D=00020003800200080000005c80030008000000fe), + PART(T=Data,L=3,D=000131) +Re-Encoding decoded SUA to SCCP +SCCP Output: 09 00 03 07 0b 04 43 01 00 fe 04 43 5c 00 fe 03 00 01 31 + +=> BSSMAP-PAGING +SCCP Input: [L2]> 09 00 03 07 0b 04 43 01 00 fe 04 43 5c 00 fe 10 00 0e 52 08 08 29 47 10 02 01 31 97 61 1a 01 06 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), + PART(T=Protocol Class,L=4,D=00000000), + PART(T=Destination Address,L=20,D=00020003800200080000000180030008000000fe), + PART(T=Source Address,L=20,D=00020003800200080000005c80030008000000fe), + PART(T=Data,L=16,D=000e52080829471002013197611a0106) +Re-Encoding decoded SUA to SCCP +SCCP Output: 09 00 03 07 0b 04 43 01 00 fe 04 43 5c 00 fe 10 00 0e 52 08 08 29 47 10 02 01 31 97 61 1a 01 06 + +=> BSSMAP-UDT +SCCP Input: [L2]> 09 00 03 05 07 02 42 fe 02 42 fe 10 00 0e 52 08 08 29 47 10 02 01 31 97 61 1a 01 06 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), + PART(T=Protocol Class,L=4,D=00000000), + PART(T=Destination Address,L=12,D=0002000180030008000000fe), + PART(T=Source Address,L=12,D=0002000180030008000000fe), + PART(T=Data,L=16,D=000e52080829471002013197611a0106) +Re-Encoding decoded SUA to SCCP +SCCP Output: 09 00 03 05 07 02 42 fe 02 42 fe 10 00 0e 52 08 08 29 47 10 02 01 31 97 61 1a 01 06 + +=> BSSMAP-CR +SCCP Input: [L2]> 01 01 02 03 02 02 04 02 42 fe 0f 1f 00 1d 57 05 08 00 72 f4 80 20 12 c3 50 17 10 05 24 11 03 33 19 a2 08 29 47 10 02 01 31 97 61 00 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CO:CORE,V=0,LEN=0), + PART(T=Protocol Class,L=4,D=00000002), + PART(T=Source Reference,L=4,D=00010203), + PART(T=Destination Address,L=12,D=0002000180030008000000fe), + PART(T=Data,L=31,D=001d5705080072f4802012c3501710052411033319a2082947100201319761) +Re-Encoding decoded SUA to SCCP +SCCP Output: 01 01 02 03 02 02 04 02 42 fe 0f 1f 00 1d 57 05 08 00 72 f4 80 20 12 c3 50 17 10 05 24 11 03 33 19 a2 08 29 47 10 02 01 31 97 61 00 + +=> BSSMAP-CC +SCCP Input: [L2]> 02 01 02 03 00 00 03 02 01 00 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CO:COAK,V=0,LEN=0), + PART(T=Protocol Class,L=4,D=00000002), + PART(T=Destination Reference,L=4,D=00010203), + PART(T=Source Reference,L=4,D=00000003) +Re-Encoding decoded SUA to SCCP +SCCP Output: 02 01 02 03 00 00 03 02 01 00 + +=> BSSMAP-DTAP +SCCP Input: [L2]> 06 00 00 03 00 01 0f 01 00 0c 03 05 5c 08 11 81 33 66 02 13 45 f4 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CO:CODT,V=0,LEN=0), + PART(T=Destination Reference,L=4,D=00000003), + PART(T=Segmentation,L=4,D=00000000), + PART(T=Data,L=15,D=01000c03055c0811813366021345f4) +Re-Encoding decoded SUA to SCCP +SCCP Output: 06 00 00 03 00 01 0f 01 00 0c 03 05 5c 08 11 81 33 66 02 13 45 f4 + +=> BSSMAP-CLEAR +SCCP Input: [L2]> 06 00 00 03 00 01 06 00 04 20 04 01 09 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CO:CODT,V=0,LEN=0), + PART(T=Destination Reference,L=4,D=00000003), + PART(T=Segmentation,L=4,D=00000000), + PART(T=Data,L=6,D=000420040109) +Re-Encoding decoded SUA to SCCP +SCCP Output: 06 00 00 03 00 01 06 00 04 20 04 01 09 + +=> BSSMAP-RELEASED +SCCP Input: [L2]> 04 00 00 03 01 02 03 00 01 0f 02 23 42 00 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CO:RELRE,V=0,LEN=0), + PART(T=Destination Reference,L=4,D=00000003), + PART(T=Source Reference,L=4,D=00010203), + PART(T=Cause,L=4,D=00000300), + PART(T=Data,L=2,D=2342) +Re-Encoding decoded SUA to SCCP +SCCP Output: 04 00 00 03 01 02 03 00 01 0f 02 23 42 00 + +=> BSSMAP-RELEASE_COMPLETE +SCCP Input: [L2]> 05 01 02 03 00 00 03 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CO:RELCO,V=0,LEN=0), + PART(T=Destination Reference,L=4,D=00010203), + PART(T=Source Reference,L=4,D=00000003) +Re-Encoding decoded SUA to SCCP +SCCP Output: 05 01 02 03 00 00 03 + +=> TCAP +SCCP Input: [L2]> 09 81 03 0d 18 0a 12 07 00 12 04 53 84 09 00 17 0b 12 06 00 12 04 44 87 20 00 20 65 9a 65 81 97 48 04 26 00 01 98 49 04 51 01 03 df 6c 81 88 a1 81 85 02 01 44 02 01 07 30 80 a7 80 a0 80 04 01 2b 30 80 30 12 83 01 10 84 01 07 85 07 91 44 57 76 67 16 97 86 01 20 30 06 82 01 18 84 01 04 00 00 00 00 a3 06 04 01 42 84 01 05 a3 06 04 01 51 84 01 05 a3 06 04 01 31 84 01 05 a3 09 04 01 12 84 01 05 82 01 02 a3 09 04 01 11 84 01 05 81 01 01 a3 06 04 01 14 84 01 00 a3 0b 04 01 41 84 01 04 30 03 83 01 10 a3 0b 04 01 41 84 01 04 30 03 82 01 18 00 00 00 00 +Transcoding message SCCP -> XUA +Decoded SUA: HDR=(CL:CLDT,V=0,LEN=0), + PART(T=Protocol Class,L=4,D=00000081), + PART(T=Destination Address,L=32,D=0001000580010014000000040a00010453840900170000008003000800000007), + PART(T=Source Address,L=32,D=0001000580010014000000040c00010444872000206500008003000800000006), + PART(T=Data,L=154,D=6581974804260001984904510103df6c8188a181850201440201073080a780a08004012b30803012830110840107850791445776671697860120300682011884010400000000a306040142840105a306040151840105a306040131840105a309040112840105820102a309040111840105810101a306040114840100a30b0401418401043003830110a30b040141840104300382011800000000) +Re-Encoding decoded SUA to SCCP +SCCP Output: 09 81 03 0d 18 0a 12 07 00 12 04 53 84 09 00 17 0b 12 06 00 12 04 44 87 20 00 20 65 9a 65 81 97 48 04 26 00 01 98 49 04 51 01 03 df 6c 81 88 a1 81 85 02 01 44 02 01 07 30 80 a7 80 a0 80 04 01 2b 30 80 30 12 83 01 10 84 01 07 85 07 91 44 57 76 67 16 97 86 01 20 30 06 82 01 18 84 01 04 00 00 00 00 a3 06 04 01 42 84 01 05 a3 06 04 01 51 84 01 05 a3 06 04 01 31 84 01 05 a3 09 04 01 12 84 01 05 82 01 02 a3 09 04 01 11 84 01 05 81 01 01 a3 06 04 01 14 84 01 00 a3 0b 04 01 41 84 01 04 30 03 83 01 10 a3 0b 04 01 41 84 01 04 30 03 82 01 18 00 00 00 00 +Parsing M3UA Message +Parsing Nested M3UA Routing Key IE +Testing SCCP Address Encode/Decode + +=> NOGT-PC1024 +input addr: RI=2,PC=1024,GTI=0 +rc=3, expected rc=3 +encoded addr: 410004 +expected addr: 410004 +decod addr: RI=2,PC=1024,GTI=0 + +=> NOGT-PC16383 +input addr: RI=2,PC=16383,GTI=0 +rc=3, expected rc=3 +encoded addr: 41ff3f +expected addr: 41ff3f +decod addr: RI=2,PC=16383,GTI=0 + +=> NOGT-PC16383-SSN90 +input addr: RI=2,PC=16383,SSN=90,GTI=0 +rc=4, expected rc=4 +encoded addr: 43ff3f5a +expected addr: 43ff3f5a +decod addr: RI=2,PC=16383,SSN=90,GTI=0 + +=> GT-PC16383-NAIONLY +input addr: RI=2,PC=16383,GTI=1,GT=() +rc=4, expected rc=4 +encoded addr: 45ff3f7f +expected addr: 45ff3f7f +decod addr: RI=2,PC=16383,GTI=1,GT=() + +=> GT-NOPC-NAIONLY +input addr: RI=1,GTI=1,GT=() +rc=2, expected rc=2 +encoded addr: 0403 +expected addr: 0403 +decod addr: RI=1,GTI=1,GT=() + +=> GT-NOPC-TTONLY +input addr: RI=1,GTI=2,GT=(TT=3,DIG=) +rc=-22, expected rc=-22 + +=> GT-NOPC-TT_NPL_ENC-ODD +input addr: RI=1,GTI=3,GT=(TT=3,NPL=1,DIG=123) +rc=5, expected rc=5 +encoded addr: 0c03112103 +expected addr: 0c03112103 +decod addr: RI=1,GTI=3,GT=(TT=3,NPL=1,DIG=123) + +=> GT-NOPC-TT_NPL_ENC-EVEN +input addr: RI=1,GTI=3,GT=(TT=3,NPL=1,DIG=1234) +rc=5, expected rc=5 +encoded addr: 0c03122143 +expected addr: 0c03122143 +decod addr: RI=1,GTI=3,GT=(TT=3,NPL=1,DIG=1234) + +=> GT-NOPC-TT_NPL_ENC_NAI-EVEN +input addr: RI=1,GTI=4,GT=(TT=3,NPL=1,NAI=4,DIG=1234) +rc=6, expected rc=6 +encoded addr: 100312042143 +expected addr: 100312042143 +decod addr: RI=1,GTI=4,GT=(TT=3,NPL=1,NAI=4,DIG=1234) + +=> GT-NOPC-GTI_INVALID +input addr: RI=1,GTI=23,GT=(DIG=1234) +rc=-22, expected rc=-22 + +=> GT-NOPC-TT_NPL_ENC_NAI-EVEN-NONNUM +input addr: RI=1,GTI=4,GT=(TT=3,NPL=1,NAI=4,DIG=1ABF) +rc=6, expected rc=6 +encoded addr: 10031204a1fb +expected addr: 10031204a1fb +decod addr: RI=1,GTI=4,GT=(TT=3,NPL=1,NAI=4,DIG=1ABF) + +All tests passed. -- cgit v1.2.3 From b100c26324bc19d9d0b81ab5f2d34880fcc17780 Mon Sep 17 00:00:00 2001 From: Thorsten Alteholz Date: Fri, 20 Apr 2018 18:39:43 +0200 Subject: Import libosmo-sccp_0.8.1-5.debian.tar.xz [dgit import tarball libosmo-sccp 0.8.1-5 libosmo-sccp_0.8.1-5.debian.tar.xz] --- changelog | 69 ++++++++++++++ compat | 1 + control | 130 +++++++++++++++++++++++++++ copyright | 52 +++++++++++ gbp.conf | 2 + libosmo-sccp-dev.install | 7 ++ libosmo-sigtran-dev.install | 3 + libosmo-sigtran-doc.doc-base | 8 ++ libosmo-sigtran-doc.install | 1 + libosmo-sigtran0.install | 1 + libosmo-sigtran0.symbols | 109 ++++++++++++++++++++++ libosmomtp0.install | 1 + libosmomtp0.symbols | 3 + libosmosccp0.install | 1 + libosmosccp0.symbols | 34 +++++++ libosmoxua0.install | 1 + libosmoxua0.symbols | 23 +++++ osmo-stp.1 | 39 ++++++++ osmo-stp.install | 1 + osmo-stp.manpages | 1 + osmo-stp.service | 1 + patches/01_make_library_shared.patch | 61 +++++++++++++ patches/02_set_version_explicitly.patch | 16 ++++ patches/03_fix_typo.patch | 30 +++++++ patches/04_change_systemd_service_file.patch | 14 +++ patches/series | 4 + rules | 32 +++++++ source/format | 1 + watch | 2 + 29 files changed, 648 insertions(+) create mode 100644 changelog create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 gbp.conf create mode 100644 libosmo-sccp-dev.install create mode 100644 libosmo-sigtran-dev.install create mode 100644 libosmo-sigtran-doc.doc-base create mode 100644 libosmo-sigtran-doc.install create mode 100644 libosmo-sigtran0.install create mode 100644 libosmo-sigtran0.symbols create mode 100644 libosmomtp0.install create mode 100644 libosmomtp0.symbols create mode 100644 libosmosccp0.install create mode 100644 libosmosccp0.symbols create mode 100644 libosmoxua0.install create mode 100644 libosmoxua0.symbols create mode 100644 osmo-stp.1 create mode 100644 osmo-stp.install create mode 100644 osmo-stp.manpages create mode 120000 osmo-stp.service create mode 100644 patches/01_make_library_shared.patch create mode 100644 patches/02_set_version_explicitly.patch create mode 100644 patches/03_fix_typo.patch create mode 100644 patches/04_change_systemd_service_file.patch create mode 100644 patches/series create mode 100755 rules create mode 100644 source/format create mode 100644 watch diff --git a/changelog b/changelog new file mode 100644 index 0000000..e169c21 --- /dev/null +++ b/changelog @@ -0,0 +1,69 @@ +libosmo-sccp (0.8.1-5) unstable; urgency=medium + + * debian/*: libosmo-sigtran.so has own dev package now + + -- Thorsten Alteholz Fri, 20 Apr 2018 18:39:43 +0200 + +libosmo-sccp (0.8.1-4) unstable; urgency=medium + + * debian/control: add lib dependency to dev package + + -- Thorsten Alteholz Thu, 19 Apr 2018 19:39:43 +0200 + +libosmo-sccp (0.8.1-3) unstable; urgency=medium + + * debian/rules: some tests on BE fail + * add .so files to dev package + + -- Thorsten Alteholz Mon, 16 Apr 2018 19:47:43 +0100 + +libosmo-sccp (0.8.1-2) unstable; urgency=medium + + * move to unstable + * debian/control: add salsa URLs + * debian/control: use dh11 + * debian/control: bump standard to 4.1.4 (no changes) + * debian/control: fix typo on dependencies (Closes: #891447) + (thanks to Andreas Beckmann) + * debian/control: add conflicts for old -dev package (Closes: #890857) + (thanks to Andreas Beckmann) + + -- Thorsten Alteholz Sun, 15 Apr 2018 18:47:14 +0100 + +libosmo-sccp (0.8.1-1) experimental; urgency=medium + + * New upstream release + + -- Kira Obrezkova Mon, 22 Jan 2018 13:52:29 +0300 + +libosmo-sccp (0.7.0-4) unstable; urgency=medium + + * debian/control: add myself to uploaders + * debian/watch: change watch file + * debian/control: bump standard to 4.1.2 (no changes) + + -- Kira Obrezkova Sat, 06 Jan 2018 10:43:48 +0300 + +libosmo-sccp (0.7.0-3) unstable; urgency=medium + + * debian/control: add myself to uploaders + * debian/control: move package to debian-mobcom and change URLs + * debian/control: change maintainer to debian-mobcom-maintainers + * debian/control: use dh10 + * debian/control: bump standard to 4.1.0 (no changes) + * debian/control: remove redundant dependency of dh-autoreconf + + -- Thorsten Alteholz Mon, 11 Sep 2017 19:52:59 +0200 + +libosmo-sccp (0.7.0-2) unstable; urgency=medium + + * Fix FTBFS for big-endian architectures + * d/control: Standards 3.9.7 - no needed changes + + -- Ruben Undheim Sat, 20 Feb 2016 18:04:09 +0100 + +libosmo-sccp (0.7.0-1) unstable; urgency=low + + * Initial release (Closes: #813294) + + -- Ruben Undheim Fri, 05 Feb 2016 16:23:37 +0100 diff --git a/compat b/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +11 diff --git a/control b/control new file mode 100644 index 0000000..3cd0f07 --- /dev/null +++ b/control @@ -0,0 +1,130 @@ +Source: libosmo-sccp +Maintainer: Debian Mobcom Maintainers +Uploaders: Ruben Undheim + , Thorsten Alteholz + , Kira Obrezkova +Section: libs +Priority: optional +Build-Depends: debhelper (>= 11), + pkg-config, + libosmocore-dev (>= 0.10.0), + autoconf, + automake, + libtool, + git, + doxygen, + libdpkg-perl, + libosmo-netif-dev (>= 0.1.0), + libsctp-dev +Standards-Version: 4.1.4 +Vcs-Browser: https://salsa.debian.org/debian-mobcom-team/libosmo-sccp +Vcs-Git: https://salsa.debian.org/debian-mobcom-team/libosmo-sccp.git +Homepage: http://cgit.osmocom.org/libosmo-sccp/ + +Package: libosmo-sccp-dev +Architecture: any +Depends: ${misc:Depends} + , libosmosccp0 (= ${binary:Version}), + , libosmoxua0 (= ${binary:Version}), +Multi-Arch: same +Section: libdevel +Description: Development files for libsccp, libmtp and libxua + SCCP is a network layer protocol that provides extended routing, flow + control, segmentation, connection-orientation, and error correction + facilities in Signaling System 7 telecommunications networks. It is heavily + used in cellular networks such as GSM. This package contains the development + files for the library. + +Package: libosmo-sigtran0 +Architecture: any +Multi-Arch: same +Depends: ${shlibs:Depends}, + ${misc:Depends} +Pre-Depends: ${misc:Pre-Depends} +Description: Osmocom SIGTRAN library (SCCP, SUA, M3UA and more) + This is a shared library containing SS7/SIGTRAN related functionality, + including connection-less and connection-oriented SCCP as per ITU-T Q.71x, + M3UA (MTP3 User Adaptation) according to IETF RFC4666, SUA (SCCP User + Adpatation) according to IETF RFC3868 as well as MTP-level routing function + and handling of signaling links, linksets, ASPs, SGs, etc. + +Package: libosmo-sigtran-doc +Architecture: all +Section: doc +Depends: ${misc:Depends}, + libosmo-sigtran0 (>= ${binary:Version}), + libjs-jquery +Description: Documentation for the Osmocom SIGTRAN library + libosmo-sigtran is a shared library containing SS7/SIGTRAN related + functionality, including connection-less and connection-oriented SCCP as per + ITU-T Q.71x, M3UA (MTP3 User Adaptation) according to IETF RFC4666, SUA + (SCCP User Adpatation) according to IETF RFC3868 as well as MTP-level routing + function and handling of signaling links, linksets, ASPs, SGs, etc. This + package provides documentation for the Osmocom SIGTRAN Library. + +Package: libosmo-sigtran-dev +Architecture: any +Multi-Arch: same +Section: libdevel +Breaks: libosmo-sccp-dev (<< 0.8.0) +Depends: ${misc:Depends}, + libosmo-sigtran0 (= ${binary:Version}), + libosmocore-dev, + libosmo-netif-dev +Description: Development headers for the Osmocom SIGTRAN library + libosmo-sigtran is a shared library containing SS7/SIGTRAN related + functionality, including connection-less and connection-oriented SCCP as per + ITU-T Q.71x, M3UA (MTP3 User Adaptation) according to IETF RFC4666, SUA + (SCCP User Adpatation) according to IETF RFC3868 as well as MTP-level routing + function and handling of signaling links, linksets, ASPs, SGs, etc. + +Package: libosmosccp0 +Architecture: any +Multi-Arch: same +Depends: ${shlibs:Depends}, + ${misc:Depends} +Pre-Depends: ${misc:Pre-Depends} +Description: Library for Signalling Connection Control Part (SCCP) + SCCP is a network layer protocol that provides extended routing, flow control, + segmentation, connection-orientation, and error correction facilities in + Signaling System 7 telecommunications networks. + It is heavily used in cellular networks such as GSM. + +Package: libosmomtp0 +Architecture: any +Multi-Arch: same +Depends: ${shlibs:Depends}, + ${misc:Depends} +Pre-Depends: ${misc:Pre-Depends} +Description: Message Transfer Part for Signaling System 7 + MTP is part of the Signaling System 7 used for communication in Public + Switched Telephone Networks. MTP is responsible for reliable, unduplicated and + in-sequence transport of SS7 messages between communication partners. + This library is closely related to the libosmosccp library. + +Package: libosmoxua0 +Architecture: any +Multi-Arch: same +Depends: ${shlibs:Depends}, + ${misc:Depends} +Pre-Depends: ${misc:Pre-Depends} +Description: Osmocom SCCP support library for generating and parsing messages + SCCP is a network layer protocol that provides extended routing, flow control, + segmentation, connection-orientation, and error correction facilities in + Signaling System 7 telecommunications networks. + It is heavily used in cellular networks such as GSM. + This library is used to provide generating and parsing message facilities. + +Package: osmo-stp +Architecture: any +Multi-Arch: allowed +Section: net +Depends: libosmo-sigtran0, + ${shlibs:Depends}, + ${misc:Depends} +Description: Osmocom SIGTRAN STP (Signaling Transfer Point) + This is the Osmocom (Open Source Mobile Communications) implementation of a + Signaling Transfer Point (STP) for SS7/SIGTRAN telecommunication networks. At + this point it is a very minimal implementation, missing lots of the + functionality usually present in a STP, such as Global Title Routing, Global + Title Translation. diff --git a/copyright b/copyright new file mode 100644 index 0000000..9420ca9 --- /dev/null +++ b/copyright @@ -0,0 +1,52 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Files-Excluded: debian/* specs/* +Upstream-Name: libopen-sccp +Source: http://cgit.osmocom.org/libosmo-sccp/ + +Files: * +Copyright: 2009-2010,2013 On-Waves + 2009-2011,2013 Holger Hans Peter Freyther + 2010 Harald Welte +License: GPL-2+ + +Files: tests/m2ua/m2ua_test.c +Copyright: 2011 Holger Hans Peter Freyther +License: AGPL-3+ + +Files: debian/* +Copyright: 2010-2015 Harald Welte + 2016 Ruben Undheim +License: GPL-2+ + + +License: GPL-2+ + This package 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, see . + . + On Debian systems, the complete text of the GNU General Public + License version 2.1 can be found in "/usr/share/common-licenses/GPL-2". + + +License: AGPL-3+ + 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 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 . diff --git a/gbp.conf b/gbp.conf new file mode 100644 index 0000000..cec628c --- /dev/null +++ b/gbp.conf @@ -0,0 +1,2 @@ +[DEFAULT] +pristine-tar = True diff --git a/libosmo-sccp-dev.install b/libosmo-sccp-dev.install new file mode 100644 index 0000000..e9d1cd3 --- /dev/null +++ b/libosmo-sccp-dev.install @@ -0,0 +1,7 @@ +usr/include/osmocom/sccp +usr/lib/*/libosmomtp.so +usr/lib/*/libosmosccp.so +usr/lib/*/libosmoxua.so +usr/lib/*/pkgconfig/libosmo-sccp.pc +usr/lib/*/pkgconfig/libosmo-mtp.pc +usr/lib/*/pkgconfig/libosmo-xua.pc diff --git a/libosmo-sigtran-dev.install b/libosmo-sigtran-dev.install new file mode 100644 index 0000000..fd6ff7f --- /dev/null +++ b/libosmo-sigtran-dev.install @@ -0,0 +1,3 @@ +usr/include/osmocom/sigtran +usr/lib/*/libosmo-sigtran*.so +usr/lib/*/pkgconfig/libosmo-sigtran.pc diff --git a/libosmo-sigtran-doc.doc-base b/libosmo-sigtran-doc.doc-base new file mode 100644 index 0000000..01ca21c --- /dev/null +++ b/libosmo-sigtran-doc.doc-base @@ -0,0 +1,8 @@ +Document: libsigtran-doc +Title: Documentation for SIGTRAN library +Abstract: Documentation for the Osmocom SIGTRAN library +Section: Network/Communication + +Format: HTML +Index: /usr/share/doc/libosmo-sccp/sigtran/html/index.html +Files: /usr/share/doc/libosmo-sccp/sigtran/html/*.html diff --git a/libosmo-sigtran-doc.install b/libosmo-sigtran-doc.install new file mode 100644 index 0000000..c6a1ec7 --- /dev/null +++ b/libosmo-sigtran-doc.install @@ -0,0 +1 @@ +usr/share/doc/libosmo-sccp/sigtran/ diff --git a/libosmo-sigtran0.install b/libosmo-sigtran0.install new file mode 100644 index 0000000..9779f4c --- /dev/null +++ b/libosmo-sigtran0.install @@ -0,0 +1 @@ +usr/lib/*/libosmo-sigtran*.so.* diff --git a/libosmo-sigtran0.symbols b/libosmo-sigtran0.symbols new file mode 100644 index 0000000..af86763 --- /dev/null +++ b/libosmo-sigtran0.symbols @@ -0,0 +1,109 @@ +libosmo-sigtran.so.0 libosmo-sigtran0 #MINVER# + osmo_isup_party_encode@Base 0.8.1 + osmo_isup_party_parse@Base 0.8.1 + osmo_sccp_addr_by_name@Base 0.8.1 + osmo_sccp_addr_dump@Base 0.8.1 + osmo_sccp_addr_encode@Base 0.8.1 + osmo_sccp_addr_name@Base 0.8.1 + osmo_sccp_addr_parse@Base 0.8.1 + osmo_sccp_addr_set_ssn@Base 0.8.1 + osmo_sccp_check_addr@Base 0.8.1 + osmo_sccp_get_sccp@Base 0.8.1 + osmo_sccp_get_ss7@Base 0.8.1 + osmo_sccp_gt_dump@Base 0.8.1 + osmo_sccp_gti_names@Base 0.8.1 + osmo_sccp_instance_create@Base 0.8.1 + osmo_sccp_instance_destroy@Base 0.8.1 + osmo_sccp_local_addr_by_instance@Base 0.8.1 + osmo_sccp_make_addr_pc_ssn@Base 0.8.1 + osmo_sccp_name_by_addr@Base 0.8.1 + osmo_sccp_routing_ind_names@Base 0.8.1 + osmo_sccp_simple_client@Base 0.8.1 + osmo_sccp_simple_client_on_ss7_id@Base 0.8.1 + osmo_sccp_simple_server@Base 0.8.1 + osmo_sccp_simple_server_add_clnt@Base 0.8.1 + osmo_sccp_simple_server_on_ss7_id@Base 0.8.1 + osmo_sccp_ssn_names@Base 0.8.1 + osmo_sccp_to_xua@Base 0.8.1 + osmo_sccp_tx_conn_req@Base 0.8.1 + osmo_sccp_tx_conn_req_msg@Base 0.8.1 + osmo_sccp_tx_conn_resp@Base 0.8.1 + osmo_sccp_tx_conn_resp_msg@Base 0.8.1 + osmo_sccp_tx_data@Base 0.8.1 + osmo_sccp_tx_data_msg@Base 0.8.1 + osmo_sccp_tx_disconn@Base 0.8.1 + osmo_sccp_tx_unitdata@Base 0.8.1 + osmo_sccp_tx_unitdata_msg@Base 0.8.1 + osmo_sccp_tx_unitdata_ranap@Base 0.8.1 + osmo_sccp_user_bind@Base 0.8.1 + osmo_sccp_user_bind_pc@Base 0.8.1 + osmo_sccp_user_get_priv@Base 0.8.1 + osmo_sccp_user_sap_down@Base 0.8.1 + osmo_sccp_user_set_priv@Base 0.8.1 + osmo_sccp_user_unbind@Base 0.8.1 + osmo_sccp_vty_init@Base 0.8.1 + osmo_scu_prim_name@Base 0.8.1 + osmo_scu_prim_names@Base 0.8.1 + osmo_ss7_as_add_asp@Base 0.8.1 + osmo_ss7_as_del_asp@Base 0.8.1 + osmo_ss7_as_destroy@Base 0.8.1 + osmo_ss7_as_find_by_l_rk_id@Base 0.8.1 + osmo_ss7_as_find_by_name@Base 0.8.1 + osmo_ss7_as_find_by_proto@Base 0.8.1 + osmo_ss7_as_find_by_rctx@Base 0.8.1 + osmo_ss7_as_find_or_create@Base 0.8.1 + osmo_ss7_as_has_asp@Base 0.8.1 + osmo_ss7_as_traffic_mode_vals@Base 0.8.1 + osmo_ss7_asp_destroy@Base 0.8.1 + osmo_ss7_asp_disconnect@Base 0.8.1 + osmo_ss7_asp_find_by_name@Base 0.8.1 + osmo_ss7_asp_find_by_proto@Base 0.8.1 + osmo_ss7_asp_find_or_create@Base 0.8.1 + osmo_ss7_asp_protocol_port@Base 0.8.1 + osmo_ss7_asp_protocol_vals@Base 0.8.1 + osmo_ss7_asp_restart@Base 0.8.1 + osmo_ss7_asp_send@Base 0.8.1 + osmo_ss7_asp_use_default_lm@Base 0.8.1 + osmo_ss7_find_free_rctx@Base 0.8.1 + osmo_ss7_init@Base 0.8.1 + osmo_ss7_instance_destroy@Base 0.8.1 + osmo_ss7_instance_find@Base 0.8.1 + osmo_ss7_instance_find_or_create@Base 0.8.1 + osmo_ss7_instance_set_pc_fmt@Base 0.8.1 + osmo_ss7_instances@Base 0.8.1 + osmo_ss7_is_config_node@Base 0.8.1 + osmo_ss7_link_destroy@Base 0.8.1 + osmo_ss7_link_find_or_create@Base 0.8.1 + osmo_ss7_linkset_destroy@Base 0.8.1 + osmo_ss7_linkset_find_by_name@Base 0.8.1 + osmo_ss7_linkset_find_or_create@Base 0.8.1 + osmo_ss7_mtp_to_user@Base 0.8.1 + osmo_ss7_pc_is_local@Base 0.8.1 + osmo_ss7_pointcode_parse@Base 0.8.1 + osmo_ss7_pointcode_parse_mask_or_len@Base 0.8.1 + osmo_ss7_pointcode_print2@Base 0.8.1 + osmo_ss7_pointcode_print@Base 0.8.1 + osmo_ss7_route_create@Base 0.8.1 + osmo_ss7_route_destroy@Base 0.8.1 + osmo_ss7_route_find_dpc@Base 0.8.1 + osmo_ss7_route_find_dpc_mask@Base 0.8.1 + osmo_ss7_route_lookup@Base 0.8.1 + osmo_ss7_route_table_destroy@Base 0.8.1 + osmo_ss7_route_table_find@Base 0.8.1 + osmo_ss7_route_table_find_or_create@Base 0.8.1 + osmo_ss7_tmode_from_xua@Base 0.8.1 + osmo_ss7_tmode_to_xua@Base 0.8.1 + osmo_ss7_user_mtp_xfer_req@Base 0.8.1 + osmo_ss7_user_register@Base 0.8.1 + osmo_ss7_user_unregister@Base 0.8.1 + osmo_ss7_vty_go_parent@Base 0.8.1 + osmo_ss7_vty_init_asp@Base 0.8.1 + osmo_ss7_vty_init_sg@Base 0.8.1 + osmo_ss7_xua_server_create@Base 0.8.1 + osmo_ss7_xua_server_destroy@Base 0.8.1 + osmo_ss7_xua_server_find@Base 0.8.1 + osmo_ss7_xua_server_set_local_host@Base 0.8.1 + osmo_sua_to_sccp@Base 0.8.1 + osmo_xlm_prim_name@Base 0.8.1 + osmo_xlm_prim_names@Base 0.8.1 + osmo_xlm_sap_down@Base 0.8.1 diff --git a/libosmomtp0.install b/libosmomtp0.install new file mode 100644 index 0000000..aa1fe05 --- /dev/null +++ b/libosmomtp0.install @@ -0,0 +1 @@ +usr/lib/*/libosmomtp*.so.* diff --git a/libosmomtp0.symbols b/libosmomtp0.symbols new file mode 100644 index 0000000..688d441 --- /dev/null +++ b/libosmomtp0.symbols @@ -0,0 +1,3 @@ +libosmomtp.so.0 libosmomtp0 #MINVER# + mtp_pcap_write_header@Base 0.7.0 + mtp_pcap_write_msu@Base 0.7.0 diff --git a/libosmosccp0.install b/libosmosccp0.install new file mode 100644 index 0000000..a14d4a6 --- /dev/null +++ b/libosmosccp0.install @@ -0,0 +1 @@ +usr/lib/*/libosmosccp*.so.* diff --git a/libosmosccp0.symbols b/libosmosccp0.symbols new file mode 100644 index 0000000..db19abc --- /dev/null +++ b/libosmosccp0.symbols @@ -0,0 +1,34 @@ +libosmosccp.so.0 libosmosccp0 #MINVER# + _sccp_parse_connection_confirm@Base 0.7.0 + _sccp_parse_connection_dt1@Base 0.7.0 + _sccp_parse_connection_refused@Base 0.7.0 + _sccp_parse_connection_release_complete@Base 0.7.0 + _sccp_parse_connection_released@Base 0.7.0 + _sccp_parse_connection_request@Base 0.7.0 + _sccp_parse_udt@Base 0.7.0 + sccp_connection_close@Base 0.7.0 + sccp_connection_connect@Base 0.7.0 + sccp_connection_force_free@Base 0.7.0 + sccp_connection_free@Base 0.7.0 + sccp_connection_send_it@Base 0.7.0 + sccp_connection_set_incoming@Base 0.7.0 + sccp_connection_socket@Base 0.7.0 + sccp_connection_write@Base 0.7.0 + sccp_create_cc@Base 0.7.0 + sccp_create_cr@Base 0.7.0 + sccp_create_dt1@Base 0.7.0 + sccp_create_refuse@Base 0.7.0 + sccp_create_rlsd@Base 0.7.0 + sccp_create_sccp_addr@Base 0.7.0 + sccp_create_udt@Base 0.7.0 + sccp_determine_msg_type@Base 0.7.0 + sccp_parse_header@Base 0.7.0 + sccp_set_log_area@Base 0.7.0 + sccp_set_read@Base 0.7.0 + sccp_src_ref_from_int@Base 0.7.0 + sccp_src_ref_to_int@Base 0.7.0 + sccp_ssn_bssap@Base 0.7.0 + sccp_system_incoming@Base 0.7.0 + sccp_system_incoming_ctx@Base 0.7.0 + sccp_system_init@Base 0.7.0 + sccp_write@Base 0.7.0 diff --git a/libosmoxua0.install b/libosmoxua0.install new file mode 100644 index 0000000..53df5c3 --- /dev/null +++ b/libosmoxua0.install @@ -0,0 +1 @@ +usr/lib/*/libosmoxua*.so.* diff --git a/libosmoxua0.symbols b/libosmoxua0.symbols new file mode 100644 index 0000000..1b69ad1 --- /dev/null +++ b/libosmoxua0.symbols @@ -0,0 +1,23 @@ +libosmoxua.so.0 libosmoxua0 #MINVER# + xua_to_msg@Base 0.7.0 + msgb_t16l16vp_put@Base 0.8.1 + msgb_t16l16vp_put_u32@Base 0.8.1 + xua_class_iei_name@Base 0.8.1 + xua_class_msg_name@Base 0.8.1 + xua_dialect_check_all_mand_ies@Base 0.8.1 + xua_from_msg@Base 0.7.0 + xua_from_nested@Base 0.8.1 + xua_hdr_dump@Base 0.8.1 + xua_msg_add_data@Base 0.7.0 + xua_msg_add_sccp_addr@Base 0.8.1 + xua_msg_add_u32@Base 0.8.1 + xua_msg_alloc@Base 0.7.0 + xua_msg_copy_part@Base 0.8.1 + xua_msg_dump@Base 0.8.1 + xua_msg_event_map@Base 0.8.1 + xua_msg_find_tag@Base 0.7.0 + xua_msg_free@Base 0.7.0 + xua_msg_free_tag@Base 0.8.1 + xua_msg_get_u32@Base 0.8.1 + xua_msg_part_get_u32@Base 0.8.1 + xua_part_add_gt@Base 0.8.1 diff --git a/osmo-stp.1 b/osmo-stp.1 new file mode 100644 index 0000000..3a0ae33 --- /dev/null +++ b/osmo-stp.1 @@ -0,0 +1,39 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.5. +.TH OSMO-STP "1" "February 2018" "osmo-stp version 0.8.1" "User Commands" +.SH NAME +osmo-stp \- Osmocom SIGTRAN STP (Signaling Transfer Point) +.SH DESCRIPTION +.TP +\fB\-h\fR \fB\-\-help\fR +This text. +.TP +\fB\-D\fR \fB\-\-daemonize\fR +Fork the process into a background daemon +.TP +\fB\-c\fR \fB\-\-config\-file\fR filename +The config file to use. Default: ./osmo\-stp.cfg +.TP +\fB\-V\fR \fB\-\-version\fR +Print the version of OsmoSTP +.SH COPYRIGHT +Copyright \(co 2015\-2017 by Harald Welte +Contributions by Holger Freyther, Neels Hofmeyr +License GPLv2+: GNU GPL Version 2 or later +.br +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +.PP + +Free Software lives by contribution. If you use this, please contribute! +.SH "SEE ALSO" +The full documentation for +.B osmo-stp +is maintained as a Texinfo manual. If the +.B info +and +.B osmo-stp +programs are properly installed at your site, the command +.IP +.B info osmo-stp +.PP +should give you access to the complete manual. diff --git a/osmo-stp.install b/osmo-stp.install new file mode 100644 index 0000000..cf80326 --- /dev/null +++ b/osmo-stp.install @@ -0,0 +1 @@ +usr/bin/osmo-stp diff --git a/osmo-stp.manpages b/osmo-stp.manpages new file mode 100644 index 0000000..6437b17 --- /dev/null +++ b/osmo-stp.manpages @@ -0,0 +1 @@ +debian/osmo-stp.1 diff --git a/osmo-stp.service b/osmo-stp.service new file mode 120000 index 0000000..55f37c2 --- /dev/null +++ b/osmo-stp.service @@ -0,0 +1 @@ +../contrib/systemd/osmo-stp.service \ No newline at end of file diff --git a/patches/01_make_library_shared.patch b/patches/01_make_library_shared.patch new file mode 100644 index 0000000..891f930 --- /dev/null +++ b/patches/01_make_library_shared.patch @@ -0,0 +1,61 @@ +Description: Upstream only delivers static libraries. This patch changes these + to shared libraries. In addition, the libraries are renamed to make it clearer + what they are (and because of a conflict in the Debian namespace for one of + them) +Author: Ruben Undheim + +Index: libosmo-sccp-0.8.1/src/Makefile.am +=================================================================== +--- libosmo-sccp-0.8.1.orig/src/Makefile.am ++++ libosmo-sccp-0.8.1/src/Makefile.am +@@ -7,11 +7,14 @@ noinst_HEADERS = sccp_internal.h xua_asp + # Legacy static libs + + sccpdir = $(libdir) +-sccp_LIBRARIES = libsccp.a libmtp.a libxua.a ++sccp_LTLIBRARIES = libosmosccp.la libosmomtp.la libosmoxua.la + +-libsccp_a_SOURCES = sccp.c +-libmtp_a_SOURCES = mtp_pcap.c +-libxua_a_SOURCES = xua_msg.c ++libosmosccp_la_SOURCES = sccp.c ++libosmosccp_la_LDFLAGS= -shared -fPIC ++libosmomtp_la_SOURCES = mtp_pcap.c ++libosmomtp_la_LDFLAGS= -shared -fPIC ++libosmoxua_la_SOURCES = xua_msg.c ++libosmoxua_la_LDFLAGS= -shared -fPIC + # ensure that the file for the static lib is built with different C + # flags, working around automake complaining that xua_msg.o is built + # both with libtool (below) and without (here) +Index: libosmo-sccp-0.8.1/tests/m2ua/Makefile.am +=================================================================== +--- libosmo-sccp-0.8.1.orig/tests/m2ua/Makefile.am ++++ libosmo-sccp-0.8.1/tests/m2ua/Makefile.am +@@ -5,4 +5,4 @@ EXTRA_DIST = m2ua_test.ok + + noinst_PROGRAMS = m2ua_test + m2ua_test_SOURCES = m2ua_test.c +-m2ua_test_LDADD = $(top_builddir)/src/libxua.a $(LIBOSMOCORE_LIBS) ++m2ua_test_LDADD = $(top_builddir)/src/libosmoxua.la $(LIBOSMOCORE_LIBS) +Index: libosmo-sccp-0.8.1/libosmo-mtp.pc.in +=================================================================== +--- libosmo-sccp-0.8.1.orig/libosmo-mtp.pc.in ++++ libosmo-sccp-0.8.1/libosmo-mtp.pc.in +@@ -6,5 +6,5 @@ includedir=@includedir@ + Name: Osmo MTP Lib + Description: Osmo MTP Lib + Version: @VERSION@ +-Libs: -L${libdir} -lmtp ++Libs: -L${libdir} -losmomtp + Cflags: -I${includedir}/ +Index: libosmo-sccp-0.8.1/libosmo-sccp.pc.in +=================================================================== +--- libosmo-sccp-0.8.1.orig/libosmo-sccp.pc.in ++++ libosmo-sccp-0.8.1/libosmo-sccp.pc.in +@@ -6,5 +6,5 @@ includedir=@includedir@ + Name: OpenBSC SCCP Lib + Description: OpenBSC SCCP Lib + Version: @VERSION@ +-Libs: -L${libdir} -lsccp ++Libs: -L${libdir} -losmosccp -losmoxua + Cflags: -I${includedir}/ diff --git a/patches/02_set_version_explicitly.patch b/patches/02_set_version_explicitly.patch new file mode 100644 index 0000000..6615adb --- /dev/null +++ b/patches/02_set_version_explicitly.patch @@ -0,0 +1,16 @@ +Description: Make sure the version is set correctly and therefore not depend + on some git command to find a "dirty" version. +Author: Ruben Undheim + +Index: libosmo-sccp-0.8.1/configure.ac +=================================================================== +--- libosmo-sccp-0.8.1.orig/configure.ac ++++ libosmo-sccp-0.8.1/configure.ac +@@ -1,6 +1,6 @@ + dnl Process this file with autoconf to produce a configure script + AC_INIT([libosmo-sccp], +- m4_esyscmd([./git-version-gen .tarball-version]), ++ 0.8.1, + [openbsc@lists.osmocom.org]) + + dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ diff --git a/patches/03_fix_typo.patch b/patches/03_fix_typo.patch new file mode 100644 index 0000000..9436df4 --- /dev/null +++ b/patches/03_fix_typo.patch @@ -0,0 +1,30 @@ +Description: This patch fixes typos on which Lintian complains +Author: Kira Obrezkova + +Index: libosmo-sccp-0.8.1/src/m3ua.c +=================================================================== +--- libosmo-sccp-0.8.1.orig/src/m3ua.c ++++ libosmo-sccp-0.8.1/src/m3ua.c +@@ -89,7 +89,7 @@ const struct value_string m3ua_ntfy_stch + }; + + const struct value_string m3ua_ntfy_other_names[] = { +- { M3UA_NOTIFY_I_OT_INS_RES, "Insufficient ASP Resouces active in AS" }, ++ { M3UA_NOTIFY_I_OT_INS_RES, "Insufficient ASP Resources active in AS" }, + { M3UA_NOTIFY_I_OT_ALT_ASP_ACT, "Alternative ASP Active" }, + { M3UA_NOTIFY_I_OT_ASP_FAILURE, "ASP Failure" }, + { 0, NULL } +Index: libosmo-sccp-0.8.1/stp/stp_main.c +=================================================================== +--- libosmo-sccp-0.8.1.orig/stp/stp_main.c ++++ libosmo-sccp-0.8.1/stp/stp_main.c +@@ -83,7 +83,7 @@ static struct { + static void print_help(void) + { + printf(" -h --help This text.\n"); +- printf(" -D --daemonize Fork teh process into a background daemon\n"); ++ printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -c --config-file filename The config file to use. Default: ./osmo-stp.cfg\n"); + printf(" -V --version Print the version of OsmoSTP\n"); + } + diff --git a/patches/04_change_systemd_service_file.patch b/patches/04_change_systemd_service_file.patch new file mode 100644 index 0000000..1310994 --- /dev/null +++ b/patches/04_change_systemd_service_file.patch @@ -0,0 +1,14 @@ +Documentation: This patch adds Documentation field to systemd service file +Author: Kira Obrezkova + +Index: libosmo-sccp-0.8.1/contrib/systemd/osmo-stp.service +=================================================================== +--- libosmo-sccp-0.8.1.orig/contrib/systemd/osmo-stp.service ++++ libosmo-sccp-0.8.1/contrib/systemd/osmo-stp.service +@@ -1,5 +1,6 @@ + [Unit] + Description=Osmocom STP (Signal Transfer Point) ++Documentation=https://osmocom.org/projects/osmo-stp/wiki + + [Service] + Type=simple diff --git a/patches/series b/patches/series new file mode 100644 index 0000000..e770aa9 --- /dev/null +++ b/patches/series @@ -0,0 +1,4 @@ +01_make_library_shared.patch +02_set_version_explicitly.patch +03_fix_typo.patch +04_change_systemd_service_file.patch diff --git a/rules b/rules new file mode 100755 index 0000000..780debd --- /dev/null +++ b/rules @@ -0,0 +1,32 @@ +#!/usr/bin/make -f + +#export DH_VERBOSE=1 + +export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed +export DEB_BUILD_MAINT_OPTIONS = hardening=+all +arch = $(shell dpkg-architecture -qDEB_BUILD_ARCH) + +%: + dh $@ --with autoreconf + +override_dh_clean: + dh_clean + rm -f .version tests/package.m4 tests/testsuite + +# Print test results in case of a failure +override_dh_auto_test: + echo ${arch} + if [ "${arch}" = "s390x" ] || \ + [ "${arch}" = "hppa" ] || \ + [ "${arch}" = "powerpc" ] || \ + [ "${arch}" = "ppc64" ] || \ + [ "${arch}" = "sparc64" ] || \ + [ "${arch}" = "mips" ] ; then \ + echo "Do not care of test result on this architecture" ;\ + else \ + echo "Do make tests on this architecture" ;\ + dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false) \ + fi + +override_dh_installinit: + dh_installinit --no-scripts diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 0000000..9c34d19 --- /dev/null +++ b/watch @@ -0,0 +1,2 @@ +version=4 +https://github.com/osmocom/libosmo-sccp/releases /osmocom/libosmo-sccp/archive/(\d\.\d\S+)\.tar\.(?:bz2|gz|xz) debian uupdate -- cgit v1.2.3 From 1c6db80d7a022766d108a761aecd9369517a2303 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 20 Apr 2018 18:39:43 +0200 Subject: _make_library_shared Gbp-Pq: Name 01_make_library_shared.patch --- libosmo-mtp.pc.in | 2 +- libosmo-sccp.pc.in | 2 +- src/Makefile.am | 13 ++++++++----- tests/m2ua/Makefile.am | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/libosmo-mtp.pc.in b/libosmo-mtp.pc.in index 675d0d3..02f10ad 100644 --- a/libosmo-mtp.pc.in +++ b/libosmo-mtp.pc.in @@ -6,5 +6,5 @@ includedir=@includedir@ Name: Osmo MTP Lib Description: Osmo MTP Lib Version: @VERSION@ -Libs: -L${libdir} -lmtp +Libs: -L${libdir} -losmomtp Cflags: -I${includedir}/ diff --git a/libosmo-sccp.pc.in b/libosmo-sccp.pc.in index eda8d49..b04664f 100644 --- a/libosmo-sccp.pc.in +++ b/libosmo-sccp.pc.in @@ -6,5 +6,5 @@ includedir=@includedir@ Name: OpenBSC SCCP Lib Description: OpenBSC SCCP Lib Version: @VERSION@ -Libs: -L${libdir} -lsccp +Libs: -L${libdir} -losmosccp -losmoxua Cflags: -I${includedir}/ diff --git a/src/Makefile.am b/src/Makefile.am index 1001d19..1fca32d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,11 +7,14 @@ noinst_HEADERS = sccp_internal.h xua_asp_fsm.h xua_as_fsm.h xua_internal.h # Legacy static libs sccpdir = $(libdir) -sccp_LIBRARIES = libsccp.a libmtp.a libxua.a - -libsccp_a_SOURCES = sccp.c -libmtp_a_SOURCES = mtp_pcap.c -libxua_a_SOURCES = xua_msg.c +sccp_LTLIBRARIES = libosmosccp.la libosmomtp.la libosmoxua.la + +libosmosccp_la_SOURCES = sccp.c +libosmosccp_la_LDFLAGS= -shared -fPIC +libosmomtp_la_SOURCES = mtp_pcap.c +libosmomtp_la_LDFLAGS= -shared -fPIC +libosmoxua_la_SOURCES = xua_msg.c +libosmoxua_la_LDFLAGS= -shared -fPIC # ensure that the file for the static lib is built with different C # flags, working around automake complaining that xua_msg.o is built # both with libtool (below) and without (here) diff --git a/tests/m2ua/Makefile.am b/tests/m2ua/Makefile.am index 33618ef..815fd87 100644 --- a/tests/m2ua/Makefile.am +++ b/tests/m2ua/Makefile.am @@ -5,4 +5,4 @@ EXTRA_DIST = m2ua_test.ok noinst_PROGRAMS = m2ua_test m2ua_test_SOURCES = m2ua_test.c -m2ua_test_LDADD = $(top_builddir)/src/libxua.a $(LIBOSMOCORE_LIBS) +m2ua_test_LDADD = $(top_builddir)/src/libosmoxua.la $(LIBOSMOCORE_LIBS) -- cgit v1.2.3 From e75d6c491bfb5a45e80f0bfbf04d91a1dd59eb75 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 20 Apr 2018 18:39:43 +0200 Subject: _set_version_explicitly Gbp-Pq: Name 02_set_version_explicitly.patch --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5165cdb..02d1985 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl Process this file with autoconf to produce a configure script AC_INIT([libosmo-sccp], - m4_esyscmd([./git-version-gen .tarball-version]), + 0.8.1, [openbsc@lists.osmocom.org]) dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ -- cgit v1.2.3 From 68685dc44372a2b4745189083f880f06e734e4db Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 20 Apr 2018 18:39:43 +0200 Subject: _fix_typo Gbp-Pq: Name 03_fix_typo.patch --- src/m3ua.c | 2 +- stp/stp_main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/m3ua.c b/src/m3ua.c index c43dfa4..3031688 100644 --- a/src/m3ua.c +++ b/src/m3ua.c @@ -89,7 +89,7 @@ const struct value_string m3ua_ntfy_stchg_names[] = { }; const struct value_string m3ua_ntfy_other_names[] = { - { M3UA_NOTIFY_I_OT_INS_RES, "Insufficient ASP Resouces active in AS" }, + { M3UA_NOTIFY_I_OT_INS_RES, "Insufficient ASP Resources active in AS" }, { M3UA_NOTIFY_I_OT_ALT_ASP_ACT, "Alternative ASP Active" }, { M3UA_NOTIFY_I_OT_ASP_FAILURE, "ASP Failure" }, { 0, NULL } diff --git a/stp/stp_main.c b/stp/stp_main.c index a33045a..eb49789 100644 --- a/stp/stp_main.c +++ b/stp/stp_main.c @@ -83,7 +83,7 @@ static struct { static void print_help(void) { printf(" -h --help This text.\n"); - printf(" -D --daemonize Fork teh process into a background daemon\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); printf(" -c --config-file filename The config file to use. Default: ./osmo-stp.cfg\n"); printf(" -V --version Print the version of OsmoSTP\n"); } -- cgit v1.2.3 From 558cec4054973946e89980bb1b4ffc24cb68b756 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 20 Apr 2018 18:39:43 +0200 Subject: _change_systemd_service_file Gbp-Pq: Name 04_change_systemd_service_file.patch --- contrib/systemd/osmo-stp.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/systemd/osmo-stp.service b/contrib/systemd/osmo-stp.service index 5d06870..87b414b 100644 --- a/contrib/systemd/osmo-stp.service +++ b/contrib/systemd/osmo-stp.service @@ -1,5 +1,6 @@ [Unit] Description=Osmocom STP (Signal Transfer Point) +Documentation=https://osmocom.org/projects/osmo-stp/wiki [Service] Type=simple -- cgit v1.2.3