libosmo-sccp-0.10.0/000077500000000000000000000000001332664606400141745ustar00rootroot00000000000000libosmo-sccp-0.10.0/.gitignore000066400000000000000000000013221332664606400161620ustar00rootroot00000000000000*.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/sccp_demo_user stp/osmo-stp *.pc config.* tags /Doxyfile libosmo-sccp-0.10.0/.gitreview000066400000000000000000000000661332664606400162040ustar00rootroot00000000000000[gerrit] host=gerrit.osmocom.org project=libosmo-sccp libosmo-sccp-0.10.0/COPYING000066400000000000000000000431031332664606400152300ustar00rootroot00000000000000 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. libosmo-sccp-0.10.0/Doxyfile.in000066400000000000000000002153721332664606400163210ustar00rootroot00000000000000# 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 libosmo-sccp-0.10.0/Makefile.am000066400000000000000000000022001332664606400162220ustar00rootroot00000000000000AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include SUBDIRS = include src tests examples stp doc pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libosmo-sccp.pc libosmo-mtp.pc libosmo-sigtran.pc libosmo-xua.pc EXTRA_DIST = .version git-version-gen osmoappdesc.py 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) libosmo-sccp-0.10.0/TODO-RELEASE000066400000000000000000000012521332664606400157420ustar00rootroot00000000000000# 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 libosmo-sccp-0.10.0/configure.ac000066400000000000000000000067011332664606400164660ustar00rootroot00000000000000dnl 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 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.12.0) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.12.0) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.12.0) PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.3.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 AC_ARG_ENABLE(sanitize, [AS_HELP_STRING( [--enable-sanitize], [Compile with address sanitizer enabled], )], [sanitize=$enableval], [sanitize="no"]) if test x"$sanitize" = x"yes" then CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined" CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" fi AC_ARG_ENABLE(werror, [AS_HELP_STRING( [--enable-werror], [Turn all compiler warnings into errors, with exceptions: a) deprecation (allow upstream to mark deprecation without breaking builds); b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) ] )], [werror=$enableval], [werror="no"]) if test x"$werror" = x"yes" then WERROR_FLAGS="-Werror" WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" CFLAGS="$CFLAGS $WERROR_FLAGS" CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" fi # 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_MSG_RESULT([CFLAGS="$CFLAGS"]) AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) 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 doc/Makefile doc/examples/Makefile Doxyfile Makefile) libosmo-sccp-0.10.0/contrib/000077500000000000000000000000001332664606400156345ustar00rootroot00000000000000libosmo-sccp-0.10.0/contrib/jenkins.sh000077500000000000000000000016761332664606400176460ustar00rootroot00000000000000#!/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 osmo-clean-workspace.sh mkdir "$deps" || true 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 "" --disable-doxygen 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 --enable-sanitize --enable-werror $MAKE $PARALLEL_MAKE $MAKE distcheck \ || cat-testlogs.sh osmo-clean-workspace.sh libosmo-sccp-0.10.0/contrib/systemd/000077500000000000000000000000001332664606400173245ustar00rootroot00000000000000libosmo-sccp-0.10.0/contrib/systemd/osmo-stp.service000066400000000000000000000003111332664606400224620ustar00rootroot00000000000000[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 libosmo-sccp-0.10.0/contrib/test/000077500000000000000000000000001332664606400166135ustar00rootroot00000000000000libosmo-sccp-0.10.0/contrib/test/osmo-stp.cfg000066400000000000000000000020501332664606400210520ustar00rootroot00000000000000! ! 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 2905 0 m3ua remote-ip 172.18.0.2 asp asp-s-0 14001 0 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 libosmo-sccp-0.10.0/contrib/test/run-in-ns.sh000077500000000000000000000006301332664606400207770ustar00rootroot00000000000000#!/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 libosmo-sccp-0.10.0/contrib/test/test-m3ua.sh000077500000000000000000000015221332664606400207740ustar00rootroot00000000000000#!/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 $! libosmo-sccp-0.10.0/contrib/test/test-sua.sh000077500000000000000000000015231332664606400207200ustar00rootroot00000000000000#!/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 $! libosmo-sccp-0.10.0/doc/000077500000000000000000000000001332664606400147415ustar00rootroot00000000000000libosmo-sccp-0.10.0/doc/Makefile.am000066400000000000000000000000571332664606400167770ustar00rootroot00000000000000SUBDIRS = \ examples \ $(NULL) libosmo-sccp-0.10.0/doc/examples/000077500000000000000000000000001332664606400165575ustar00rootroot00000000000000libosmo-sccp-0.10.0/doc/examples/Makefile.am000066400000000000000000000001621332664606400206120ustar00rootroot00000000000000examples_stpdir = $(docdir)/examples/osmo-stp examples_stp_DATA = osmo-stp.cfg EXTRA_DIST = $(examples_stp_DATA) libosmo-sccp-0.10.0/doc/examples/osmo-stp.cfg000066400000000000000000000006501332664606400210220ustar00rootroot00000000000000! ! 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 libosmo-sccp-0.10.0/examples/000077500000000000000000000000001332664606400160125ustar00rootroot00000000000000libosmo-sccp-0.10.0/examples/Makefile.am000066400000000000000000000006651332664606400200550ustar00rootroot00000000000000AM_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 = sccp_demo_user sccp_demo_user_SOURCES = sccp_demo_user.c sccp_test_server.c sccp_test_vty.c sccp_demo_user_LDADD = $(top_builddir)/src/libosmo-sigtran.la \ $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) libosmo-sccp-0.10.0/examples/internal.h000066400000000000000000000004411332664606400177760ustar00rootroot00000000000000#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); libosmo-sccp-0.10.0/examples/sccp_demo_user.c000066400000000000000000000157401332664606400211570ustar00rootroot00000000000000 #include #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(int local_port, const char *local_address, int local_pc, int remote_port, const char *remote_address, int remote_pc) { struct osmo_sccp_instance *sccp; sccp = osmo_sccp_simple_server(NULL, local_pc, OSMO_SS7_ASP_PROT_M3UA, local_port, local_address); if (sccp == NULL) return NULL; osmo_sccp_simple_server_add_clnt(sccp, OSMO_SS7_ASP_PROT_M3UA, "client", remote_pc, local_port, remote_port, remote_address); 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; void *tall_ctx = talloc_named_const(NULL, 1, "example"); msgb_talloc_ctx_init(tall_ctx, 0); osmo_init_logging2(tall_ctx, &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-demo-user", .version = 0, }; #define DEFAULT_LOCAL_ADDRESS_SERVER "127.0.0.1" #define DEFAULT_LOCAL_ADDRESS_CLIENT "127.0.0.2" #define DEFAULT_REMOTE_ADDRESS_CLIENT DEFAULT_LOCAL_ADDRESS_SERVER #define DEFAULT_REMOTE_ADDRESS_SERVER DEFAULT_LOCAL_ADDRESS_CLIENT #define DEFAULT_LOCAL_PORT_SERVER M3UA_PORT #define DEFAULT_LOCAL_PORT_CLIENT M3UA_PORT #define DEFAULT_REMOTE_PORT_CLIENT DEFAULT_LOCAL_PORT_SERVER #define DEFAULT_REMOTE_PORT_SERVER DEFAULT_LOCAL_PORT_CLIENT #define DEFAULT_REMOTE_PORT_SERVER_STR DEFAULT_LOCAL_PORT_CLIENT_STR #define DEFAULT_PC_SERVER 1 #define DEFAULT_PC_CLIENT 23 static void usage(void) { fprintf(stderr, "sccp_demo_user [-c] [-l LOCAL_ADDRESS[:LOCAL_PORT]]\n" " [-r REMOTE_ADDRESS[:REMOTE_PORT]]\n" " [-L LOCAL_POINT_CODE] [-R REMOTE_POINT_CODE]\n" "Options:\n" " -c: Run in client mode (default is server mode)\n" " -l: local IP address and SCTP port (default is %s:%d in server mode,\n" " %s:%d in client mode)\n" " -r: remote IP address and SCTP port (default is %s:%d in server mode,\n" " %s:%d in client mode)\n" " -L: local point code (default is %d in server mode, %d in client mode)\n" " -R: remote point code (default is %d in server mode, %d in client mode)\n", DEFAULT_LOCAL_ADDRESS_SERVER, DEFAULT_LOCAL_PORT_SERVER, DEFAULT_LOCAL_ADDRESS_CLIENT, DEFAULT_LOCAL_PORT_CLIENT, DEFAULT_REMOTE_ADDRESS_SERVER, DEFAULT_REMOTE_PORT_SERVER, DEFAULT_REMOTE_ADDRESS_CLIENT, DEFAULT_REMOTE_PORT_CLIENT, DEFAULT_PC_SERVER, DEFAULT_PC_CLIENT, DEFAULT_PC_CLIENT, DEFAULT_PC_SERVER); exit(1); } static int is_decimal_string(const char *s) { const char *p = s; if (*p == '\0') return 0; while (*p) { if (!isdigit(*p++)) return 0; } return 1; } static int parse_address_port(char **address, int *port, const char *arg) { char *s, *colon; s = strdup(arg); OSMO_ASSERT(s); colon = strrchr(s, ':'); if (colon) { char *portstr = colon + 1; *colon = '\0'; if (*portstr == '\0') { fprintf(stderr, "missing port number after : in '%s'\n", arg); free(s); return 1; } if (!is_decimal_string(portstr)) { fprintf(stderr, "invalid port number: '%s'\n", portstr); free(s); return 1; } *port = atoi(portstr); } *address = s; return 0; } int main(int argc, char **argv) { bool client = false; int rc, ch; char *local_address = DEFAULT_LOCAL_ADDRESS_SERVER; int local_port = DEFAULT_LOCAL_PORT_SERVER; int local_pc = DEFAULT_PC_SERVER; char *remote_address = DEFAULT_REMOTE_ADDRESS_SERVER; int remote_port = DEFAULT_REMOTE_PORT_SERVER; int remote_pc = DEFAULT_PC_CLIENT; bool lflag = false, rflag = false, Lflag = false, Rflag = false; while ((ch = getopt(argc, argv, "cl:r:L:R:")) != -1) { switch (ch) { case 'c': client = true; /* Set client-mode defaults unless already overridden during option parsing. */ if (!lflag) { local_address = DEFAULT_LOCAL_ADDRESS_CLIENT; local_port = DEFAULT_LOCAL_PORT_CLIENT; } if (!Lflag) local_pc = DEFAULT_PC_CLIENT; if (!rflag) { remote_address = DEFAULT_REMOTE_ADDRESS_CLIENT; remote_port = DEFAULT_REMOTE_PORT_CLIENT; } if (!Rflag) remote_pc = DEFAULT_PC_SERVER; break; case 'l': if (parse_address_port(&local_address, &local_port, optarg)) exit(1); lflag = true; break; case 'r': if (parse_address_port(&remote_address, &remote_port, optarg)) exit(1); rflag = true; break; case 'L': if (!is_decimal_string(optarg)) { fprintf(stderr, "invalid decimal point code: '%s'\n", optarg); exit(1); } local_pc = atoi(optarg); Lflag = true; break; case 'R': if (!is_decimal_string(optarg)) { fprintf(stderr, "invalid decimal point code: '%s'\n", optarg); exit(1); } remote_pc = atoi(optarg); Rflag = true; break; default: usage(); } } argc -= optind; argv += optind; if (argc != 0) usage(); 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(); rc = telnet_init_dynif(NULL, NULL, vty_get_bind_addr(), 2324+client); if (rc < 0) { perror("Error binding VTY port"); exit(1); } if (client) { g_sccp = osmo_sccp_simple_client(NULL, "client", local_pc, OSMO_SS7_ASP_PROT_M3UA, local_port, local_address, remote_port, remote_address); if (g_sccp == NULL) { perror("Could not create SCCP client"); exit (1); } sccp_test_user_vty_install(g_sccp, OSMO_SCCP_SSN_BSSAP); } else { g_sccp = sua_server_helper(local_port, local_address, local_pc, remote_port, remote_address, remote_pc); if (g_sccp == NULL) { perror("Could not create SCCP server"); exit(1); } sccp_test_server_init(g_sccp); } while (1) { osmo_select_main(0); } } libosmo-sccp-0.10.0/examples/sccp_test_server.c000066400000000000000000000072351332664606400215420ustar00rootroot00000000000000 #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; } libosmo-sccp-0.10.0/examples/sccp_test_vty.c000066400000000000000000000066071332664606400210600ustar00rootroot00000000000000 #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); 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; } libosmo-sccp-0.10.0/git-version-gen000077500000000000000000000125001332664606400171350ustar00rootroot00000000000000#!/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 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: libosmo-sccp-0.10.0/include/000077500000000000000000000000001332664606400156175ustar00rootroot00000000000000libosmo-sccp-0.10.0/include/Makefile.am000066400000000000000000000000331332664606400176470ustar00rootroot00000000000000SUBDIRS = sccp mtp osmocom libosmo-sccp-0.10.0/include/mtp/000077500000000000000000000000001332664606400164175ustar00rootroot00000000000000libosmo-sccp-0.10.0/include/mtp/Makefile.am000066400000000000000000000001111332664606400204440ustar00rootroot00000000000000mtp_HEADERS = mtp_level3.h mtp_pcap.h mtpdir = $(includedir)/osmocom/mtp libosmo-sccp-0.10.0/include/mtp/mtp_level3.h000066400000000000000000000102321332664606400206400ustar00rootroot00000000000000/* 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)); libosmo-sccp-0.10.0/include/mtp/mtp_pcap.h000066400000000000000000000017571332664606400204050ustar00rootroot00000000000000/* * (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 libosmo-sccp-0.10.0/include/osmocom/000077500000000000000000000000001332664606400172735ustar00rootroot00000000000000libosmo-sccp-0.10.0/include/osmocom/Makefile.am000066400000000000000000000000221332664606400213210ustar00rootroot00000000000000SUBDIRS = sigtran libosmo-sccp-0.10.0/include/osmocom/sigtran/000077500000000000000000000000001332664606400207425ustar00rootroot00000000000000libosmo-sccp-0.10.0/include/osmocom/sigtran/Makefile.am000066400000000000000000000004431332664606400227770ustar00rootroot00000000000000sigtran_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 libosmo-sccp-0.10.0/include/osmocom/sigtran/m2ua_types.h000066400000000000000000000202121332664606400232000ustar00rootroot00000000000000#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 libosmo-sccp-0.10.0/include/osmocom/sigtran/mtp_sap.h000066400000000000000000000033051332664606400225570ustar00rootroot00000000000000#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); libosmo-sccp-0.10.0/include/osmocom/sigtran/osmo_ss7.h000066400000000000000000000357421332664606400226770ustar00rootroot00000000000000#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); int osmo_ss7_instance_bind(struct osmo_ss7_instance *inst); int osmo_ss7_bind_all_instances(); /*********************************************************************** * 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); const char *osmo_ss7_route_name(struct osmo_ss7_route *rt, bool list_asps); /*********************************************************************** * 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); /*! Weak function to handle payload for unknown/unsupported PPID or IPA StreamID. * This function can be overridden by application code to implement whatever handling * it wants for such additional payloads/streams. * \param[in] asp Application Server Process through which data was received * \param[in] ppid_sid SCTP PPID (in sigtran case) or IPA Stream ID * \param[in] msg Message buffer containing received data. Continues to be owned by caller! * \return 0 on success; negative on error */ typedef int osmo_ss7_asp_rx_unknown_cb(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg); void osmo_ss7_register_rx_unknown_cb(osmo_ss7_asp_rx_unknown_cb *cb); #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_bind(struct osmo_xua_server *xs); 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); libosmo-sccp-0.10.0/include/osmocom/sigtran/protocol/000077500000000000000000000000001332664606400226035ustar00rootroot00000000000000libosmo-sccp-0.10.0/include/osmocom/sigtran/protocol/m3ua.h000066400000000000000000000116361332664606400236300ustar00rootroot00000000000000/* 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, }; libosmo-sccp-0.10.0/include/osmocom/sigtran/protocol/mtp.h000066400000000000000000000011111332664606400235460ustar00rootroot00000000000000#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[]; libosmo-sccp-0.10.0/include/osmocom/sigtran/protocol/sua.h000066400000000000000000000116341332664606400235510ustar00rootroot00000000000000/* 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 libosmo-sccp-0.10.0/include/osmocom/sigtran/sccp_helpers.h000066400000000000000000000042611332664606400235700ustar00rootroot00000000000000#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); char *osmo_sccp_inst_addr_name(const struct osmo_sccp_instance *sccp, const struct osmo_sccp_addr *addr); libosmo-sccp-0.10.0/include/osmocom/sigtran/sccp_sap.h000066400000000000000000000171001332664606400227050ustar00rootroot00000000000000#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); const char *osmo_sccp_user_name(struct osmo_sccp_user *scu); libosmo-sccp-0.10.0/include/osmocom/sigtran/sigtran_sap.h000066400000000000000000000033521332664606400234300ustar00rootroot00000000000000#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); libosmo-sccp-0.10.0/include/osmocom/sigtran/xua_msg.h000066400000000000000000000067121332664606400225640ustar00rootroot00000000000000/* 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; void osmo_xua_msg_tall_ctx_init(void *ctx); 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); libosmo-sccp-0.10.0/include/osmocom/sigtran/xua_types.h000066400000000000000000000023041332664606400231330ustar00rootroot00000000000000#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)); libosmo-sccp-0.10.0/include/sccp/000077500000000000000000000000001332664606400165475ustar00rootroot00000000000000libosmo-sccp-0.10.0/include/sccp/Makefile.am000066400000000000000000000001101332664606400205730ustar00rootroot00000000000000sccp_HEADERS = sccp_types.h sccp.h sccpdir = $(includedir)/osmocom/sccp libosmo-sccp-0.10.0/include/sccp/sccp.h000066400000000000000000000137001332664606400176510ustar00rootroot00000000000000/* * 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 libosmo-sccp-0.10.0/include/sccp/sccp_types.h000066400000000000000000000261721332664606400211040ustar00rootroot00000000000000/* * 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, }; /* ITU=T Q.713 Section 3.15 */ 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_NO_TRANS_FOR_ADDRESS_NATURE = 18, SCCP_REFUSAL_UNEQUIPPED_USER = 19, }; /* * 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 libosmo-sccp-0.10.0/libosmo-mtp.pc.in000066400000000000000000000003041332664606400173640ustar00rootroot00000000000000prefix=@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}/ libosmo-sccp-0.10.0/libosmo-sccp.pc.in000066400000000000000000000003151332664606400175160ustar00rootroot00000000000000prefix=@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}/ libosmo-sccp-0.10.0/libosmo-sigtran.pc.in000066400000000000000000000003701332664606400202360ustar00rootroot00000000000000prefix=@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}/ libosmo-sccp-0.10.0/libosmo-xua.pc.in000066400000000000000000000003111332664606400173570ustar00rootroot00000000000000prefix=@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}/ libosmo-sccp-0.10.0/osmoappdesc.py000066400000000000000000000003621332664606400170640ustar00rootroot00000000000000#!/usr/bin/env python app_configs = { "osmo-stp": ["doc/examples/osmo-stp.cfg"], } apps = [(4239, "stp/osmo-stp", "OsmoSTP", "osmo-stp"), ] vty_command = ["./stp/osmo-stp", "-c", "doc/examples/osmo-stp.cfg"] vty_app = apps[0] libosmo-sccp-0.10.0/src/000077500000000000000000000000001332664606400147635ustar00rootroot00000000000000libosmo-sccp-0.10.0/src/Makefile.am000066400000000000000000000026521332664606400170240ustar00rootroot00000000000000AM_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=2:0:2 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) libosmo-sccp-0.10.0/src/ipa.c000066400000000000000000000214331332664606400157030ustar00rootroot00000000000000/* implementation of IPA/SCCPlite transport */ /* (C) 2015-2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 "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; } /* Patch a SCCP message and add point codes to Called/Calling Party (if missing) */ static struct msgb *patch_sccp_with_pc(struct osmo_ss7_asp *asp, struct msgb *sccp_msg_in, uint32_t opc, uint32_t dpc) { struct osmo_sccp_addr addr; struct msgb *sccp_msg_out; struct xua_msg *sua; int rc; /* start by converting SCCP to SUA */ sua = osmo_sccp_to_xua(sccp_msg_in); if (!sua) { LOGPASP(asp, DLSS7, LOGL_ERROR, "Couldn't convert SCCP to SUA: %s\n", msgb_hexdump(sccp_msg_in)); msgb_free(sccp_msg_in); return NULL; } /* free the input message and work with SUA version instead */ msgb_free(sccp_msg_in); rc = sua_addr_parse(&addr, sua, SUA_IEI_DEST_ADDR); switch (rc) { case 0: if (addr.presence & OSMO_SCCP_ADDR_T_PC) break; /* if there's no point code in dest_addr, add one */ addr.presence |= OSMO_SCCP_ADDR_T_PC; addr.pc = dpc; xua_msg_free_tag(sua, SUA_IEI_DEST_ADDR); xua_msg_add_sccp_addr(sua, SUA_IEI_DEST_ADDR, &addr); break; case -ENODEV: /* no destination address in message */ break; default: /* some other error */ xua_msg_free(sua); return NULL; } rc = sua_addr_parse(&addr, sua, SUA_IEI_SRC_ADDR); switch (rc) { case 0: if (addr.presence & OSMO_SCCP_ADDR_T_PC) break; /* if there's no point code in src_addr, add one */ addr.presence |= OSMO_SCCP_ADDR_T_PC; addr.pc = opc; xua_msg_free_tag(sua, SUA_IEI_SRC_ADDR); xua_msg_add_sccp_addr(sua, SUA_IEI_SRC_ADDR, &addr); break; case -ENODEV: /* no source address in message */ break; default: /* some other error */ xua_msg_free(sua); return NULL; } /* re-encode SUA to SCCP and return */ sccp_msg_out = osmo_sua_to_sccp(sua); xua_msg_free(sua); return sccp_msg_out; } static int ipa_rx_msg_sccp(struct osmo_ss7_asp *asp, struct msgb *msg) { int rc; struct m3ua_data_hdr data_hdr; struct xua_msg *xua; struct osmo_ss7_as *as = find_as_for_asp(asp); uint32_t opc, dpc; 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. Furthermore, the SCCP Called/Calling * Party are SSN-only, with no GT or PC. 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 :/ */ /* First, determine the DPC and OPC to use */ if (asp->cfg.is_server) { /* Source: the PC of the routing key */ opc = as->cfg.routing_key.pc; /* Destination: Based on VTY config */ dpc = as->cfg.pc_override.dpc; } else { /* Source: Based on VTY config */ opc = as->cfg.pc_override.dpc; /* Destination: PC of the routing key */ dpc = as->cfg.routing_key.pc; } /* Second, patch this into the SCCP message */ msg = patch_sccp_with_pc(asp, msg, opc, dpc); /* Third, create a MTP3/M3UA label with those point codes */ memset(&data_hdr, 0, sizeof(data_hdr)); data_hdr.si = MTP_SI_SCCP; data_hdr.opc = osmo_htonl(opc); data_hdr.dpc = osmo_htonl(dpc); /* Create M3UA message in XUA structure */ xua = m3ua_xfer_from_data(&data_hdr, msgb_l2(msg), msgb_l2len(msg)); msgb_free(msg); /* Update xua->mtp with values from data_hdr */ m3ua_dh_to_xfer_param(&xua->mtp, &data_hdr); /* Pass on as if we had received it from an M3UA ASP */ rc = m3ua_hmdc_rx_from_l2(asp->inst, xua); xua_msg_free(xua); return rc; } /*! \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: rc = ss7_asp_rx_unknown(asp, hh->proto, msg); } return rc; } libosmo-sccp-0.10.0/src/m3ua.c000066400000000000000000000547621332664606400160120ustar00rootroot00000000000000/* Minimal implementation of RFC 4666 - MTP3 User Adaptation Layer */ /* (C) 2015-2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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); /* xua will be freed by caller m3ua_rx_msg() */ } 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; } libosmo-sccp-0.10.0/src/mtp_pcap.c000066400000000000000000000043361332664606400167400ustar00rootroot00000000000000/* PCAP code from OpenBSC done by Holger Freyther */ /* * (C) 2010 by Holger Hans Peter Freyther * (C) 2010 by On-Waves * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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; } libosmo-sccp-0.10.0/src/osmo_ss7.c000066400000000000000000001543001332664606400167030ustar00rootroot00000000000000/* Core SS7 Instance/Linkset/Link/AS/ASP Handling */ /* (C) 2015-2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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, [OSMO_SS7_ASP_PROT_IPA] = 5000, }; 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; } /*! \brief bind all xUA servers belonging to an SS7 Instance * \param[in] inst SS7 Instance to apply the socket binding (and start listening) * \returns 0 on success; negative value on error */ int osmo_ss7_instance_bind(struct osmo_ss7_instance *inst) { struct osmo_xua_server *oxs; int rc = 0; llist_for_each_entry(oxs, &inst->xua_servers, list) { if (osmo_ss7_xua_server_bind(oxs) < 0) { LOGSS7(inst, LOGL_ERROR, "Unable to bind xUA server %s:%u\n", oxs->cfg.local.host, oxs->cfg.local.port); rc = -1; } } return rc; } /*! \brief bind all xUA servers on each of the stored SS7 instances * \returns 0 on success; negative value on error */ int osmo_ss7_bind_all_instances() { OSMO_ASSERT(ss7_initialized); struct osmo_ss7_instance *inst; int rc = 0; llist_for_each_entry(inst, &osmo_ss7_instances, list) { if (osmo_ss7_instance_bind(inst) < 0 ) { LOGSS7(inst, LOGL_ERROR, "Unable to bind all xUA servers in ss7 instance\n"); rc = -1; } } return rc; } /*********************************************************************** * 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); /* we don't return error in here because osmo_stream_cli_open2() will continue to retry to connect so the error is transient */ } /* 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 rc = ss7_asp_rx_unknown(asp, ppid, msg); 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 rc = ss7_asp_rx_unknown(asp, ppid, msg); 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 configured with 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); 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)); 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; } /*! \brief Set the xUA server to bind/listen to the currently configured ip/port * \param[in] xs xUA server to operate * \returns 0 on success, negative value on error. */ int osmo_ss7_xua_server_bind(struct osmo_xua_server *xs) { LOGP(DLSS7, LOGL_INFO, "(Re)binding %s Server to %s:%u\n", get_value_string(osmo_ss7_asp_protocol_vals, xs->cfg.proto), xs->cfg.local.host, xs->cfg.local.port); return osmo_stream_srv_link_open(xs->server); } 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; } } static osmo_ss7_asp_rx_unknown_cb *g_osmo_ss7_asp_rx_unknown_cb; int ss7_asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg) { if (g_osmo_ss7_asp_rx_unknown_cb) return (*g_osmo_ss7_asp_rx_unknown_cb)(asp, ppid_mux, msg); switch(asp->cfg.proto) { case OSMO_SS7_ASP_PROT_IPA: LOGPASP(asp, DLSS7, LOGL_NOTICE, "Rx IPA for unknown Stream ID 0x%02x: %s\n", ppid_mux, msgb_hexdump(msg)); break; default: LOGPASP(asp, DLSS7, LOGL_NOTICE, "Rx SCTP chunk for unknown PPID %u: %s\n", ppid_mux, msgb_hexdump(msg)); break; } return 0; } /*! Register a call-back function for unknown SCTP PPID / IPA Stream ID */ void osmo_ss7_register_rx_unknown_cb(osmo_ss7_asp_rx_unknown_cb *cb) { g_osmo_ss7_asp_rx_unknown_cb = cb; } libosmo-sccp-0.10.0/src/osmo_ss7_hmrt.c000066400000000000000000000234061332664606400177370ustar00rootroot00000000000000/*********************************************************************** * MTP Level 3 - Signalling message handling (SMH) Figure 23/Q.704 ***********************************************************************/ /* (C) 2015-2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 "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; } } /*! Return human readable representation of the route, in a static buffer. * This uses both osmo_ss7_pointcode_print() and osmo_ss7_pointcode_print2(), so pairing * osmo_ss7_route_name() with osmo_ss7_pointcode_print() in the same printf statement is likely to * conflict. * \param[in] rt The route information to print, or NULL. * \param[in] list_asps If true, append info for all ASPs for the route's AS. * \returns A string constant or static buffer. */ const char *osmo_ss7_route_name(struct osmo_ss7_route *rt, bool list_asps) { static char buf[256]; char *pos = buf; struct osmo_ss7_instance *inst; size_t l; if (!rt) return "no route"; inst = rt->rtable->inst; #define APPEND(fmt, args ...) \ do { \ l = snprintf(pos, sizeof(buf) - (pos - buf), fmt, ## args); \ pos += l; \ if (pos - buf >= sizeof(buf) ) \ goto out; \ } while (0) APPEND("pc=%u=%s mask=0x%x=%s", rt->cfg.pc, osmo_ss7_pointcode_print(inst, rt->cfg.pc), rt->cfg.mask, osmo_ss7_pointcode_print2(inst, rt->cfg.mask)); if (rt->dest.as) { struct osmo_ss7_as *as = rt->dest.as; int i; APPEND(" via AS %s proto=%s", as->cfg.name, osmo_ss7_asp_protocol_name(as->cfg.proto)); if (list_asps) { for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { struct osmo_ss7_asp *asp = as->cfg.asps[i]; if (!asp) continue; APPEND(" ASP"); if (asp->cfg.name) APPEND(" %s", asp->cfg.name); if (asp->sock_name) APPEND(" %s", asp->sock_name); } } } else if (rt->dest.linkset) APPEND(" via linkset %s", rt->dest.linkset->cfg.name); else APPEND(" has no route set"); #undef APPEND out: buf[sizeof(buf)-1] = '\0'; return buf; } /* 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; if (log_check_level(DLSS7, LOGL_DEBUG)) { /* osmo_ss7_route_name() calls osmo_ss7_pointcode_print() and * osmo_ss7_pointcode_print2(), guard against its static buffer being * overwritten. */ const char *rt_name = osmo_ss7_route_name(rt, false); DEBUGP(DLSS7, "Found route for dpc=%u=%s: %s\n", dpc, osmo_ss7_pointcode_print(inst, dpc), rt_name); } 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) { if (log_check_level(DLSS7, LOGL_ERROR)) { /* osmo_ss7_route_name() calls osmo_ss7_pointcode_print() and * osmo_ss7_pointcode_print2(), guard against its static buffer being * overwritten. */ const char *rt_name = osmo_ss7_route_name(rt, false); LOGP(DLSS7, LOGL_ERROR, "Found route for dpc=%u=%s: %s," " but MTP-TRANSFER.req unsupported for linkset.\n", dpc, osmo_ss7_pointcode_print(inst, dpc), rt_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; } libosmo-sccp-0.10.0/src/osmo_ss7_vty.c000066400000000000000000001477671332664606400176300ustar00rootroot00000000000000/* Core SS7 Instance/Linkset/Link/AS/ASP VTY Interface */ /* (C) 2015-2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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]); if (osmo_ss7_xua_server_bind(xs) < 0) { vty_out(vty, "Unable to bind xUA server to IP %s%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } 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); if (xs->cfg.local.host) 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); } static void vty_dump_xua_server(struct vty *vty, struct osmo_xua_server *xs) { vty_out(vty, "xUA server for %s on %s:%u%s", get_value_string(osmo_ss7_asp_protocol_vals, xs->cfg.proto), xs->cfg.local.host ? xs->cfg.local.host : "0.0.0.0", xs->cfg.local.port, VTY_NEWLINE); } /* List all addressbook entries */ DEFUN(show_cs7_xua, show_cs7_xua_cmd, "show cs7 "XUA_VAR_STR" [<0-65534>]", SHOW_STR CS7_STR XUA_VAR_HELP_STR "Port Number") { struct osmo_ss7_instance *inst; struct osmo_xua_server *xs; enum osmo_ss7_asp_protocol proto = parse_asp_proto(argv[0]); llist_for_each_entry(inst, &osmo_ss7_instances, list) { if (argc > 1) { int port = atoi(argv[1]); xs = osmo_ss7_xua_server_find(inst, proto, port); if (!xs) { vty_out(vty, "%% No matching server found%s", VTY_NEWLINE); return CMD_WARNING; } vty_dump_xua_server(vty, xs); } else { llist_for_each_entry(xs, &inst->xua_servers, list) { if (xs->cfg.proto == proto) vty_dump_xua_server(vty, xs); } } } return CMD_SUCCESS; } /*********************************************************************** * 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[1], 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, "Error: SCCP address name to long: '%s'%s", name, VTY_NEWLINE); return CMD_ERR_INCOMPLETE; } /* 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, "Error: SCCP address name already used in ss7 instance %u: '%s'%s", entry->inst->cfg.id, entry->name, VTY_NEWLINE); return CMD_ERR_INCOMPLETE; } 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); 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); 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); install_element_ve(&show_cs7_xua_cmd); /* the mother of all VTY config nodes */ install_element(CONFIG_NODE, &cs7_instance_cmd); install_node(&cs7_node, config_write_cs7); 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); 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); 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); 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); 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); } libosmo-sccp-0.10.0/src/sccp.c000066400000000000000000001160741332664606400160700ustar00rootroot00000000000000/* * SCCP management code * * (C) 2009, 2010, 2013 by Holger Hans Peter Freyther * (C) 2009, 2010, 2013 by On-Waves * * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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; } libosmo-sccp-0.10.0/src/sccp2sua.c000066400000000000000000001212561332664606400166610ustar00rootroot00000000000000/* SCCP <-> SUA transcoding routines */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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, "IEI %u: Parsed Addr: %s\n", iei, 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; } libosmo-sccp-0.10.0/src/sccp_helpers.c000066400000000000000000000240521332664606400176040ustar00rootroot00000000000000/* SCCP User SAP helper functions */ /* (C) 2015-2017 by Harald Welte * (C) 2016 by sysmocom s.m.f.c. GmbH * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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" #define SCU_MSG_SIZE 2048 #define SCU_MSG_HEADROOM 512 static struct msgb *scu_msgb_alloc(const char *name) { return msgb_alloc_headroom(SCU_MSG_SIZE+SCU_MSG_HEADROOM, SCU_MSG_HEADROOM, name); } 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)); if (addr->gt.gti != OSMO_SCCP_GTI_NO_GT || addr->presence & OSMO_SCCP_ADDR_T_GT) 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)); if (addr->gt.gti != OSMO_SCCP_GTI_NO_GT || addr->presence & OSMO_SCCP_ADDR_T_GT) 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; } /* Derive ss7 from the sccp instance and call osmo_sccp_addr_name() with that. * If sccp is passed as NULL, simply use the default point code format. */ char *osmo_sccp_inst_addr_name(const struct osmo_sccp_instance *sccp, const struct osmo_sccp_addr *addr) { return osmo_sccp_addr_name(sccp? sccp->ss7 : NULL, addr); } libosmo-sccp-0.10.0/src/sccp_internal.h000066400000000000000000000051011332664606400177550ustar00rootroot00000000000000#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); libosmo-sccp-0.10.0/src/sccp_sap.c000066400000000000000000000110451332664606400167230ustar00rootroot00000000000000/* SCCP User SAP related routines */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 } }; libosmo-sccp-0.10.0/src/sccp_sclc.c000066400000000000000000000257321332664606400170740ustar00rootroot00000000000000/* SCCP Connectionless Control (SCLC) according to ITU-T Q.713/Q.714 */ /* (C) 2015-2017 by Harald Welte * All Rights reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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; } } libosmo-sccp-0.10.0/src/sccp_scoc.c000066400000000000000000001535271332664606400171030ustar00rootroot00000000000000/* SCCP Connection Oriented (SCOC) according to ITU-T Q.713/Q.714 */ /* (C) 2015-2017 by Harald Welte * All Rights reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 SCCP user with proper reply */ LOGP(DLSCCP, LOGL_ERROR, "Cannot create conn-id for primitive %s\n", osmo_scu_prim_name(&prim->oph)); 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 SCCP user with proper reply */ LOGP(DLSCCP, LOGL_ERROR, "Received unknown conn-id %u for primitive %s\n", scu_prim_conn_id(prim), osmo_scu_prim_name(&prim->oph)); 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); } libosmo-sccp-0.10.0/src/sccp_scrc.c000066400000000000000000000332061332664606400170750ustar00rootroot00000000000000/* SCCP Routing Control (SCRC) according to ITU-T Q.714 */ /* (C) 2015-2017 by Harald Welte * All Rights reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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"); #if 1 /* Prevent endless recursion, see OS#2666. */ sccp_sclc_rx_scrc_rout_fail(inst, xua, SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE); return 0; #else /* Node 7 (Sheet 5) */ return scrc_node_7(inst, xua, called); #endif } /* 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); } libosmo-sccp-0.10.0/src/sccp_user.c000066400000000000000000000504731332664606400171260ustar00rootroot00000000000000/* SCCP User related routines */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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; } /*! Compose a human readable string to describe the SCCP user's connection. * The output follows ['':], e.g. "'OsmoHNBW':RI=SSN_PC,PC=0.23.5,SSN=RANAP", * or just "RI=SSN_PC,PC=0.23.5,SSN=RANAP" if no scu->name is set. * This calls osmo_sccp_addr_name(), which returns a static buffer; hence calling this function and * osmo_sccp_addr_name() in the same printf statement is likely to conflict. */ const char *osmo_sccp_user_name(struct osmo_sccp_user *scu) { static char buf[128]; struct osmo_sccp_addr sca; /* Interestingly enough, the osmo_sccp_user stores an SSN and PC, but not in an osmo_sccp_addr * struct. To be able to use osmo_sccp_addr_name(), we need to first create an osmo_sccp_addr. */ osmo_sccp_make_addr_pc_ssn(&sca, scu->pc, scu->ssn); snprintf(buf, sizeof(buf), "%s%s%s", scu->name && *scu->name ? scu->name : "", scu->name && *scu->name ? ":" : "", osmo_sccp_addr_name(scu->inst->ss7, &sca)); buf[sizeof(buf)-1] = '\0'; return buf; } /*********************************************************************** * 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; int rc; 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; rc = osmo_ss7_xua_server_bind(xs); if (rc < 0) goto out_xs; /* 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; /* 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); talloc_free(as_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; } libosmo-sccp-0.10.0/src/sccp_vty.c000066400000000000000000000075671332664606400170000ustar00rootroot00000000000000/* Core SS7 Instance/Linkset/Link/AS/ASP VTY Interface */ /* (C) 2015-2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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); } libosmo-sccp-0.10.0/src/sua.c000066400000000000000000000504471332664606400157310ustar00rootroot00000000000000/* Minimal implementation of RFC 3868 - SCCP User Adaptation Layer */ /* (C) 2015-2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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) { struct msgb *msg; if (!name) name = "SCCP"; msg = msgb_alloc_headroom(SCCP_MSG_SIZE+SCCP_MSG_HEADROOM, SCCP_MSG_HEADROOM, name); if (!msg) return NULL; msg->l2h = msg->tail; return msg; } /*********************************************************************** * 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; } libosmo-sccp-0.10.0/src/xua_as_fsm.c000066400000000000000000000245141332664606400172620ustar00rootroot00000000000000/* 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. * * SPDX-License-Identifier: GPL-2.0+ * * 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 void xua_as_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv; osmo_timer_del(&xafp->recovery.t_r); } 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, .cleanup = xua_as_fsm_cleanup, }; /*! \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; } libosmo-sccp-0.10.0/src/xua_as_fsm.h000066400000000000000000000004601332664606400172610ustar00rootroot00000000000000#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); libosmo-sccp-0.10.0/src/xua_asp_fsm.c000066400000000000000000001002211332664606400174300ustar00rootroot00000000000000/* SCCP M3UA / SUA ASP osmo_fsm according to RFC3868 4.3.1 */ /* (C) Copyright 2017 by Harald Welte * * All Rights reserved. * * SPDX-License-Identifier: GPL-2.0+ * * 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 void xua_asp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct xua_asp_fsm_priv *xafp = fi->priv; osmo_timer_del(&xafp->t_ack.timer); } 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, .cleanup = xua_asp_fsm_cleanup, }; 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) { struct ipa_asp_fsm_priv *iafp = fi->priv; struct osmo_ss7_asp *asp = iafp->asp; struct osmo_ss7_instance *inst = asp->inst; struct osmo_ss7_as *as = osmo_ss7_as_find_by_rctx(inst, 0); OSMO_ASSERT(as); 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); /* As opposed to M3UA, there is no RKM and we have to implicitly automatically add * a route once an IPA connection has come up */ osmo_ss7_route_create(inst->rtable_system, as->cfg.routing_key.pc, 0xffffff, as->cfg.name); 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)); /* avoid possible unsigned integer underflow, as ipa_ccm_make_id_resp_from_req() * expects an unsigned integer, and in case of a zero-length L2 message we might * have data_len == -1 here */ if (data_len < 0) data_len = 0; /* 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, }, }; static void ipa_asp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct ipa_asp_fsm_priv *iafp = fi->priv; struct osmo_ss7_asp *asp = iafp->asp; struct osmo_ss7_instance *inst = asp->inst; struct osmo_ss7_as *as = osmo_ss7_as_find_by_rctx(inst, 0); struct osmo_ss7_route *rt; OSMO_ASSERT(as); /* find the route which we have created if we ever reached ipa_asp_fsm_wait_id_ack2 */ rt = osmo_ss7_route_find_dpc_mask(inst->rtable_system, as->cfg.routing_key.pc, 0xffffff); /* no route found, bail out */ if (!rt) return; /* route points to different AS, bail out */ if (rt->dest.as != as) return; osmo_ss7_route_destroy(rt); osmo_timer_del(&iafp->pong_timer); } 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, .cleanup = ipa_asp_fsm_cleanup, }; /*! \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; } libosmo-sccp-0.10.0/src/xua_asp_fsm.h000066400000000000000000000016651332664606400174510ustar00rootroot00000000000000#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); libosmo-sccp-0.10.0/src/xua_default_lm_fsm.c000066400000000000000000000256421332664606400207760ustar00rootroot00000000000000/* Default XUA Layer Manager */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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); OSMO_ASSERT(prim); 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; } libosmo-sccp-0.10.0/src/xua_internal.h000066400000000000000000000064111332664606400176270ustar00rootroot00000000000000#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); int ss7_asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg); libosmo-sccp-0.10.0/src/xua_msg.c000066400000000000000000000272531332664606400166030ustar00rootroot00000000000000/* Routines for generating and parsing messages */ /* (C) 2011 by Holger Hans Peter Freyther * (C) 2016-2017 by Harald Welte * * SPDX-License-Identifier: GPL-2.0+ * * 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 = NULL; /* Allocate the root talloc context used for xua_msg_alloc(). */ void osmo_xua_msg_tall_ctx_init(void *ctx) { tall_xua = talloc_named_const(ctx, 0, "xua_msg"); } 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; } libosmo-sccp-0.10.0/src/xua_rkm.c000066400000000000000000000446711332664606400166110ustar00rootroot00000000000000/* xUA Routing Key Management (RKM) as per RFC 4666 */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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; } msgb_free(prim->oph.msg); 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); } } libosmo-sccp-0.10.0/stp/000077500000000000000000000000001332664606400150025ustar00rootroot00000000000000libosmo-sccp-0.10.0/stp/Makefile.am000066400000000000000000000005321332664606400170360ustar00rootroot00000000000000AM_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) libosmo-sccp-0.10.0/stp/stp_main.c000066400000000000000000000126421332664606400167650ustar00rootroot00000000000000/* 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 = "OsmoSTP", .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"); msgb_talloc_ctx_init(tall_stp_ctx, 0); osmo_init_logging2(tall_stp_ctx, &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); } osmo_ss7_bind_all_instances(); rc = telnet_init_dynif(NULL, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_STP); if (rc < 0) { perror("Error 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); } } libosmo-sccp-0.10.0/tests/000077500000000000000000000000001332664606400153365ustar00rootroot00000000000000libosmo-sccp-0.10.0/tests/Makefile.am000066400000000000000000000030071332664606400173720ustar00rootroot00000000000000SUBDIRS = 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 $@ libosmo-sccp-0.10.0/tests/m2ua/000077500000000000000000000000001332664606400162025ustar00rootroot00000000000000libosmo-sccp-0.10.0/tests/m2ua/Makefile.am000066400000000000000000000003751332664606400202430ustar00rootroot00000000000000AM_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) libosmo-sccp-0.10.0/tests/m2ua/m2ua_test.c000066400000000000000000000060541332664606400202560ustar00rootroot00000000000000/* (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; } libosmo-sccp-0.10.0/tests/m2ua/m2ua_test.ok000066400000000000000000000001031332664606400204320ustar00rootroot00000000000000Testing ASP UP parsing. Testing parsing of data. All tests passed. libosmo-sccp-0.10.0/tests/mtp/000077500000000000000000000000001332664606400161365ustar00rootroot00000000000000libosmo-sccp-0.10.0/tests/mtp/Makefile.am000066400000000000000000000002761332664606400201770ustar00rootroot00000000000000AM_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 libosmo-sccp-0.10.0/tests/mtp/mtp_parse_test.c000066400000000000000000000470621332664606400213440ustar00rootroot00000000000000/* 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; } libosmo-sccp-0.10.0/tests/mtp/mtp_parse_test.ok000066400000000000000000000000571332664606400215240ustar00rootroot00000000000000Basic MTP Structure testing. All tests passed. libosmo-sccp-0.10.0/tests/sccp/000077500000000000000000000000001332664606400162665ustar00rootroot00000000000000libosmo-sccp-0.10.0/tests/sccp/Makefile.am000066400000000000000000000003741332664606400203260ustar00rootroot00000000000000AM_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) libosmo-sccp-0.10.0/tests/sccp/sccp_test.c000066400000000000000000000636701332664606400204350ustar00rootroot00000000000000/* * 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; msgb_free(data); } 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; msgb_free(data); } 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 (parse_result[current_test].dst_gti_data && 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 (parse_result[current_test].src_gti_data && 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)); } } talloc_free(msg); } 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) {} libosmo-sccp-0.10.0/tests/sccp/sccp_test.ok000066400000000000000000000036431332664606400206160ustar00rootroot00000000000000Testing 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. libosmo-sccp-0.10.0/tests/ss7/000077500000000000000000000000001332664606400160525ustar00rootroot00000000000000libosmo-sccp-0.10.0/tests/ss7/Makefile.am000066400000000000000000000005551332664606400201130ustar00rootroot00000000000000AM_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 libosmo-sccp-0.10.0/tests/ss7/ss7_test.c000066400000000000000000000224501332664606400177740ustar00rootroot00000000000000#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; void *tall_ctx = talloc_named_const(NULL, 1, "example"); msgb_talloc_ctx_init(tall_ctx, 0); osmo_init_logging2(tall_ctx, &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); } libosmo-sccp-0.10.0/tests/ss7/ss7_test.err000066400000000000000000000036301332664606400203410ustar00rootroot000000000000000: 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 libosmo-sccp-0.10.0/tests/ss7/ss7_test.ok000066400000000000000000000017571332664606400201720ustar00rootroot00000000000000Testing 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 libosmo-sccp-0.10.0/tests/testsuite.at000066400000000000000000000016161332664606400177210ustar00rootroot00000000000000AT_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 libosmo-sccp-0.10.0/tests/xua/000077500000000000000000000000001332664606400161335ustar00rootroot00000000000000libosmo-sccp-0.10.0/tests/xua/Makefile.am000066400000000000000000000006401332664606400201670ustar00rootroot00000000000000AM_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 libosmo-sccp-0.10.0/tests/xua/sccp_test_data.c000066400000000000000000000063241332664606400212640ustar00rootroot00000000000000#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 }; libosmo-sccp-0.10.0/tests/xua/sccp_test_data.h000066400000000000000000000007361332664606400212720ustar00rootroot00000000000000#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]; libosmo-sccp-0.10.0/tests/xua/xua_test.c000066400000000000000000000370331332664606400201410ustar00rootroot00000000000000/* (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_addr_ssn_bssmap), }, }, }, { .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); talloc_free(nested); talloc_free(xua); } 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; } libosmo-sccp-0.10.0/tests/xua/xua_test.err000066400000000000000000000001051332664606400204750ustar00rootroot00000000000000Unsupported Translation Type 2requested Unsupported GTI 23 requested libosmo-sccp-0.10.0/tests/xua/xua_test.ok000066400000000000000000000213531332664606400203260ustar00rootroot00000000000000digits='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 parsed: RI=2,SSN=254 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: [L2]> 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: [L2]> 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: [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 => 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: [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 => 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: [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 => 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: [L2]> 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: [L2]> 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: [L2]> 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: [L2]> 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: [L2]> 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: [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 Parsing M3UA Message Parsing Nested M3UA Routing Key IE Testing SCCP Address Encode/Decode => NOGT-PC1024 input addr: RI=2,PC=1024 rc=3, expected rc=3 encoded addr: 410004 expected addr: 410004 decod addr: RI=2,PC=1024 => NOGT-PC16383 input addr: RI=2,PC=16383 rc=3, expected rc=3 encoded addr: 41ff3f expected addr: 41ff3f decod addr: RI=2,PC=16383 => NOGT-PC16383-SSN90 input addr: RI=2,PC=16383,SSN=90 rc=4, expected rc=4 encoded addr: 43ff3f5a expected addr: 43ff3f5a decod addr: RI=2,PC=16383,SSN=90 => 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.