diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a7cb104 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +hnbgwdebian/*.log +*.o +*.lo +*.a +.deps +Makefile +Makefile.in +config.h +config.h.in +*.pc +*~ + +*.*~ +*.sw? +.libs +*.pyc +*.gcda +*.gcno + +**/TAGS + +#configure +aclocal.m4 +autom4te.cache/ +config.log +config.status +config.guess +config.sub +configure +compile +depcomp +install-sh +missing +stamp-h1 +libtool +ltmain.sh +m4/*.m4 + +# git-version-gen magic +.tarball-version +.version +osmo-hnbgw-*.tar.bz2 +osmo-hnbgw-*.tar.gz + +tags +/deps + +src/osmo-hnbgw/osmo-hnbgw + +#tests +tests/testsuite.dir +tests/*/*_test + +tests/atconfig +tests/atlocal +tests/package.m4 +tests/testsuite +tests/testsuite.log + +writtenconfig/ + +# manuals +doc/manuals/*.html +doc/manuals/*.svg +doc/manuals/*.pdf +doc/manuals/*__*.png +doc/manuals/*.check +doc/manuals/generated/ +doc/manuals/osmohnbgw-usermanual.xml +doc/manuals/common +doc/manuals/build + +contrib/osmo-hnbgw.spec diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..b69f431 --- /dev/null +++ b/.gitreview @@ -0,0 +1,3 @@ +[gerrit] +host=gerrit.osmocom.org +project=osmo-hnbgw diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f621811 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Pau Espin Pedrol diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e3176be --- /dev/null +++ b/Makefile.am @@ -0,0 +1,36 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 + +## FIXME: automake >= 1.13 or autoconf >= 2.70 provide better suited AC_CONFIG_MACRO_DIRS for configure.ac +## remove line below when OE toolchain is updated to version which include those +ACLOCAL_AMFLAGS = -I m4 +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +SUBDIRS = \ + include \ + src \ + tests \ + doc \ + contrib \ + $(NULL) + +BUILT_SOURCES = $(top_srcdir)/.version +EXTRA_DIST = \ + .version \ + contrib/osmo-hnbgw.spec.in \ + debian \ + git-version-gen \ + osmoappdesc.py \ + $(NULL) + +AM_DISTCHECK_CONFIGURE_FLAGS = \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) + +@RELMAKE@ + +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb5afe3 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +osmo-hnbgw - Osmocom hNodeB Implementation +=========================================== + +This repository contains a C-language implementation of a 3G Home NodeB Gateway (OsmoHNBGW). +It is part of the [Osmocom](https://osmocom.org/) Open Source Mobile Communications +project. + +You can use it to interface Iuh-speaking Home NodeB (HnodeB), such as +osmo-hnodeb or ip.access nano3g, to Iu-speaking MSCs and SGSNs. + +Homepage +-------- + +The official homepage of the project is +https://osmocom.org/projects/osmohnbgw/wiki + +GIT Repository +-------------- + +You can clone from the official osmo-hnbgw.git repository using + + git clone git://git.osmocom.org/osmo-hnbgw.git + +There is a cgit interface at https://git.osmocom.org/osmo-hnbgw/ + +Documentation +------------- + +User Manuals and VTY reference manuals are [optionally] built in PDF form +as part of the build process. + +Pre-rendered PDF version of the current "master" can be found at +[User Manual](https://ftp.osmocom.org/docs/latest/osmohnbgw-usermanual.pdf) +as well as the [VTY Reference Manual](https://ftp.osmocom.org/docs/latest/osmohnbgw-vty-reference.pdf) + + +Mailing List +------------ + +Discussions related to osmo-hnbgw are happening on the +openbsc@lists.osmocom.org mailing list, please see +https://lists.osmocom.org/mailman/listinfo/openbsc for subscription +options and the list archive. + +Please observe the [Osmocom Mailing List +Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules) +when posting. + +Contributing +------------ + +Our coding standards are described at +https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards + +We us a gerrit based patch submission/review process for managing +contributions. Please see +https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit for +more details + +The current patch queue for osmo-hnbgw can be seen at +https://gerrit.osmocom.org/#/q/project:osmo-hnbgw+status:open diff --git a/TODO-RELEASE b/TODO-RELEASE new file mode 100644 index 0000000..d0852fc --- /dev/null +++ b/TODO-RELEASE @@ -0,0 +1,9 @@ +# When cleaning up this file: bump API version in corresponding Makefile.am and rename corresponding debian/lib*.install +# according to https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info +# In short: +# LIBVERSION=c:r:a +# If the library source code has changed at all since the last update, then increment revision: c:r + 1:a. +# If any interfaces have been added, removed, or changed since the last update: c + 1:0:0. +# If any interfaces have been added since the last public release: c:r:a + 1. +# If any interfaces have been removed or changed since the last public release: c:r:0. +#library what description / commit summary line diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..3e02bd9 --- /dev/null +++ b/configure.ac @@ -0,0 +1,242 @@ +dnl Process this file with autoconf to produce a configure script +AC_INIT([osmo-hnbgw], + 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) + +CFLAGS="$CFLAGS -std=gnu11" + +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 patching ${archive_cmds} to affect generation of file "libtool" to fix linking with clang +AS_CASE(["$LD"],[*clang*], + [AS_CASE(["${host_os}"], + [*linux*],[archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'])]) + +dnl check for pkg-config (explained in detail in libosmocore/configure.ac) +AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no) +if test "x$PKG_CONFIG_INSTALLED" = "xno"; then + AC_MSG_WARN([You need to install pkg-config]) +fi +PKG_PROG_PKG_CONFIG([0.20]) + +dnl check for AX_CHECK_COMPILE_FLAG +m4_ifdef([AX_CHECK_COMPILE_FLAG], [], [ + AC_MSG_ERROR([Please install autoconf-archive; re-run 'autoreconf -fi' for it to take effect.]) + ]) + +dnl checks for libraries +AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""]) +AC_SUBST(LIBRARY_DL) +old_LIBS=$LIBS +AC_SEARCH_LIBS([sctp_recvmsg], [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_recvmsg not found in searched libs])]) +LIBS=$old_LIBS + +PKG_CHECK_MODULES(LIBASN1C, libasn1c >= 0.9.30) +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.6.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.6.0) +PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.6.0) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.6.0) +PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 1.1.0) +PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 1.5.0) +PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.1.0) +PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.1.0) +PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.1.0) + + +dnl checks for header files +AC_HEADER_STDC + +dnl Checks for typedefs, structures and compiler characteristics + +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) + +AX_CHECK_COMPILE_FLAG([-Werror=implicit], [CFLAGS="$CFLAGS -Werror=implicit"]) +AX_CHECK_COMPILE_FLAG([-Werror=maybe-uninitialized], [CFLAGS="$CFLAGS -Werror=maybe-uninitialized"]) +AX_CHECK_COMPILE_FLAG([-Werror=memset-transposed-args], [CFLAGS="$CFLAGS -Werror=memset-transposed-args"]) +AX_CHECK_COMPILE_FLAG([-Wnull-dereference], [CFLAGS="$CFLAGS -Wnull-dereference"]) +AX_CHECK_COMPILE_FLAG([-Werror=sizeof-array-argument], [CFLAGS="$CFLAGS -Werror=sizeof-array-argument"]) +AX_CHECK_COMPILE_FLAG([-Werror=sizeof-pointer-memaccess], [CFLAGS="$CFLAGS -Werror=sizeof-pointer-memaccess"]) + +# Coverage build taken from WebKit's configure.in +AC_MSG_CHECKING([whether to enable code coverage support]) +AC_ARG_ENABLE(coverage, + AC_HELP_STRING([--enable-coverage], + [enable code coverage support [default=no]]), + [],[enable_coverage="no"]) +AC_MSG_RESULT([$enable_coverage]) +if test "$enable_coverage" = "yes"; then + COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" + COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" + AC_SUBST([COVERAGE_CFLAGS]) + AC_SUBST([COVERAGE_LDFLAGS]) +fi + +AC_ARG_ENABLE(profile, + [AS_HELP_STRING([--enable-profile], [Compile with profiling support enabled], )], + [profile=$enableval], [profile="no"]) +if test x"$profile" = x"yes" +then + CFLAGS="$CFLAGS -pg" + CPPFLAGS="$CPPFLAGS -pg" +fi + +AC_ARG_ENABLE([external_tests], + AC_HELP_STRING([--enable-external-tests], + [Include the VTY/CTRL tests in make check [default=no]]), + [enable_ext_tests="$enableval"],[enable_ext_tests="no"]) +if test "x$enable_ext_tests" = "xyes" ; then + AC_CHECK_PROG(PYTHON3_AVAIL,python3,yes) + if test "x$PYTHON3_AVAIL" != "xyes" ; then + AC_MSG_ERROR([Please install python3 to run the VTY/CTRL tests.]) + fi + AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes) + if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then + AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.]) + fi +fi +AC_MSG_CHECKING([whether to enable VTY/CTRL tests]) +AC_MSG_RESULT([$enable_ext_tests]) +AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes") + +# Generate manuals +AC_ARG_ENABLE(manuals, + [AS_HELP_STRING( + [--enable-manuals], + [Generate manual PDFs [default=no]], + )], + [osmo_ac_build_manuals=$enableval], [osmo_ac_build_manuals="no"]) +AM_CONDITIONAL([BUILD_MANUALS], [test x"$osmo_ac_build_manuals" = x"yes"]) +AC_ARG_VAR(OSMO_GSM_MANUALS_DIR, [path to common osmo-gsm-manuals files, overriding pkg-config and "../osmo-gsm-manuals" + fallback]) +if test x"$osmo_ac_build_manuals" = x"yes" +then + # Find OSMO_GSM_MANUALS_DIR (env, pkg-conf, fallback) + if test -n "$OSMO_GSM_MANUALS_DIR"; then + echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from env)" + else + OSMO_GSM_MANUALS_DIR="$($PKG_CONFIG osmo-gsm-manuals --variable=osmogsmmanualsdir 2>/dev/null)" + if test -n "$OSMO_GSM_MANUALS_DIR"; then + echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from pkg-conf)" + else + OSMO_GSM_MANUALS_DIR="../osmo-gsm-manuals" + echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (fallback)" + fi + fi + if ! test -d "$OSMO_GSM_MANUALS_DIR"; then + AC_MSG_ERROR("OSMO_GSM_MANUALS_DIR does not exist! Install osmo-gsm-manuals or set OSMO_GSM_MANUALS_DIR.") + fi + + # Find and run check-depends + CHECK_DEPENDS="$OSMO_GSM_MANUALS_DIR/check-depends.sh" + if ! test -x "$CHECK_DEPENDS"; then + CHECK_DEPENDS="osmo-gsm-manuals-check-depends" + fi + if ! $CHECK_DEPENDS; then + AC_MSG_ERROR("missing dependencies for --enable-manuals") + fi + + # Put in Makefile with absolute path + OSMO_GSM_MANUALS_DIR="$(realpath "$OSMO_GSM_MANUALS_DIR")" + AC_SUBST([OSMO_GSM_MANUALS_DIR]) +fi + +# https://www.freedesktop.org/software/systemd/man/daemon.html +AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, + [with_systemdsystemunitdir=auto]) +AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ + def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) + + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) +AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) +AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + +AC_OUTPUT( + include/Makefile + include/osmocom/Makefile + include/osmocom/hnbgw/Makefile + src/Makefile + src/osmo-hnbgw/Makefile + tests/Makefile + tests/atlocal + doc/Makefile + doc/examples/Makefile + doc/manuals/Makefile + contrib/Makefile + contrib/systemd/Makefile + contrib/osmo-hnbgw.spec + Makefile) diff --git a/contrib/Makefile.am b/contrib/Makefile.am new file mode 100644 index 0000000..3439c97 --- /dev/null +++ b/contrib/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = systemd diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh new file mode 100755 index 0000000..24607db --- /dev/null +++ b/contrib/jenkins.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# jenkins build helper script for osmo-hnbgw. This is how we build on jenkins.osmocom.org +# +# environment variables: +# * WITH_MANUALS: build manual PDFs if set to "1" +# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1") +# + +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" +export PATH="$inst/bin:$PATH" + +osmo-build-dep.sh libosmocore "" --disable-doxygen +osmo-build-dep.sh libosmo-abis +osmo-build-dep.sh libosmo-netif +osmo-build-dep.sh libosmo-sccp +osmo-build-dep.sh libasn1c +osmo-build-dep.sh osmo-iuh + +# Additional configure options and depends +CONFIG="" +if [ "$WITH_MANUALS" = "1" ]; then + CONFIG="--enable-manuals" +fi + +set +x +echo +echo +echo +echo " =============================== osmo-hnbgw ===============================" +echo +set -x + +cd "$base" +autoreconf --install --force +./configure --enable-sanitize --enable-external-tests $CONFIG +$MAKE $PARALLEL_MAKE +LD_LIBRARY_PATH="$inst/lib" $MAKE check \ + || cat-testlogs.sh +LD_LIBRARY_PATH="$inst/lib" \ + DISTCHECK_CONFIGURE_FLAGS="--enable-vty-tests --enable-external-tests $CONFIG" \ + $MAKE $PARALLEL_MAKE distcheck \ + || cat-testlogs.sh + +if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then + make -C "$base/doc/manuals" publish +fi + +$MAKE $PARALLEL_MAKE maintainer-clean +osmo-clean-workspace.sh diff --git a/contrib/osmo-hnbgw.spec.in b/contrib/osmo-hnbgw.spec.in new file mode 100644 index 0000000..5ab0f5a --- /dev/null +++ b/contrib/osmo-hnbgw.spec.in @@ -0,0 +1,97 @@ +# +# spec file for package osmo-hnbgw +# +# Copyright (c) 2017, Martin Hauke +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +## Disable LTO for now since it breaks compilation of the tests +## https://osmocom.org/issues/4113 +%define _lto_cflags %{nil} + +Name: osmo-hnbgw +Version: @VERSION@ +Release: 0 +Summary: OsmoHNBGW: Osmocom's Base Station Controller for 2G CS mobile networks +License: AGPL-3.0-or-later AND GPL-2.0-or-later +Group: Hardware/Mobile +URL: https://osmocom.org/projects/osmohnbgw +Source: %{name}-%{version}.tar.xz +BuildRequires: autoconf-archive +BuildRequires: automake >= 1.9 +BuildRequires: libtool >= 2 +BuildRequires: lksctp-tools-devel +BuildRequires: pkgconfig >= 0.20 +%if 0%{?suse_version} +BuildRequires: systemd-rpm-macros +%endif +BuildRequires: pkgconfig(libcrypto) >= 0.9.5 +BuildRequires: pkgconfig(libosmo-netif) >= 1.1.0 +BuildRequires: pkgconfig(libosmo-sigtran) >= 1.5.0 +BuildRequires: pkgconfig(libosmoabis) >= 1.2.0 +BuildRequires: pkgconfig(libosmotrau) >= 1.2.0 +BuildRequires: pkgconfig(libosmocore) >= 1.6.0 +BuildRequires: pkgconfig(libosmoctrl) >= 1.6.0 +BuildRequires: pkgconfig(libosmogb) >= 1.6.0 +BuildRequires: pkgconfig(libosmogsm) >= 1.6.0 +BuildRequires: pkgconfig(libosmovty) >= 1.6.0 +BuildRequires: pkgconfig(libosmo-hnbap) >= 1.1.0 +BuildRequires: pkgconfig(libosmo-ranap) >= 1.1.0 +BuildRequires: pkgconfig(libosmo-rua) >= 1.1.0 +BuildRequires: pkgconfig(talloc) +BuildRequires: pkgconfig(libasn1c) >= 0.9.30 +%{?systemd_requires} + +%description +OsmoHNBGW: Osmocom's Home NodeB for 3G mobile networks. + +%prep +%setup -q + +%build +echo "%{version}" >.tarball-version +autoreconf -fi +%configure \ + --docdir=%{_docdir}/%{name} \ + --with-systemdsystemunitdir=%{_unitdir} +make %{?_smp_mflags} + +%install +%make_install + +%if 0%{?suse_version} +%preun +%service_del_preun %{name}.service + +%postun +%service_del_postun %{name}.service + +%pre +%service_add_pre %{name}.service + +%post +%service_add_post %{name}.service +%endif + +%check +make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +) + +%files +%license COPYING +%doc AUTHORS README.md +%{_bindir}/osmo-hnbgw +%dir %{_docdir}/%{name}/examples +%dir %{_docdir}/%{name}/examples/osmo-hnbgw +%{_docdir}/%{name}/examples/osmo-hnbgw/osmo-hnbgw.cfg +%dir %{_sysconfdir}/osmocom +%config(noreplace) %{_sysconfdir}/osmocom/osmo-hnbgw.cfg +%{_unitdir}/%{name}.service + +%changelog diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am new file mode 100644 index 0000000..212601c --- /dev/null +++ b/contrib/systemd/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = osmo-hnbgw.service + +if HAVE_SYSTEMD +systemdsystemunit_DATA = \ + osmo-hnbgw.service +endif diff --git a/contrib/systemd/osmo-hnbgw.service b/contrib/systemd/osmo-hnbgw.service new file mode 100644 index 0000000..7aca29f --- /dev/null +++ b/contrib/systemd/osmo-hnbgw.service @@ -0,0 +1,11 @@ +[Unit] +Description=Osmocom Home Nodeb Gateway (OsmoHNBGW) + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-hnbgw -c /etc/osmocom/osmo-hnbgw.cfg +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/debian/changelog @@ -0,0 +1 @@ + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..1ea3ad2 --- /dev/null +++ b/debian/control @@ -0,0 +1,51 @@ +Source: osmo-hnbgw +Section: net +Priority: extra +Maintainer: Osmocom team +Build-Depends: debhelper (>=9), + dh-autoreconf, + autotools-dev, + autoconf, + autoconf-archive, + automake, + libtool, + pkg-config, + python3-minimal, + libtalloc-dev, + libasn1c-dev (>= 0.9.30), + libsctp-dev, + libosmocore-dev (>= 1.6.0), + libosmo-sigtran-dev (>= 1.5.0), + libosmo-abis-dev (>= 1.2.0), + libosmo-netif-dev (>= 1.1.0), + libosmo-hnbap-dev (>= 1.1.0), + libosmo-ranap-dev (>= 1.1.0), + libosmo-rua-dev (>= 1.1.0), + osmo-gsm-manuals-dev (>= 1.2.0) +Standards-Version: 3.9.8 +Vcs-Git: git://git.osmocom.org/osmo-hnbgw.git +Vcs-Browser: https://git.osmocom.org/osmo-hnbgw/ +Homepage: https://projects.osmocom.org/projects/osmo-hnbgw + +Package: osmo-hnbgw +Architecture: any +Multi-Arch: foreign +Depends: ${misc:Depends}, ${shlibs:Depends} +Recommends: osmo-mgw +Description: OsmoHNBGW: Osmocom Home Node B Gateway + +Package: osmo-hnbgw-dbg +Section: debug +Architecture: any +Multi-Arch: same +Depends: osmo-hnbgw (= ${binary:Version}), ${misc:Depends} +Description: OsmoHNBGW: Osmocom Home Node B Gateway + +Package: osmo-hnbgw-doc +Architecture: all +Section: doc +Priority: optional +Depends: ${misc:Depends} +Description: ${misc:Package} PDF documentation + Various manuals: user manual, VTY reference manual and/or + protocol/interface manuals. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..cfcb0de --- /dev/null +++ b/debian/copyright @@ -0,0 +1,19 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: osmo-hnbgw +Source: git://git.osmocom.org/osmo-hnbgw + +Files: * +Copyright: 2021 sysmocom - s.f.m.c. GmbH +License: AGPL-3.0+ + 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 . diff --git a/debian/osmo-hnbgw.install b/debian/osmo-hnbgw.install new file mode 100644 index 0000000..ac1f931 --- /dev/null +++ b/debian/osmo-hnbgw.install @@ -0,0 +1,4 @@ +etc/osmocom/osmo-hnbgw.cfg +lib/systemd/system/osmo-hnbgw.service +usr/bin/osmo-hnbgw +usr/share/doc/osmo-hnbgw/examples/osmo-hnbgw/osmo-hnbgw.cfg usr/share/doc/osmo-hnbgw/examples diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..bce8ed6 --- /dev/null +++ b/debian/rules @@ -0,0 +1,66 @@ +#!/usr/bin/make -f +# You must remove unused comment lines for the released package. +# See debhelper(7) (uncomment to enable) +# This is an autogenerated template for debian/rules. +# +# Output every command that modifies files on the build system. +#export DH_VERBOSE = 1 +# +# Copy some variable definitions from pkg-info.mk and vendor.mk +# under /usr/share/dpkg/ to here if they are useful. +# +# See FEATURE AREAS/ENVIRONMENT in dpkg-buildflags(1) +# Apply all hardening options +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all +# Package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# Package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed +# +# With debhelper version 9 or newer, the dh command exports +# all buildflags. So there is no need to include the +# /usr/share/dpkg/buildflags.mk file here if compat is 9 or newer. +# +# These are rarely used code. (START) +# +# The following include for *.mk magically sets miscellaneous +# variables while honoring existing values of pertinent +# environment variables: +# +# Architecture-related variables such as DEB_TARGET_MULTIARCH: +#include /usr/share/dpkg/architecture.mk +# Vendor-related variables such as DEB_VENDOR: +#include /usr/share/dpkg/vendor.mk +# Package-related variables such as DEB_DISTRIBUTION +#include /usr/share/dpkg/pkg-info.mk +# +# You may alternatively set them susing a simple script such as: +# DEB_VENDOR ?= $(shell dpkg-vendor --query Vendor) +# +# These are rarely used code. (END) +# + +# main packaging script based on dh7 syntax +%: + dh $@ --with autoreconf + +# debmake generated override targets +CONFIGURE_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals +override_dh_auto_configure: + dh_auto_configure -- $(CONFIGURE_FLAGS) +# +# Do not install libtool archive, python .pyc .pyo +#override_dh_install: +# dh_install --list-missing -X.la -X.pyc -X.pyo + +# See https://www.debian.org/doc/manuals/developers-reference/best-pkging-practices.html#bpp-dbg +override_dh_strip: + dh_strip -posmo-hnbgw --dbg-package=osmo-hnbgw-dbg + +# Print test results in case of a failure +override_dh_auto_test: + dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false) + +# Don't create .pdf.gz files (barely saves space and they can't be opened directly by most pdf readers) +override_dh_compress: + dh_compress -X.pdf diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..15f36b7 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = \ + examples \ + manuals \ + $(NULL) diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am new file mode 100644 index 0000000..d1c6d51 --- /dev/null +++ b/doc/examples/Makefile.am @@ -0,0 +1,30 @@ +OSMOCONF_FILES = \ + osmo-hnbgw/osmo-hnbgw.cfg + +osmoconfdir = $(sysconfdir)/osmocom +osmoconf_DATA = $(OSMOCONF_FILES) + +EXTRA_DIST = $(OSMOCONF_FILES) + +CFG_FILES = find $(srcdir) -name '*.cfg*' | sed -e 's,^$(srcdir),,' + +dist-hook: + for f in $$($(CFG_FILES)); do \ + j="$(distdir)/$$f" && \ + mkdir -p "$$(dirname $$j)" && \ + $(INSTALL_DATA) $(srcdir)/$$f $$j; \ + done + +install-data-hook: + for f in $$($(CFG_FILES)); do \ + j="$(DESTDIR)$(docdir)/examples/$$f" && \ + mkdir -p "$$(dirname $$j)" && \ + $(INSTALL_DATA) $(srcdir)/$$f $$j; \ + done + +uninstall-hook: + @$(PRE_UNINSTALL) + for f in $$($(CFG_FILES)); do \ + j="$(DESTDIR)$(docdir)/examples/$$f" && \ + $(RM) $$j; \ + done diff --git a/doc/examples/osmo-hnbgw/osmo-hnbgw.cfg b/doc/examples/osmo-hnbgw/osmo-hnbgw.cfg new file mode 100644 index 0000000..9286b2a --- /dev/null +++ b/doc/examples/osmo-hnbgw/osmo-hnbgw.cfg @@ -0,0 +1,25 @@ +! +! OsmoHNBGW (0) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print extended-timestamp 1 + logging level all debug + logging level lglobal notice + logging level llapd notice + logging level linp notice + 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 +hnbgw + iuh + local-ip 0.0.0.0 + hnbap-allow-tmsi 1 diff --git a/doc/manuals/Makefile.am b/doc/manuals/Makefile.am new file mode 100644 index 0000000..cab4f8c --- /dev/null +++ b/doc/manuals/Makefile.am @@ -0,0 +1,25 @@ +EXTRA_DIST = \ + osmohnbgw-usermanual.adoc \ + osmohnbgw-usermanual-docinfo.xml \ + chapters \ + osmohnbgw-vty-reference.xml \ + regen_doc.sh \ + vty + +if BUILD_MANUALS + ASCIIDOC = osmohnbgw-usermanual.adoc + ASCIIDOC_DEPS = $(srcdir)/chapters/*.adoc + include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.asciidoc.inc + + VTY_REFERENCE = osmohnbgw-vty-reference.xml + + BUILT_REFERENCE_XML = $(builddir)/vty/hnbgw_vty_reference.xml + $(builddir)/vty/hnbgw_vty_reference.xml: $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw + mkdir -p $(builddir)/vty + $(top_builddir)/src/osmo-hnbgw/osmo-hnbgw --vty-ref-xml > $@ + + include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.vty-reference.inc + + OSMO_REPOSITORY = osmo-hnbgw + include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.common.inc +endif diff --git a/doc/manuals/chapters/overview.adoc b/doc/manuals/chapters/overview.adoc new file mode 100644 index 0000000..2368b4f --- /dev/null +++ b/doc/manuals/chapters/overview.adoc @@ -0,0 +1,56 @@ +[[overview]] +== Overview + + +[[intro_overview]] +=== About OsmoHNBGW + +OsmoHNBGW implements the Home NodeB Gateway function in the 3G network architecture. It serves +as a gateway between the classic 3G core network (CN) domain with its IuCS and IuPS interface +and the femtocell based RAN. + +A typical 3G network consisting of Osmocom components will look as illustrated in the following +diagram: + +[[fig-3g]] +.Typical 3G network architecture used with OsmoHNBGW +---- + +------------+ +--------+ +----------+ +---------+ + UE <-->| hNodeB |<--Iuh---->| HNB-GW |<--IuCS-->| OsmoMSC |<--GSUP-->| OsmoHLR | + UE <-->| femto cell | ...-->| | ...-->| | | | + | | | | +----------+ +---------| + +------------+<--GTP-U | | + \ | | +------+ +------+ + | | |<--IuPS-->| SGSN |<--GTP-C-->| GGSN | + | +--------+ ...-->| | GTP-U-->| | + | +------+ / +------+ + \_______________________________/ +---- + +The HNB-GW performs a translation interface between the IuCS/IuPS interfaces on the one hand +side, and the Iuh interface on the or ther hand: + +---- + Iuh IuCS/IuPS + +NAS +----+----+ +----+----+ +Non-Access Stratum | CC | MM | | CC | MM | +- - - - - - - - - - - +----+----+-------+ +----+----+ + | RANAP | | H | RANAP | +Access Stratum +---------+ HNBAP | N +---------+ - - SCCP USER SAP + | RUA | | B | SUA | \ + +---------+-------+ - +---------+ | + | SCTP | G | SCTP | } SIGTRAN + +-----------------+ W +---------+ | + | IP | | IP | / + +-----------------+ +---------+ +---- + +On the femtocell (Home NodeB) side, OsmoHNBGW implements the Iuh interface as specified by 3GPP. + +=== The Iuh interface + +Iuh consists of the following sub-layers: + +- HNBAP (Home NodeB Application Part) +- RUA (RANAP User Adaptation, between RANAP and SCTP diff --git a/doc/manuals/chapters/running.adoc b/doc/manuals/chapters/running.adoc new file mode 100644 index 0000000..f40027a --- /dev/null +++ b/doc/manuals/chapters/running.adoc @@ -0,0 +1,119 @@ +== Running OsmoHNBGW + +The OsmoHNBGW executable (`osmo-hnbgw`) offers the following command-line +arguments: + +=== SYNOPSIS + +*osmo-hnbgw* [-h|-V] [-d 'DBGMASK'] [-D] [-c 'CONFIGFILE'] [-s] [-T] [-e 'LOGLEVEL'] + +=== OPTIONS + +*-h, --help*:: + Print a short help message about the supported options +*-V, --version*:: + Print the compile-time version number of the OsmoHNBGW program +*-d, --debug 'DBGMASK','DBGLEVELS'*:: + Set the log subsystems and levels for logging to stderr. This + has mostly been superseded by VTY-based logging configuration, + see <> for further information. +*-D, --daemonize*:: + Fork the process as a daemon into background. +*-c, --config-file 'CONFIGFILE'*:: + Specify the file and path name of the configuration file to be + used. If none is specified, use `osmo-msc.cfg` in the current + working directory. +*-s, --disable-color*:: + Disable colors for logging to stderr. This has mostly been + deprecated by VTY based logging configuration, see <> + for more information. +*-T, --timestamp*:: + Enable time-stamping of log messages to stderr. This has mostly + been deprecated by VTY based logging configuration, see + <> for more information. +*-e, --log-level 'LOGLEVEL'*:: + Set the global log level for logging to stderr. This has mostly + been deprecated by VTY based logging configuration, see + <> for more information. + + +=== Multiple instances + +Running multiple instances of `osmo-hnbgw` on the same computer is possible if +all interfaces (VTY, CTRL, Iuh) are separated using the appropriate +configuration options. The IP based interfaces are binding to local host by +default. In order to separate the processes, the user has to bind those +services to specific but different IP addresses and/or ports. + +The VTY and the Control interface can be bound to IP addresses from the loopback +address range, for example: + +---- +line vty + bind 127.0.0.2 +ctrl + bind 127.0.0.2 +---- + +The Iuh interface can be bound to an individual port: + +---- +hnbgw + iuh + local-ip 0.0.0.0 + local-port 29169 +---- + +For the following links, OsmoHNBGW acts as a client and does not listen/bind to a +specific interface, and will hence not encounter conflicts for multiple instances +running on the same interface: + +- The SCCP/M3UA links are established by OsmoHNBGW contacting an STP. + +To run multiple OsmoHNBGW instances on the same SCCP routing, each HNBGW has to +configure a distinct point-code, see <>. + + +=== Configuring Primary Links + +[[configure_iucs_iups]] +==== Configure SCCP/M3UA to connect to an MSC's _IuCS_ and an SGSN's _IuPS_ interface + +OsmoHNBGW acts as client to contact an STP instance and establish an SCCP/M3UA +link. + +An example configuration of OsmoHNBGW's SCCP link: + +---- +cs7 instance 0 + point-code 0.23.5 + asp asp-clnt-OsmoHNBGW 2905 0 m3ua + remote-ip 127.0.0.1 + sctp-role client + sccp-address msc + routing-indicator PC + point-code 0.23.1 + sccp-address sgsn + routing-indicator PC + point-code 0.23.2 +hnbgw + iucs + remote-addr msc + iups + remote-addr sgsn +---- + +This configuration is explained in detail in <>. + +==== Configure RUA to accept Iuh connections from hNodeB + +OsmoHNBGW acts as server to accept Iuh connections from hNodeB devices. + +An example configuration for OsmoHNBGW's RUA server: + +---- +hnbgw + iuh + local-ip 10.9.8.7 + local-port 29169 +---- diff --git a/doc/manuals/osmohnbgw-usermanual-docinfo.xml b/doc/manuals/osmohnbgw-usermanual-docinfo.xml new file mode 100644 index 0000000..404d2a1 --- /dev/null +++ b/doc/manuals/osmohnbgw-usermanual-docinfo.xml @@ -0,0 +1,51 @@ + + + 1 + November 30th, 2019 + HW + + Initial version + + + + + + + Harald + Welte + hwelte@sysmocom.de + HW + + sysmocom + sysmocom - s.f.m.c. GmbH + Managing Director + + + + + + 2019 + sysmocom - s.f.m.c. GmbH + + + + + Permission is granted to copy, distribute and/or modify this + document under the terms of the GNU Free Documentation License, + Version 1.3 or any later version published by the Free Software + Foundation; with the Invariant Sections being just 'Foreword', + 'Acknowledgements' and 'Preface', with no Front-Cover Texts, + and no Back-Cover Texts. A copy of the license is included in + the section entitled "GNU Free Documentation License". + + + The Asciidoc source code of this manual can be found at + + http://git.osmocom.org/osmo-hnbgw/ + + and of the common chapters at + + http://git.osmocom.org/osmo-gsm-manuals/ + + + diff --git a/doc/manuals/osmohnbgw-usermanual.adoc b/doc/manuals/osmohnbgw-usermanual.adoc new file mode 100644 index 0000000..21faed7 --- /dev/null +++ b/doc/manuals/osmohnbgw-usermanual.adoc @@ -0,0 +1,37 @@ +:gfdl-enabled: +:program-name: OsmoHNBGW + +OsmoHNBGW User Manual +===================== +Harald Welte + + +include::./common/chapters/preface.adoc[] + +include::{srcdir}/chapters/overview.adoc[] + +include::{srcdir}/chapters/running.adoc[] + +// include::{srcdir}/chapters/control.adoc[] + +// include::./common/chapters/counters-overview.adoc[] + +// include::{srcdir}/chapters/counters.adoc[] + +include::./common/chapters/vty.adoc[] + +include::./common/chapters/logging.adoc[] + +include::./common/chapters/cs7-config.adoc[] + +// include::{srcdir}/chapters/net.adoc[] + +// include::./common/chapters/control_if.adoc[] + +include::./common/chapters/port_numbers.adoc[] + +include::./common/chapters/bibliography.adoc[] + +include::./common/chapters/glossary.adoc[] + +include::./common/chapters/gfdl.adoc[] diff --git a/doc/manuals/osmohnbgw-vty-reference.xml b/doc/manuals/osmohnbgw-vty-reference.xml new file mode 100644 index 0000000..cd69333 --- /dev/null +++ b/doc/manuals/osmohnbgw-vty-reference.xml @@ -0,0 +1,38 @@ + + + + +]> + + + + + + v1 + 29th July 2019 + dw + Initial + + + + OsmoHNBGW VTY Reference + + + 2019 + + + + This work is copyright by sysmocom - s.f.m.c. GmbH. All rights reserved. + + + + + + &chapter-vty; + + diff --git a/doc/manuals/regen_doc.sh b/doc/manuals/regen_doc.sh new file mode 100755 index 0000000..39dd9ee --- /dev/null +++ b/doc/manuals/regen_doc.sh @@ -0,0 +1,17 @@ +#!/bin/sh -x + +if [ -z "$DOCKER_PLAYGROUND" ]; then + echo "You need to set DOCKER_PLAYGROUND" + exit 1 +fi + +SCRIPT=$(realpath "$0") +MANUAL_DIR=$(dirname "$SCRIPT") + +COMMIT=${COMMIT:-$(git log -1 --format=format:%H)} + +cd "$DOCKER_PLAYGROUND/scripts" || exit 1 + +OSMO_SGSN_BRANCH=$COMMIT ./regen_doc.sh osmo-hnbgw 4261 \ + "$MANUAL_DIR/chapters/counters_generated.adoc" \ + "$MANUAL_DIR/vty/hnbgw_vty_reference.xml" diff --git a/doc/manuals/vty/hnbgw_vty_additions.xml b/doc/manuals/vty/hnbgw_vty_additions.xml new file mode 100644 index 0000000..a4c675e --- /dev/null +++ b/doc/manuals/vty/hnbgw_vty_additions.xml @@ -0,0 +1,2 @@ + + diff --git a/doc/protocols_around_hnbgw.txt b/doc/protocols_around_hnbgw.txt new file mode 100644 index 0000000..3eef155 --- /dev/null +++ b/doc/protocols_around_hnbgw.txt @@ -0,0 +1,60 @@ +Protocols Around the Home Node B Gateway +======================================== + + +--------+ + ,-->| Osmo | + / | MGCPGW | + | | |<--MGCP + | +--------+ \ + / | + +------------+<--RTP +--------+ `->+----------+ + UE <-->| hNodeB | | Osmo | | OsmoMSC | +------+ + UE <-->| femto cell |<--Iuh---->| HNB-GW |<--IuCS-->| | | Osmo | + | | | | | (VLR)|<-GSUP->| HLR | + | | | | +----------+ GSUP->+------+ + +------------+<--GTP-U | | / + \ | | +------+<---' +------+ + | | |<--IuPS-->| Osmo |<--GTP-C--->| Open | + | +--------+ | SGSN | GTP-U--->| GGSN | + | +------+ / +------+ + \_______________________________/ + + + + Iuh IuCS/IuPS + +NAS +----+----+ +----+----+ +Non-Access Stratum | CC | MM | | CC | MM | +- - - - - - - - - - - +----+----+-------+ +----+----+ + | RANAP | | H | RANAP | +Access Stratum +---------+ HNBAP | N +---------+ - - SCCP USER SAP + | RUA | | B | SUA | \ + +---------+-------+ - +---------+ | + | SCTP | G | SCTP | } SIGTRAN + +-----------------+ W +---------+ | + | IP | | IP | / + +-----------------+ +---------+ + + +Various SIGTRAN implementations: + + IuCS/IuPS + usual + | simplest + | | + v v + +------+------+------+-----+ + | SCCP | SCCP | | | + +------+------+ SCCP | | + | MTP3 | MTP3 | | | + +------+------+------+ SUA | + | MTP2 | | | | + +------+ M2UA | M3UA | | + | M2PA | | | | + +------+------+------+-----+ + | SCTP | + +--------------------------+ + | IP | + +--------------------------+ + +UE (User Endpoint) == MS (Mobile Subscriber) == mobile device diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..9d963a0 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + osmocom \ + $(NULL) diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am new file mode 100644 index 0000000..50c69be --- /dev/null +++ b/include/osmocom/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + hnbgw \ + $(NULL) diff --git a/include/osmocom/hnbgw/Makefile.am b/include/osmocom/hnbgw/Makefile.am new file mode 100644 index 0000000..b2a667d --- /dev/null +++ b/include/osmocom/hnbgw/Makefile.am @@ -0,0 +1,4 @@ +noinst_HEADERS = \ + vty.h \ + context_map.h hnbgw.h hnbgw_cn.h \ + hnbgw_hnbap.h hnbgw_rua.h hnbgw_ranap.h diff --git a/include/osmocom/hnbgw/context_map.h b/include/osmocom/hnbgw/context_map.h new file mode 100644 index 0000000..6279b91 --- /dev/null +++ b/include/osmocom/hnbgw/context_map.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +enum hnbgw_context_map_state { + MAP_S_NULL, + MAP_S_ACTIVE, /* currently active map */ + MAP_S_RESERVED1, /* just disconnected, still resrved */ + MAP_S_RESERVED2, /* still reserved */ + MAP_S_NUM_STATES /* Number of states, keep this at the end */ +}; + +extern const struct value_string hnbgw_context_map_state_names[]; +static inline const char *hnbgw_context_map_state_name(enum hnbgw_context_map_state val) +{ return get_value_string(hnbgw_context_map_state_names, val); } + +struct hnb_context; +struct hnbgw_cnlink; + +struct hnbgw_context_map { + /* entry in the per-CN list of mappings */ + struct llist_head cn_list; + /* entry in the per-HNB list of mappings */ + struct llist_head hnb_list; + /* pointer to HNB */ + struct hnb_context *hnb_ctx; + /* pointer to CN */ + struct hnbgw_cnlink *cn_link; + /* RUA contxt ID */ + uint32_t rua_ctx_id; + /* False for CS, true for PS */ + bool is_ps; + /* SCCP User SAP connection ID */ + uint32_t scu_conn_id; + + enum hnbgw_context_map_state state; +}; + + +struct hnbgw_context_map * +context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id, + bool is_ps, + struct hnbgw_cnlink *cn_if_new); + +struct hnbgw_context_map * +context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id); + +void context_map_deactivate(struct hnbgw_context_map *map); + +int context_map_init(struct hnb_gw *gw); diff --git a/include/osmocom/hnbgw/hnbgw.h b/include/osmocom/hnbgw/hnbgw.h new file mode 100644 index 0000000..fc8298d --- /dev/null +++ b/include/osmocom/hnbgw/hnbgw.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#define DEBUG +#include + + +enum { + DMAIN, + DHNBAP, + DRUA, + DRANAP, +}; + +#define LOGHNB(x, ss, lvl, fmt, args ...) \ + LOGP(ss, lvl, "%s " fmt, hnb_context_name(x), ## args) + +enum hnb_ctrl_node { + CTRL_NODE_HNB = _LAST_CTRL_NODE, + _LAST_CTRL_NODE_HNB +}; + +#define HNBGW_LOCAL_IP_DEFAULT "0.0.0.0" +/* TODO: CS and PS now both connect to OsmoSTP, i.e. that's always going to be the same address. Drop the + * duplicity. */ +#define HNBGW_IUCS_REMOTE_IP_DEFAULT "127.0.0.1" +#define HNBGW_IUPS_REMOTE_IP_DEFAULT "127.0.0.1" + +/* 25.467 Section 7.1 */ +#define IUH_DEFAULT_SCTP_PORT 29169 +#define RNA_DEFAULT_SCTP_PORT 25471 + +#define IUH_PPI_RUA 19 +#define IUH_PPI_HNBAP 20 +#define IUH_PPI_SABP 31 +#define IUH_PPI_RNA 42 +#define IUH_PPI_PUA 55 + +#define IUH_MSGB_SIZE 2048 + +struct umts_cell_id { + uint16_t mcc; /*!< Mobile Country Code */ + uint16_t mnc; /*!< Mobile Network Code */ + uint16_t lac; /*!< Locaton Area Code */ + uint16_t rac; /*!< Routing Area Code */ + uint16_t sac; /*!< Service Area Code */ + uint32_t cid; /*!< Cell ID */ +}; + +struct hnb_gw; + +enum hnbgw_cnlink_state { + /* we have just been initialized or were disconnected */ + CNLINK_S_NULL, + /* establishment of the SUA/SCCP link is pending */ + CNLINK_S_EST_PEND, + /* establishment of the SUA/SCCP link was confirmed */ + CNLINK_S_EST_CONF, + /* we have esnt the RANAP RESET and wait for the ACK */ + CNLINK_S_EST_RST_TX_WAIT_ACK, + /* we have received the RANAP RESET ACK and are active */ + CNLINK_S_EST_ACTIVE, +}; + +struct hnbgw_cnlink { + struct llist_head list; + enum hnbgw_cnlink_state state; + struct hnb_gw *gw; + /* timer for re-transmitting the RANAP Reset */ + struct osmo_timer_list T_RafC; + /* reference to the SCCP User SAP by which we communicate */ + struct osmo_sccp_instance *sccp; + struct osmo_sccp_user *sccp_user; + uint32_t next_conn_id; + + /* linked list of hnbgw_context_map */ + struct llist_head map_list; +}; + +struct hnb_context { + /*! Entry in HNB-global list of HNB */ + struct llist_head list; + /*! HNB-GW we are part of */ + struct hnb_gw *gw; + /*! SCTP socket + write queue for Iuh to this specific HNB */ + struct osmo_stream_srv *conn; + /*! copied from HNB-Identity-Info IE */ + char identity_info[256]; + /*! copied from Cell Identity IE */ + struct umts_cell_id id; + + /*! SCTP stream ID for HNBAP */ + uint16_t hnbap_stream; + /*! SCTP stream ID for RUA */ + uint16_t rua_stream; + + /*! True if a HNB-REGISTER-REQ from this HNB has been accepted. Note that + * this entire data structure is freed if the HNB sends HNB-DE-REGISTER-REQ. */ + bool hnb_registered; + + /* linked list of hnbgw_context_map */ + struct llist_head map_list; +}; + +struct ue_context { + /*! Entry in the HNB-global list of UE */ + struct llist_head list; + /*! Unique Context ID for this UE */ + uint32_t context_id; + char imsi[16+1]; + uint32_t tmsi; + /*! UE is serviced via this HNB */ + struct hnb_context *hnb; +}; + +struct hnb_gw { + struct { + const char *iuh_local_ip; + /*! SCTP port for Iuh listening */ + uint16_t iuh_local_port; + /*! The UDP port where we receive multiplexed CS user + * plane traffic from HNBs */ + uint16_t iuh_cs_mux_port; + const char *iucs_remote_addr_name; + const char *iups_remote_addr_name; + uint16_t rnc_id; + bool hnbap_allow_tmsi; + /*! print hnb-id (true) or MCC-MNC-LAC-RAC-SAC (false) in logs */ + bool log_prefix_hnb_id; + } config; + /*! SCTP listen socket for incoming connections */ + struct osmo_stream_srv_link *iuh; + /* list of struct hnb_context */ + struct llist_head hnb_list; + /* list of struct ue_context */ + struct llist_head ue_list; + /* next availble UE Context ID */ + uint32_t next_ue_ctx_id; + struct ctrl_handle *ctrl; + /* currently active CN links for CS and PS */ + struct { + struct osmo_sccp_instance *client; + struct hnbgw_cnlink *cnlink; + struct osmo_sccp_addr local_addr; + struct osmo_sccp_addr iucs_remote_addr; + struct osmo_sccp_addr iups_remote_addr; + } sccp; +}; + +extern void *talloc_asn1_ctx; + +struct hnb_context *hnb_context_by_id(struct hnb_gw *gw, uint32_t cid); +struct hnb_context *hnb_context_by_identity_info(struct hnb_gw *gw, const char *identity_info); +const char *hnb_context_name(struct hnb_context *ctx); +unsigned hnb_contexts(const struct hnb_gw *gw); + +struct ue_context *ue_context_by_id(struct hnb_gw *gw, uint32_t id); +struct ue_context *ue_context_by_imsi(struct hnb_gw *gw, const char *imsi); +struct ue_context *ue_context_by_tmsi(struct hnb_gw *gw, uint32_t tmsi); +struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi, + uint32_t tmsi); +void ue_context_free(struct ue_context *ue); + +struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_link *link, int new_fd); +void hnb_context_release(struct hnb_context *ctx); + +void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx); +int hnbgw_vty_go_parent(struct vty *vty); diff --git a/include/osmocom/hnbgw/hnbgw_cn.h b/include/osmocom/hnbgw/hnbgw_cn.h new file mode 100644 index 0000000..b481a69 --- /dev/null +++ b/include/osmocom/hnbgw/hnbgw_cn.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip); diff --git a/include/osmocom/hnbgw/hnbgw_hnbap.h b/include/osmocom/hnbgw/hnbgw_hnbap.h new file mode 100644 index 0000000..d0af955 --- /dev/null +++ b/include/osmocom/hnbgw/hnbgw_hnbap.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +int hnbgw_hnbap_rx(struct hnb_context *hnb, struct msgb *msg); +int hnbgw_hnbap_init(void); diff --git a/include/osmocom/hnbgw/hnbgw_ranap.h b/include/osmocom/hnbgw/hnbgw_ranap.h new file mode 100644 index 0000000..a5d7380 --- /dev/null +++ b/include/osmocom/hnbgw/hnbgw_ranap.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len); +int hnbgw_ranap_init(void); diff --git a/include/osmocom/hnbgw/hnbgw_rua.h b/include/osmocom/hnbgw/hnbgw_rua.h new file mode 100644 index 0000000..4b65115 --- /dev/null +++ b/include/osmocom/hnbgw/hnbgw_rua.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg); +int hnbgw_rua_init(void); + +int rua_tx_udt(struct hnb_context *hnb, const uint8_t *data, unsigned int len); +int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id, + const uint8_t *data, unsigned int len); +int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id, + const RUA_Cause_t *cause, const uint8_t *data, unsigned int len); diff --git a/include/osmocom/hnbgw/vty.h b/include/osmocom/hnbgw/vty.h new file mode 100644 index 0000000..3d05da5 --- /dev/null +++ b/include/osmocom/hnbgw/vty.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +enum osmo_iuh_vty_node { + HNBGW_NODE = _LAST_OSMOVTY_NODE + 1, + IUH_NODE, + IUCS_NODE, + IUPS_NODE, +}; + diff --git a/osmoappdesc.py b/osmoappdesc.py new file mode 100644 index 0000000..8615b6e --- /dev/null +++ b/osmoappdesc.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +# (C) 2021 by sysmocom - s.m.f.c. GmbH +# 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 + +app_configs = { + "osmo-hnbgw": ["doc/examples/osmo-hnbgw/osmo-hnbgw.cfg"] +} + +apps = [(4261, "src/osmo-hnbgw/osmo-hnbgw", "OsmoHNBGW", "osmo-hnbgw") + ] + +vty_command = ["./src/osmo-hnbgw/osmo-hnbgw", "-c", + "doc/examples/osmo-hnbgw/osmo-hnbgw.cfg"] + +vty_app = apps[0] diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..d83bbf0 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + osmo-hnbgw \ + $(NULL) diff --git a/src/osmo-hnbgw/Makefile.am b/src/osmo-hnbgw/Makefile.am new file mode 100644 index 0000000..bda8633 --- /dev/null +++ b/src/osmo-hnbgw/Makefile.am @@ -0,0 +1,55 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBASN1C_CFLAGS) \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMOSIGTRAN_CFLAGS) \ + $(LIBOSMORUA_CFLAGS) \ + $(LIBOSMORANAP_CFLAGS) \ + $(LIBOSMOHNBAP_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-hnbgw \ + $(NULL) + +osmo_hnbgw_SOURCES = \ + hnbgw.c \ + hnbgw_hnbap.c \ + hnbgw_rua.c \ + hnbgw_ranap.c \ + hnbgw_vty.c \ + context_map.c \ + hnbgw_cn.c \ + $(NULL) + +osmo_hnbgw_LDADD = \ + $(LIBASN1C_LIBS) \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(LIBOSMOSIGTRAN_LIBS) \ + $(LIBOSMORUA_LIBS) \ + $(LIBOSMORANAP_LIBS) \ + $(LIBOSMOHNBAP_LIBS) \ + $(LIBSCTP_LIBS) \ + $(NULL) diff --git a/src/osmo-hnbgw/context_map.c b/src/osmo-hnbgw/context_map.c new file mode 100644 index 0000000..09aa965 --- /dev/null +++ b/src/osmo-hnbgw/context_map.c @@ -0,0 +1,181 @@ +/* Mapper between RUA ContextID (24 bit, per HNB) and the SUA/SCCP + * Connection ID (32bit, per signalling link) */ + +/* (C) 2015 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 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 . + * + */ + +/* an expired mapping is destroyed after 1..2 * EXPIRY_TIMER_SECS */ +#define EXPIRY_TIMER_SECS 23 + +#include + +#include +#include + +const struct value_string hnbgw_context_map_state_names[] = { + {MAP_S_NULL , "not-initialized"}, + {MAP_S_ACTIVE , "active"}, + {MAP_S_RESERVED1, "inactive-reserved"}, + {MAP_S_RESERVED2, "inactive-discard"}, + {0, NULL} +}; + +/* is a given SCCP USER SAP Connection ID in use for a given CN link? */ +static int cn_id_in_use(struct hnbgw_cnlink *cn, uint32_t id) +{ + struct hnbgw_context_map *map; + + llist_for_each_entry(map, &cn->map_list, cn_list) { + if (map->scu_conn_id == id) + return 1; + } + return 0; +} + +/* try to allocate a new SCCP User SAP Connection ID */ +static int alloc_cn_conn_id(struct hnbgw_cnlink *cn, uint32_t *id_out) +{ + uint32_t i; + uint32_t id; + + for (i = 0; i < 0xffffffff; i++) { + id = cn->next_conn_id++; + if (!cn_id_in_use(cn, id)) { + *id_out = id; + return 1; + } + } + return -1; +} + +/* Map from a HNB + ContextID to the SCCP-side Connection ID */ +struct hnbgw_context_map * +context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id, + bool is_ps, + struct hnbgw_cnlink *cn_if_new) +{ + struct hnbgw_context_map *map; + uint32_t new_scu_conn_id; + + llist_for_each_entry(map, &hnb->map_list, hnb_list) { + if (map->state != MAP_S_ACTIVE) + continue; + if (map->cn_link != cn_if_new) { + continue; + } + if (map->rua_ctx_id == rua_ctx_id + && map->is_ps == is_ps) { + return map; + } + } + + if (alloc_cn_conn_id(cn_if_new, &new_scu_conn_id) < 0) { + LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unable to allocate CN connection ID\n"); + return NULL; + } + + LOGHNB(hnb, DMAIN, LOGL_INFO, "Creating new Mapping RUA CTX %p/%u <-> SCU Conn ID %p/%u\n", + hnb, rua_ctx_id, cn_if_new, new_scu_conn_id); + + /* alloate a new map entry */ + map = talloc_zero(hnb, struct hnbgw_context_map); + map->state = MAP_S_NULL; + map->cn_link = cn_if_new; + map->hnb_ctx = hnb; + map->rua_ctx_id = rua_ctx_id; + map->is_ps = is_ps; + map->scu_conn_id = new_scu_conn_id; + + /* put it into both lists */ + llist_add_tail(&map->hnb_list, &hnb->map_list); + llist_add_tail(&map->cn_list, &cn_if_new->map_list); + map->state = MAP_S_ACTIVE; + + return map; +} + +/* Map from a CN + Connection ID to HNB + Context ID */ +struct hnbgw_context_map * +context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id) +{ + struct hnbgw_context_map *map; + + llist_for_each_entry(map, &cn->map_list, cn_list) { + if (map->state != MAP_S_ACTIVE) + continue; + if (map->scu_conn_id == scu_conn_id) { + return map; + } + } + /* we don't allocate new mappings in the CN->HNB + * direction, as the RUA=SCCP=SUA connections are always + * established from HNB towards CN. */ + LOGP(DMAIN, LOGL_NOTICE, "Unable to resolve map for CN " "connection ID %p/%u\n", cn, scu_conn_id); + return NULL; +} + +void context_map_deactivate(struct hnbgw_context_map *map) +{ + /* set the state to reserved. We still show up in the list and + * avoid re-allocation of the context-id until we are cleaned up + * by the context_map garbage collector timer */ + + if (map->state != MAP_S_RESERVED2) + map->state = MAP_S_RESERVED1; +} + +static struct osmo_timer_list context_map_tmr; + +static void context_map_tmr_cb(void *data) +{ + struct hnb_gw *gw = data; + struct hnbgw_cnlink *cn = gw->sccp.cnlink; + struct hnbgw_context_map *map, *next_map; + + DEBUGP(DMAIN, "Running context mapper garbage collection\n"); + llist_for_each_entry_safe(map, next_map, &cn->map_list, cn_list) { + switch (map->state) { + case MAP_S_RESERVED1: + /* first time we see this reserved + * entry: mark it for stage 2 */ + map->state = MAP_S_RESERVED2; + break; + case MAP_S_RESERVED2: + /* second time we see this reserved + * entry: remove it */ + map->state = MAP_S_NULL; + llist_del(&map->cn_list); + llist_del(&map->hnb_list); + talloc_free(map); + break; + default: + break; + } + } + /* re-schedule this timer */ + osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0); +} + +int context_map_init(struct hnb_gw *gw) +{ + context_map_tmr.cb = context_map_tmr_cb; + context_map_tmr.data = gw; + osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0); + + return 0; +} diff --git a/src/osmo-hnbgw/hnbgw.c b/src/osmo-hnbgw/hnbgw.c new file mode 100644 index 0000000..da15bfc --- /dev/null +++ b/src/osmo-hnbgw/hnbgw.c @@ -0,0 +1,700 @@ +/* main application for hnb-gw part of osmo-iuh */ + +/* (C) 2015 by Harald Welte + * (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 const char * const osmo_hnbgw_copyright = + "OsmoHNBGW - Osmocom Home Node B Gateway implementation\r\n" + "Copyright (C) 2016 by sysmocom s.f.m.c. GmbH \r\n" + "Contributions by Daniel Willmann, Harald Welte, Neels Hofmeyr\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static void *tall_hnb_ctx; + +static struct hnb_gw *g_hnb_gw; + +static struct hnb_gw *hnb_gw_create(void *ctx) +{ + struct hnb_gw *gw = talloc_zero(ctx, struct hnb_gw); + + /* strdup so we can easily talloc_free in the VTY code */ + gw->config.iuh_local_ip = talloc_strdup(gw, HNBGW_LOCAL_IP_DEFAULT); + gw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT; + gw->config.log_prefix_hnb_id = true; + + gw->next_ue_ctx_id = 23; + INIT_LLIST_HEAD(&gw->hnb_list); + INIT_LLIST_HEAD(&gw->ue_list); + + context_map_init(gw); + + return gw; +} + +struct hnb_context *hnb_context_by_id(struct hnb_gw *gw, uint32_t cid) +{ + struct hnb_context *hnb; + + llist_for_each_entry(hnb, &gw->hnb_list, list) { + if (hnb->id.cid == cid) + return hnb; + } + + return NULL; +} + +struct hnb_context *hnb_context_by_identity_info(struct hnb_gw *gw, const char *identity_info) +{ + struct hnb_context *hnb; + + llist_for_each_entry(hnb, &gw->hnb_list, list) { + if (strcmp(identity_info, hnb->identity_info) == 0) + return hnb; + } + + return NULL; +} + + +unsigned hnb_contexts(const struct hnb_gw *gw) +{ + unsigned num_ctx = 0; + struct hnb_context *hnb; + + llist_for_each_entry(hnb, &gw->hnb_list, list) { + num_ctx++; + } + + return num_ctx; +} + +struct ue_context *ue_context_by_id(struct hnb_gw *gw, uint32_t id) +{ + struct ue_context *ue; + + llist_for_each_entry(ue, &gw->ue_list, list) { + if (ue->context_id == id) + return ue; + } + return NULL; + +} + +struct ue_context *ue_context_by_imsi(struct hnb_gw *gw, const char *imsi) +{ + struct ue_context *ue; + + llist_for_each_entry(ue, &gw->ue_list, list) { + if (!strcmp(ue->imsi, imsi)) + return ue; + } + return NULL; +} + +struct ue_context *ue_context_by_tmsi(struct hnb_gw *gw, uint32_t tmsi) +{ + struct ue_context *ue; + + llist_for_each_entry(ue, &gw->ue_list, list) { + if (ue->tmsi == tmsi) + return ue; + } + return NULL; +} + +void ue_context_free_by_hnb(struct hnb_gw *gw, const struct hnb_context *hnb) +{ + struct ue_context *ue, *tmp; + + llist_for_each_entry_safe(ue, tmp, &gw->ue_list, list) { + if (ue->hnb == hnb) + ue_context_free(ue); + } +} + +static uint32_t get_next_ue_ctx_id(struct hnb_gw *gw) +{ + uint32_t id; + + do { + id = gw->next_ue_ctx_id++; + } while (ue_context_by_id(gw, id)); + + return id; +} + +struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi, + uint32_t tmsi) +{ + struct ue_context *ue; + + ue = talloc_zero(tall_hnb_ctx, struct ue_context); + if (!ue) + return NULL; + + ue->hnb = hnb; + if (imsi) + OSMO_STRLCPY_ARRAY(ue->imsi, imsi); + else + ue->imsi[0] = '\0'; + ue->tmsi = tmsi; + ue->context_id = get_next_ue_ctx_id(hnb->gw); + llist_add_tail(&ue->list, &hnb->gw->ue_list); + + LOGP(DHNBAP, LOGL_INFO, "created UE context: id 0x%x, imsi %s, tmsi 0x%x\n", + ue->context_id, imsi? imsi : "-", tmsi); + + return ue; +} + +void ue_context_free(struct ue_context *ue) +{ + llist_del(&ue->list); + talloc_free(ue); +} + +static int hnb_read_cb(struct osmo_stream_srv *conn) +{ + struct hnb_context *hnb = osmo_stream_srv_get_data(conn); + struct msgb *msg = msgb_alloc(IUH_MSGB_SIZE, "Iuh rx"); + int rc; + + if (!msg) + return -ENOMEM; + + /* we store a reference to the HomeNodeB in the msg->dest for the + * benefit of varoius downstream processing functions */ + msg->dst = hnb; + + rc = osmo_stream_srv_recv(conn, msg); + if (rc == -EAGAIN) { + /* Notification received */ + msgb_free(msg); + return 0; + } else if (rc < 0) { + LOGHNB(hnb, DMAIN, LOGL_ERROR, "Error during sctp_recvmsg()\n"); + /* FIXME: clean up after disappeared HNB */ + hnb_context_release(hnb); + goto out; + } else if (rc == 0) { + hnb_context_release(hnb); + rc = -1; + + goto out; + } else { + msgb_put(msg, rc); + } + + switch (msgb_sctp_ppid(msg)) { + case IUH_PPI_HNBAP: + hnb->hnbap_stream = msgb_sctp_stream(msg); + rc = hnbgw_hnbap_rx(hnb, msg); + break; + case IUH_PPI_RUA: + hnb->rua_stream = msgb_sctp_stream(msg); + rc = hnbgw_rua_rx(hnb, msg); + break; + case IUH_PPI_SABP: + case IUH_PPI_RNA: + case IUH_PPI_PUA: + LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unimplemented SCTP PPID=%lu received\n", msgb_sctp_ppid(msg)); + rc = 0; + break; + default: + LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unknown SCTP PPID=%lu received\n", msgb_sctp_ppid(msg)); + rc = 0; + break; + } + +out: + msgb_free(msg); + return rc; +} + +struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, struct osmo_stream_srv_link *link, int new_fd) +{ + struct hnb_context *ctx; + + ctx = talloc_zero(tall_hnb_ctx, struct hnb_context); + if (!ctx) + return NULL; + INIT_LLIST_HEAD(&ctx->map_list); + + ctx->gw = gw; + ctx->conn = osmo_stream_srv_create(tall_hnb_ctx, link, new_fd, hnb_read_cb, NULL, ctx); + if (!ctx->conn) { + LOGP(DMAIN, LOGL_INFO, "error while creating connection\n"); + talloc_free(ctx); + return NULL; + } + + llist_add_tail(&ctx->list, &gw->hnb_list); + return ctx; +} + +static const char *umts_cell_id_name(const struct umts_cell_id *ucid) +{ + static __thread char buf[40]; + + snprintf(buf, sizeof(buf), "%u-%u-L%u-R%u-S%u", ucid->mcc, ucid->mnc, ucid->lac, ucid->rac, ucid->sac); + return buf; +} + +const char *hnb_context_name(struct hnb_context *ctx) +{ + if (!ctx) + return "NULL"; + + if (ctx->gw->config.log_prefix_hnb_id) + return ctx->identity_info; + else + return umts_cell_id_name(&ctx->id); +} + +void hnb_context_release(struct hnb_context *ctx) +{ + struct hnbgw_context_map *map, *map2; + + /* remove from the list of HNB contexts */ + llist_del(&ctx->list); + + /* deactivate all context maps */ + llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) { + /* remove it from list, as HNB context will soon be + * gone. Let's hope the second osmo_llist_del in the + * map garbage collector works fine? */ + llist_del(&map->hnb_list); + llist_del(&map->cn_list); + context_map_deactivate(map); + } + ue_context_free_by_hnb(ctx->gw, ctx); + + osmo_stream_srv_destroy(ctx->conn); + + talloc_free(ctx); +} + +/*! call-back when the listen FD has something to read */ +static int accept_cb(struct osmo_stream_srv_link *srv, int fd) +{ + struct hnb_gw *gw = osmo_stream_srv_link_get_data(srv); + struct hnb_context *ctx; + + ctx = hnb_context_alloc(gw, srv, fd); + if (!ctx) + return -ENOMEM; + + return 0; +} + +static const struct log_info_cat log_cat[] = { + [DMAIN] = { + .name = "DMAIN", .loglevel = LOGL_NOTICE, .enabled = 1, + .color = "", + .description = "Main program", + }, + [DHNBAP] = { + .name = "DHNBAP", .loglevel = LOGL_NOTICE, .enabled = 1, + .color = "", + .description = "Home Node B Application Part", + }, + [DRUA] = { + .name = "DRUA", .loglevel = LOGL_NOTICE, .enabled = 1, + .color = "", + .description = "RANAP User Adaptation", + }, + [DRANAP] = { + .name = "DRANAP", .loglevel = LOGL_NOTICE, .enabled = 1, + .color = "", + .description = "RAN Application Part", + }, +}; + +static const struct log_info hnbgw_log_info = { + .cat = log_cat, + .num_cat = ARRAY_SIZE(log_cat), +}; + +static struct vty_app_info vty_info = { + .name = "OsmoHNBGW", + .version = PACKAGE_VERSION, + .go_parent_cb = hnbgw_vty_go_parent, +}; + +static struct { + int daemonize; + const char *config_file; + bool log_disable_color; + bool log_enable_timestamp; + int log_level; + const char *log_category_mask; +} hnbgw_cmdline_config = { + 0, + "osmo-hnbgw.cfg", + false, + false, + 0, + NULL, +}; + +static void print_usage() +{ + printf("Usage: osmo-hnbgw\n"); +} + +static void print_help() +{ + printf(" -h --help This text.\n"); + printf(" -d option --debug=DHNBAP:DRUA:DRANAP:DMAIN Enable debugging.\n"); + printf(" -D --daemonize Fork the process into a background daemon.\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -s --disable-color\n"); + printf(" -T --timestamp Prefix every log line with a timestamp.\n"); + printf(" -V --version Print the version of OsmoHNBGW.\n"); + printf(" -e --log-level number Set a global loglevel.\n"); + + printf("\nVTY reference generation:\n"); + printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"); + printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n"); +} + +static void handle_long_options(const char *prog_name, const int long_option) +{ + static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT; + + switch (long_option) { + case 1: + vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg); + if (vty_ref_mode < 0) { + fprintf(stderr, "%s: Unknown VTY reference generation " + "mode '%s'\n", prog_name, optarg); + exit(2); + } + break; + case 2: + fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n", + get_value_string(vty_ref_gen_mode_names, vty_ref_mode), + get_value_string(vty_ref_gen_mode_desc, vty_ref_mode)); + vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode); + exit(0); + default: + fprintf(stderr, "%s: error parsing cmdline options\n", prog_name); + exit(2); + } +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static int long_option = 0; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"daemonize", 0, 0, 'D'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"version", 0, 0, 'V' }, + {"log-level", 1, 0, 'e'}, + {"vty-ref-mode", 1, &long_option, 1}, + {"vty-ref-xml", 0, &long_option, 2}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:Dc:sTVe:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 0: + handle_long_options(argv[0], long_option); + break; + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + hnbgw_cmdline_config.log_disable_color = true; + break; + case 'd': + hnbgw_cmdline_config.log_category_mask = optarg; + break; + case 'D': + hnbgw_cmdline_config.daemonize = 1; + break; + case 'c': + hnbgw_cmdline_config.config_file = optarg; + break; + case 'T': + hnbgw_cmdline_config.log_enable_timestamp = true; + break; + case 'e': + hnbgw_cmdline_config.log_level = atoi(optarg); + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + break; + } + } + + if (argc > optind) { + fprintf(stderr, "Unsupported positional arguments on command line\n"); + exit(2); + } +} + +CTRL_CMD_DEFINE_RO(hnb_info, "info"); +static int get_hnb_info(struct ctrl_cmd *cmd, void *data) +{ + struct hnb_context *hnb = data; + + cmd->reply = talloc_strdup(cmd, hnb->identity_info); + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(hnbs, "num-hnb"); +static int get_hnbs(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = talloc_asprintf(cmd, "%u", hnb_contexts(data)); + + return CTRL_CMD_REPLY; +} + +int hnb_ctrl_cmds_install() +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_hnbs); + rc |= ctrl_cmd_install(CTRL_NODE_HNB, &cmd_hnb_info); + + return rc; +} + +static int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i) +{ + const char *token = vector_slot(vline, *i); + struct hnb_context *hnb; + long num; + + switch (*node_type) { + case CTRL_NODE_ROOT: + if (strcmp(token, "hnb") != 0) + return 0; + + (*i)++; + + if (!ctrl_parse_get_num(vline, *i, &num)) + return -ERANGE; + + hnb = hnb_context_by_id(data, num); + if (!hnb) + return -ENODEV; + + *node_data = hnb; + *node_type = CTRL_NODE_HNB; + break; + default: + return 0; + } + + return 1; +} + +int main(int argc, char **argv) +{ + struct osmo_stream_srv_link *srv; + int rc; + + tall_hnb_ctx = talloc_named_const(NULL, 0, "hnb_context"); + talloc_asn1_ctx = talloc_named_const(NULL, 1, "asn1_context"); + msgb_talloc_ctx_init(tall_hnb_ctx, 0); + + g_hnb_gw = hnb_gw_create(tall_hnb_ctx); + g_hnb_gw->config.rnc_id = 23; + + rc = osmo_init_logging2(tall_hnb_ctx, &hnbgw_log_info); + if (rc < 0) + exit(1); + + rc = osmo_ss7_init(); + if (rc < 0) { + LOGP(DMAIN, LOGL_FATAL, "osmo_ss7_init() failed with rc=%d\n", rc); + exit(1); + } + + vty_info.copyright = osmo_hnbgw_copyright; + vty_init(&vty_info); + + osmo_ss7_vty_init_asp(tall_hnb_ctx); + osmo_sccp_vty_init(); + hnbgw_vty_init(g_hnb_gw, tall_hnb_ctx); + ctrl_vty_init(tall_hnb_ctx); + logging_vty_add_cmds(); + + /* Handle options after vty_init(), for --version */ + handle_options(argc, argv); + + rc = vty_read_config_file(hnbgw_cmdline_config.config_file, NULL); + if (rc < 0) { + LOGP(DMAIN, LOGL_FATAL, "Failed to parse the config file: '%s'\n", + hnbgw_cmdline_config.config_file); + return 1; + } + + /* + * cmdline options take precedence over config file, but if no options + * were passed we must not override the config file. + */ + if (hnbgw_cmdline_config.log_disable_color) + log_set_use_color(osmo_stderr_target, 0); + if (hnbgw_cmdline_config.log_category_mask) + log_parse_category_mask(osmo_stderr_target, + hnbgw_cmdline_config.log_category_mask); + if (hnbgw_cmdline_config.log_enable_timestamp) + log_set_print_timestamp(osmo_stderr_target, 1); + if (hnbgw_cmdline_config.log_level) + log_set_log_level(osmo_stderr_target, + hnbgw_cmdline_config.log_level); + + rc = telnet_init_dynif(tall_hnb_ctx, g_hnb_gw, vty_get_bind_addr(), OSMO_VTY_PORT_HNBGW); + if (rc < 0) { + perror("Error binding VTY port"); + exit(1); + } + + g_hnb_gw->ctrl = ctrl_interface_setup_dynip2(g_hnb_gw, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW, + hnb_ctrl_node_lookup, _LAST_CTRL_NODE_HNB); + if (!g_hnb_gw->ctrl) { + LOGP(DMAIN, LOGL_ERROR, "Failed to create CTRL interface on %s:%u\n", + ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_HNBGW); + exit(1); + } else { + rc = hnb_ctrl_cmds_install(); + if (rc) { + LOGP(DMAIN, LOGL_ERROR, "Failed to install CTRL interface commands\n"); + return 2; + } + } + + ranap_set_log_area(DRANAP); + + rc = hnbgw_cnlink_init(g_hnb_gw, "localhost", M3UA_PORT, "localhost"); + if (rc < 0) { + LOGP(DMAIN, LOGL_ERROR, "Failed to initialize SCCP link to CN\n"); + exit(1); + } + + LOGP(DHNBAP, LOGL_NOTICE, "Using RNC-Id %u\n", g_hnb_gw->config.rnc_id); + + OSMO_ASSERT(g_hnb_gw->config.iuh_local_ip); + LOGP(DMAIN, LOGL_NOTICE, "Listening for Iuh at %s %d\n", + g_hnb_gw->config.iuh_local_ip, + g_hnb_gw->config.iuh_local_port); + srv = osmo_stream_srv_link_create(tall_hnb_ctx); + if (!srv) { + perror("cannot create server"); + exit(1); + } + osmo_stream_srv_link_set_data(srv, g_hnb_gw); + osmo_stream_srv_link_set_proto(srv, IPPROTO_SCTP); + osmo_stream_srv_link_set_nodelay(srv, true); + osmo_stream_srv_link_set_addr(srv, g_hnb_gw->config.iuh_local_ip); + osmo_stream_srv_link_set_port(srv, g_hnb_gw->config.iuh_local_port); + osmo_stream_srv_link_set_accept_cb(srv, accept_cb); + + if (osmo_stream_srv_link_open(srv) < 0) { + perror("Cannot open server"); + exit(1); + } + g_hnb_gw->iuh = srv; + + if (hnbgw_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); + } + + /* not reached */ + exit(0); +} diff --git a/src/osmo-hnbgw/hnbgw_cn.c b/src/osmo-hnbgw/hnbgw_cn.c new file mode 100644 index 0000000..7fbb691 --- /dev/null +++ b/src/osmo-hnbgw/hnbgw_cn.c @@ -0,0 +1,559 @@ +/* IuCS/IuPS Core Network interface of HNB-GW */ + +/* (C) 2015 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 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 +#include + +#include +#include +#include +#include +#include + +/*********************************************************************** + * Outbound RANAP RESET to CN + ***********************************************************************/ + +void hnbgw_cnlink_change_state(struct hnbgw_cnlink *cnlink, enum hnbgw_cnlink_state state); + +static int transmit_rst(struct hnb_gw *gw, RANAP_CN_DomainIndicator_t domain, + struct osmo_sccp_addr *remote_addr) +{ + struct msgb *msg; + RANAP_Cause_t cause = { + .present = RANAP_Cause_PR_transmissionNetwork, + .choice. transmissionNetwork = RANAP_CauseTransmissionNetwork_signalling_transport_resource_failure, + }; + + LOGP(DRANAP, LOGL_NOTICE, "Tx RESET to %s %s\n", + domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS", + osmo_sccp_inst_addr_name(gw->sccp.cnlink->sccp, remote_addr)); + + msg = ranap_new_msg_reset(domain, &cause); + + return osmo_sccp_tx_unitdata_msg(gw->sccp.cnlink->sccp_user, + &gw->sccp.local_addr, + remote_addr, + msg); +} + +static int transmit_reset_ack(struct hnb_gw *gw, RANAP_CN_DomainIndicator_t domain, + const struct osmo_sccp_addr *remote_addr) +{ + struct msgb *msg; + + LOGP(DRANAP, LOGL_NOTICE, "Tx RESET ACK to %s %s\n", + domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS", + osmo_sccp_inst_addr_name(gw->sccp.cnlink->sccp, remote_addr)); + + msg = ranap_new_msg_reset_ack(domain, NULL); + + return osmo_sccp_tx_unitdata_msg(gw->sccp.cnlink->sccp_user, + &gw->sccp.local_addr, + remote_addr, + msg); +} + +/* Timer callback once T_RafC expires */ +static void cnlink_trafc_cb(void *data) +{ + struct hnb_gw *gw = data; + + transmit_rst(gw, RANAP_CN_DomainIndicator_cs_domain, &gw->sccp.iucs_remote_addr); + transmit_rst(gw, RANAP_CN_DomainIndicator_ps_domain, &gw->sccp.iups_remote_addr); + hnbgw_cnlink_change_state(gw->sccp.cnlink, CNLINK_S_EST_RST_TX_WAIT_ACK); + /* The spec states that we should abandon after a configurable + * number of times. We decide to simply continue trying */ +} + +/* change the state of a CN Link */ +void hnbgw_cnlink_change_state(struct hnbgw_cnlink *cnlink, enum hnbgw_cnlink_state state) +{ + switch (state) { + case CNLINK_S_NULL: + case CNLINK_S_EST_PEND: + break; + case CNLINK_S_EST_CONF: + cnlink_trafc_cb(cnlink->gw); + break; + case CNLINK_S_EST_RST_TX_WAIT_ACK: + osmo_timer_schedule(&cnlink->T_RafC, 5, 0); + break; + case CNLINK_S_EST_ACTIVE: + osmo_timer_del(&cnlink->T_RafC); + break; + } +} + +/*********************************************************************** + * Incoming primitives from SCCP User SAP + ***********************************************************************/ + +static int cn_ranap_rx_reset_cmd(struct hnbgw_cnlink *cnlink, + const struct osmo_scu_unitdata_param *unitdata, + RANAP_InitiatingMessage_t *imsg) +{ + RANAP_CN_DomainIndicator_t domain; + RANAP_ResetIEs_t ies; + int rc; + + rc = ranap_decode_reseties(&ies, &imsg->value); + domain = ies.cN_DomainIndicator; + ranap_free_reseties(&ies); + + LOGP(DRANAP, LOGL_NOTICE, "Rx RESET from %s %s, returning ACK\n", + domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS", + osmo_sccp_inst_addr_name(cnlink->sccp, &unitdata->calling_addr)); + + /* FIXME: actually reset connections, if any */ + + if (transmit_reset_ack(cnlink->gw, domain, &unitdata->calling_addr)) + LOGP(DRANAP, LOGL_ERROR, "Error: cannot send RESET ACK to %s %s\n", + domain == RANAP_CN_DomainIndicator_cs_domain ? "IuCS" : "IuPS", + osmo_sccp_inst_addr_name(cnlink->sccp, &unitdata->calling_addr)); + + return rc; +} + +static int cn_ranap_rx_reset_ack(struct hnbgw_cnlink *cnlink, + RANAP_SuccessfulOutcome_t *omsg) +{ + RANAP_ResetAcknowledgeIEs_t ies; + int rc; + + rc = ranap_decode_resetacknowledgeies(&ies, &omsg->value); + + hnbgw_cnlink_change_state(cnlink, CNLINK_S_EST_ACTIVE); + + ranap_free_resetacknowledgeies(&ies); + return rc; +} + +static int cn_ranap_rx_paging_cmd(struct hnbgw_cnlink *cnlink, + RANAP_InitiatingMessage_t *imsg, + const uint8_t *data, unsigned int len) +{ + struct hnb_gw *gw = cnlink->gw; + struct hnb_context *hnb; + RANAP_PagingIEs_t ies; + int rc; + + rc = ranap_decode_pagingies(&ies, &imsg->value); + if (rc < 0) + return rc; + + /* FIXME: determine which HNBs to send this Paging command, + * rather than broadcasting to all HNBs */ + llist_for_each_entry(hnb, &gw->hnb_list, list) { + rc = rua_tx_udt(hnb, data, len); + } + + ranap_free_pagingies(&ies); + return 0; +} + +static int cn_ranap_rx_initiating_msg(struct hnbgw_cnlink *cnlink, + const struct osmo_scu_unitdata_param *unitdata, + RANAP_InitiatingMessage_t *imsg, + const uint8_t *data, unsigned int len) +{ + switch (imsg->procedureCode) { + case RANAP_ProcedureCode_id_Reset: + return cn_ranap_rx_reset_cmd(cnlink, unitdata, imsg); + case RANAP_ProcedureCode_id_Paging: + return cn_ranap_rx_paging_cmd(cnlink, imsg, data, len); + case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */ + break; + case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */ + break; + case RANAP_ProcedureCode_id_ResetResource: /* request */ + case RANAP_ProcedureCode_id_InformationTransfer: + case RANAP_ProcedureCode_id_DirectInformationTransfer: + case RANAP_ProcedureCode_id_UplinkInformationExchange: + LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP " + "Procedure %ld from CN, ignoring\n", imsg->procedureCode); + break; + default: + LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP " + "Procedure %ld from CN, ignoring\n", imsg->procedureCode); + break; + } + return 0; +} + +static int cn_ranap_rx_successful_msg(struct hnbgw_cnlink *cnlink, + RANAP_SuccessfulOutcome_t *omsg) +{ + switch (omsg->procedureCode) { + case RANAP_ProcedureCode_id_Reset: /* Reset acknowledge */ + return cn_ranap_rx_reset_ack(cnlink, omsg); + case RANAP_ProcedureCode_id_ResetResource: /* response */ + case RANAP_ProcedureCode_id_InformationTransfer: + case RANAP_ProcedureCode_id_DirectInformationTransfer: + case RANAP_ProcedureCode_id_UplinkInformationExchange: + LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP " + "Procedure %ld from CN, ignoring\n", omsg->procedureCode); + break; + default: + LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP " + "Procedure %ld from CN, ignoring\n", omsg->procedureCode); + break; + } + return 0; +} + + +static int _cn_ranap_rx(struct hnbgw_cnlink *cnlink, + const struct osmo_scu_unitdata_param *unitdata, + RANAP_RANAP_PDU_t *pdu, const uint8_t *data, unsigned int len) +{ + int rc; + + switch (pdu->present) { + case RANAP_RANAP_PDU_PR_initiatingMessage: + rc = cn_ranap_rx_initiating_msg(cnlink, unitdata, &pdu->choice.initiatingMessage, + data, len); + break; + case RANAP_RANAP_PDU_PR_successfulOutcome: + rc = cn_ranap_rx_successful_msg(cnlink, &pdu->choice.successfulOutcome); + break; + case RANAP_RANAP_PDU_PR_unsuccessfulOutcome: + LOGP(DRANAP, LOGL_NOTICE, "Received unsupported RANAP " + "unsuccessful outcome procedure %ld from CN, ignoring\n", + pdu->choice.unsuccessfulOutcome.procedureCode); + rc = -ENOTSUP; + break; + default: + LOGP(DRANAP, LOGL_NOTICE, "Received suspicious RANAP " + "presence %u from CN, ignoring\n", pdu->present); + rc = -EINVAL; + break; + } + + return rc; +} + +static int handle_cn_ranap(struct hnbgw_cnlink *cnlink, const struct osmo_scu_unitdata_param *unitdata, + const uint8_t *data, unsigned int len) +{ + RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu; + asn_dec_rval_t dec_ret; + int rc; + + memset(pdu, 0, sizeof(*pdu)); + dec_ret = aper_decode(NULL,&asn_DEF_RANAP_RANAP_PDU, (void **) &pdu, + data, len, 0, 0); + if (dec_ret.code != RC_OK) { + LOGP(DRANAP, LOGL_ERROR, "Error in RANAP ASN.1 decode\n"); + return -1; + } + + rc = _cn_ranap_rx(cnlink, unitdata, pdu, data, len); + + return rc; +} + +static bool pc_and_ssn_match(const struct osmo_sccp_addr *a, const struct osmo_sccp_addr *b) +{ + return (a == b) + || ((a->pc == b->pc) + && (a->ssn == b->ssn)); +} + +static int classify_cn_remote_addr(const struct hnb_gw *gw, + const struct osmo_sccp_addr *cn_remote_addr, + bool *is_ps) +{ + if (pc_and_ssn_match(cn_remote_addr, &gw->sccp.iucs_remote_addr)) { + if (is_ps) + *is_ps = false; + return 0; + } + if (pc_and_ssn_match(cn_remote_addr, &gw->sccp.iups_remote_addr)) { + if (is_ps) + *is_ps = true; + return 0; + } + LOGP(DMAIN, LOGL_ERROR, "Unexpected remote address, matches neither CS nor PS address: %s\n", + osmo_sccp_addr_dump(cn_remote_addr)); + return -1; +} + +static int handle_cn_unitdata(struct hnbgw_cnlink *cnlink, + const struct osmo_scu_unitdata_param *param, + struct osmo_prim_hdr *oph) +{ + if (param->called_addr.ssn != OSMO_SCCP_SSN_RANAP) { + LOGP(DMAIN, LOGL_NOTICE, "N-UNITDATA.ind for unknown SSN %u\n", + param->called_addr.ssn); + return -1; + } + + if (classify_cn_remote_addr(cnlink->gw, ¶m->calling_addr, NULL) < 0) + return -1; + + return handle_cn_ranap(cnlink, param, msgb_l2(oph->msg), msgb_l2len(oph->msg)); +} + +static int handle_cn_conn_conf(struct hnbgw_cnlink *cnlink, + const struct osmo_scu_connect_param *param, + struct osmo_prim_hdr *oph) +{ + /* we don't actually need to do anything, as RUA towards the HNB + * doesn't seem to know any confirmations to its CONNECT + * operation */ + + LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() conn_id=%d\n", + param->conn_id); + LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() called_addr=%s\n", + inet_ntoa(param->called_addr.ip.v4)); + LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() calling_addr=%s\n", + inet_ntoa(param->calling_addr.ip.v4)); + LOGP(DMAIN, LOGL_DEBUG, "handle_cn_conn_conf() responding_addr=%s\n", + inet_ntoa(param->responding_addr.ip.v4)); + + return 0; +} + +static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink, + const struct osmo_scu_data_param *param, + struct osmo_prim_hdr *oph) +{ + struct hnbgw_context_map *map; + + /* connection-oriented data is always passed transparently + * towards the specific HNB, via a RUA connection identified by + * conn_id */ + + map = context_map_by_cn(cnlink, param->conn_id); + if (!map) { + /* FIXME: Return an error / released primitive */ + return 0; + } + + return rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, + msgb_l2(oph->msg), msgb_l2len(oph->msg)); +} + +static int handle_cn_disc_ind(struct hnbgw_cnlink *cnlink, + const struct osmo_scu_disconn_param *param, + struct osmo_prim_hdr *oph) +{ + struct hnbgw_context_map *map; + + LOGP(DMAIN, LOGL_DEBUG, "handle_cn_disc_ind() conn_id=%d originator=%d\n", + param->conn_id, param->originator); + LOGP(DMAIN, LOGL_DEBUG, "handle_cn_disc_ind() responding_addr=%s\n", + inet_ntoa(param->responding_addr.ip.v4)); + + RUA_Cause_t rua_cause = { + .present = RUA_Cause_PR_NOTHING, + /* FIXME: Convert incoming SCCP cause to RUA cause */ + }; + + /* we need to notify the HNB associated with this connection via + * a RUA DISCONNECT */ + + map = context_map_by_cn(cnlink, param->conn_id); + if (!map) { + /* FIXME: Return an error / released primitive */ + return 0; + } + + return rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id, + &rua_cause, msgb_l2(oph->msg), msgb_l2len(oph->msg)); +} + +/* Entry point for primitives coming up from SCCP User SAP */ +static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_sccp_user *scu = ctx; + struct hnbgw_cnlink *cnlink; + struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph; + int rc = 0; + + LOGP(DMAIN, LOGL_DEBUG, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph)); + + if (!scu) { + LOGP(DMAIN, LOGL_ERROR, + "sccp_sap_up(): NULL osmo_sccp_user, cannot send prim (sap %u prim %u op %d)\n", + oph->sap, oph->primitive, oph->operation); + return -1; + } + + cnlink = osmo_sccp_user_get_priv(scu); + if (!cnlink) { + LOGP(DMAIN, LOGL_ERROR, + "sccp_sap_up(): NULL hnbgw_cnlink, cannot send prim (sap %u prim %u op %d)\n", + oph->sap, oph->primitive, oph->operation); + return -1; + } + + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION): + rc = handle_cn_unitdata(cnlink, &prim->u.unitdata, oph); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): + rc = handle_cn_conn_conf(cnlink, &prim->u.connect, oph); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): + rc = handle_cn_data_ind(cnlink, &prim->u.data, oph); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): + rc = handle_cn_disc_ind(cnlink, &prim->u.disconnect, oph); + break; + default: + LOGP(DMAIN, LOGL_ERROR, + "Received unknown prim %u from SCCP USER SAP\n", + OSMO_PRIM_HDR(oph)); + break; + } + + msgb_free(oph->msg); + + return rc; +} + +static bool addr_has_pc_and_ssn(const struct osmo_sccp_addr *addr) +{ + if (!(addr->presence & OSMO_SCCP_ADDR_T_SSN)) + return false; + if (!(addr->presence & OSMO_SCCP_ADDR_T_PC)) + return false; + return true; +} + +static int resolve_addr_name(struct osmo_sccp_addr *dest, struct osmo_ss7_instance **ss7, + const char *addr_name, const char *label, + uint32_t default_pc) +{ + struct osmo_ss7_instance *ss7_tmp; + + if (!addr_name) { + osmo_sccp_make_addr_pc_ssn(dest, default_pc, OSMO_SCCP_SSN_RANAP); + LOGP(DMAIN, LOGL_INFO, "%s remote addr not configured, using default: %s\n", label, + osmo_sccp_addr_name(*ss7, dest)); + return 0; + } + + ss7_tmp = osmo_sccp_addr_by_name(dest, addr_name); + if (!ss7_tmp) { + LOGP(DMAIN, LOGL_ERROR, "%s remote addr: no such SCCP address book entry: '%s'\n", + label, addr_name); + return -1; + } + + if (*ss7 && (*ss7 != ss7_tmp)) { + LOGP(DMAIN, LOGL_ERROR, "IuCS and IuPS cannot be served from separate CS7 instances," + " cs7 instance %d != %d\n", (*ss7)->cfg.id, ss7_tmp->cfg.id); + return -1; + } + + *ss7 = ss7_tmp; + + osmo_sccp_addr_set_ssn(dest, OSMO_SCCP_SSN_RANAP); + + if (!addr_has_pc_and_ssn(dest)) { + LOGP(DMAIN, LOGL_ERROR, "Invalid/incomplete %s remote-addr: %s\n", + label, osmo_sccp_addr_name(*ss7, dest)); + return -1; + } + + LOGP(DRANAP, LOGL_NOTICE, "Remote %s SCCP addr: %s\n", + label, osmo_sccp_addr_name(*ss7, dest)); + return 0; +} + +int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip) +{ + struct hnbgw_cnlink *cnlink; + struct osmo_ss7_instance *ss7; + uint32_t local_pc; + + OSMO_ASSERT(!gw->sccp.client); + OSMO_ASSERT(!gw->sccp.cnlink); + + ss7 = NULL; + if (resolve_addr_name(&gw->sccp.iucs_remote_addr, &ss7, + gw->config.iucs_remote_addr_name, "IuCS", (23 << 3) + 1)) + return -1; + if (resolve_addr_name(&gw->sccp.iups_remote_addr, &ss7, + gw->config.iups_remote_addr_name, "IuPS", (23 << 3) + 4)) + return -1; + + if (!ss7) { + LOGP(DRANAP, LOGL_NOTICE, "No cs7 instance configured for IuCS nor IuPS," + " creating default instance\n"); + ss7 = osmo_ss7_instance_find_or_create(gw, 0); + ss7->cfg.primary_pc = (23 << 3) + 5; + } + + if (!osmo_ss7_pc_is_valid(ss7->cfg.primary_pc)) { + LOGP(DMAIN, LOGL_ERROR, "IuCS/IuPS uplink cannot be setup: CS7 instance %d has no point-code set\n", + ss7->cfg.id); + return -1; + } + local_pc = ss7->cfg.primary_pc; + + osmo_sccp_make_addr_pc_ssn(&gw->sccp.local_addr, local_pc, OSMO_SCCP_SSN_RANAP); + LOGP(DRANAP, LOGL_NOTICE, "Local SCCP addr: %s\n", osmo_sccp_addr_name(ss7, &gw->sccp.local_addr)); + + gw->sccp.client = osmo_sccp_simple_client_on_ss7_id(gw, ss7->cfg.id, "OsmoHNBGW", + local_pc, OSMO_SS7_ASP_PROT_M3UA, + 0, local_ip, stp_port, stp_host); + if (!gw->sccp.client) { + LOGP(DMAIN, LOGL_ERROR, "Failed to init SCCP Client\n"); + return -1; + } + + cnlink = talloc_zero(gw, struct hnbgw_cnlink); + cnlink->gw = gw; + INIT_LLIST_HEAD(&cnlink->map_list); + cnlink->T_RafC.cb = cnlink_trafc_cb; + cnlink->T_RafC.data = gw; + cnlink->next_conn_id = 1000; + + cnlink->sccp_user = osmo_sccp_user_bind_pc(gw->sccp.client, "OsmoHNBGW", sccp_sap_up, + OSMO_SCCP_SSN_RANAP, gw->sccp.local_addr.pc); + if (!cnlink->sccp_user) { + LOGP(DMAIN, LOGL_ERROR, "Failed to init SCCP User\n"); + return -1; + } + + LOGP(DRANAP, LOGL_NOTICE, "Remote SCCP addr: IuCS: %s\n", + osmo_sccp_addr_name(ss7, &gw->sccp.iucs_remote_addr)); + LOGP(DRANAP, LOGL_NOTICE, "Remote SCCP addr: IuPS: %s\n", + osmo_sccp_addr_name(ss7, &gw->sccp.iups_remote_addr)); + + /* In sccp_sap_up() we expect the cnlink in the user's priv. */ + osmo_sccp_user_set_priv(cnlink->sccp_user, cnlink); + + gw->sccp.cnlink = cnlink; + + return 0; +} diff --git a/src/osmo-hnbgw/hnbgw_hnbap.c b/src/osmo-hnbgw/hnbgw_hnbap.c new file mode 100644 index 0000000..5b92e4e --- /dev/null +++ b/src/osmo-hnbgw/hnbgw_hnbap.c @@ -0,0 +1,632 @@ +/* hnb-gw specific code for HNBAP */ + +/* (C) 2015 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 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 +#include + +#include "asn1helpers.h" +#include +#include + +#include +#include + +#define IU_MSG_NUM_IES 32 +#define IU_MSG_NUM_EXT_IES 32 + +static int hnbgw_hnbap_tx(struct hnb_context *ctx, struct msgb *msg) +{ + if (!msg) + return -EINVAL; + + msgb_sctp_ppid(msg) = IUH_PPI_HNBAP; + osmo_stream_srv_send(ctx->conn, msg); + + return 0; +} + +static int hnbgw_tx_hnb_register_rej(struct hnb_context *ctx) +{ + HNBAP_HNBRegisterReject_t reject_out; + HNBAP_HNBRegisterRejectIEs_t reject; + struct msgb *msg; + int rc; + + reject.presenceMask = 0, + reject.cause.present = HNBAP_Cause_PR_radioNetwork; + reject.cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unspecified; + + /* encode the Information Elements */ + memset(&reject_out, 0, sizeof(reject_out)); + rc = hnbap_encode_hnbregisterrejecties(&reject_out, &reject); + if (rc < 0) { + LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to encode HNB-REGISTER-REJECT to %s: rc=%d\n", + ctx->identity_info, rc); + return rc; + } + + /* generate a successfull outcome PDU */ + msg = hnbap_generate_unsuccessful_outcome(HNBAP_ProcedureCode_id_HNBRegister, + HNBAP_Criticality_reject, + &asn_DEF_HNBAP_HNBRegisterReject, + &reject_out); + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_HNBRegisterReject, &reject_out); + + rc = hnbgw_hnbap_tx(ctx, msg); + if (rc == 0) { + /* Tell libosmo-netif to destroy this connection when it is done + * sending our HNB-REGISTER-REJECT response. */ + osmo_stream_srv_set_flush_and_destroy(ctx->conn); + } else { + /* The message was not queued. Destroy the connection right away. */ + hnb_context_release(ctx); + return rc; + } + + return 0; +} + +static int hnbgw_tx_hnb_register_acc(struct hnb_context *ctx) +{ + HNBAP_HNBRegisterAccept_t accept_out; + struct msgb *msg; + int rc; + + /* Single required response IE: RNC-ID */ + HNBAP_HNBRegisterAcceptIEs_t accept = { + .rnc_id = ctx->gw->config.rnc_id + }; + + /* encode the Information Elements */ + memset(&accept_out, 0, sizeof(accept_out)); + rc = hnbap_encode_hnbregisteraccepties(&accept_out, &accept); + if (rc < 0) { + LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to encode HNB-REGISTER-ACCEPT to %s: rc=%d\n", + ctx->identity_info, rc); + return rc; + } + + /* generate a successfull outcome PDU */ + msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_HNBRegister, + HNBAP_Criticality_reject, + &asn_DEF_HNBAP_HNBRegisterAccept, + &accept_out); + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_HNBRegisterAccept, &accept_out); + + LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "Accepting HNB-REGISTER-REQ from %s\n", ctx->identity_info); + + return hnbgw_hnbap_tx(ctx, msg); +} + + +static int hnbgw_tx_ue_register_acc(struct ue_context *ue) +{ + HNBAP_UERegisterAccept_t accept_out; + HNBAP_UERegisterAcceptIEs_t accept; + struct msgb *msg; + uint8_t encoded_imsi[10]; + uint32_t ctx_id; + size_t encoded_imsi_len; + int rc; + + encoded_imsi_len = ranap_imsi_encode(encoded_imsi, + sizeof(encoded_imsi), ue->imsi); + + memset(&accept, 0, sizeof(accept)); + accept.uE_Identity.present = HNBAP_UE_Identity_PR_iMSI; + OCTET_STRING_fromBuf(&accept.uE_Identity.choice.iMSI, + (const char *)encoded_imsi, encoded_imsi_len); + asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id); + + memset(&accept_out, 0, sizeof(accept_out)); + rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept); + if (rc < 0) { + return rc; + } + + msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_UERegister, + HNBAP_Criticality_reject, + &asn_DEF_HNBAP_UERegisterAccept, + &accept_out); + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, &accept.uE_Identity.choice.iMSI); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out); + + return hnbgw_hnbap_tx(ue->hnb, msg); +} + +static int hnbgw_tx_ue_register_rej_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id) +{ + HNBAP_UERegisterReject_t reject_out; + HNBAP_UERegisterRejectIEs_t reject; + struct msgb *msg; + int rc; + + memset(&reject, 0, sizeof(reject)); + reject.uE_Identity.present = ue_id->present; + + /* Copy the identity over to the reject message */ + switch (ue_id->present) { + case HNBAP_UE_Identity_PR_tMSILAI: + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id tMSI %d %s\n", ue_id->choice.tMSILAI.tMSI.size, + osmo_hexdump(ue_id->choice.tMSILAI.tMSI.buf, ue_id->choice.tMSILAI.tMSI.size)); + + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id pLMNID %d %s\n", ue_id->choice.tMSILAI.lAI.pLMNID.size, + osmo_hexdump(ue_id->choice.tMSILAI.lAI.pLMNID.buf, ue_id->choice.tMSILAI.lAI.pLMNID.size)); + + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id lAC %d %s\n", ue_id->choice.tMSILAI.lAI.lAC.size, + osmo_hexdump(ue_id->choice.tMSILAI.lAI.lAC.buf, ue_id->choice.tMSILAI.lAI.lAC.size)); + + BIT_STRING_fromBuf(&reject.uE_Identity.choice.tMSILAI.tMSI, + ue_id->choice.tMSILAI.tMSI.buf, + ue_id->choice.tMSILAI.tMSI.size * 8 + - ue_id->choice.tMSILAI.tMSI.bits_unused); + OCTET_STRING_fromBuf(&reject.uE_Identity.choice.tMSILAI.lAI.pLMNID, + (const char *)ue_id->choice.tMSILAI.lAI.pLMNID.buf, + ue_id->choice.tMSILAI.lAI.pLMNID.size); + OCTET_STRING_fromBuf(&reject.uE_Identity.choice.tMSILAI.lAI.lAC, + (const char *)ue_id->choice.tMSILAI.lAI.lAC.buf, + ue_id->choice.tMSILAI.lAI.lAC.size); + break; + + case HNBAP_UE_Identity_PR_pTMSIRAI: + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id pTMSI %d %s\n", ue_id->choice.pTMSIRAI.pTMSI.size, + osmo_hexdump(ue_id->choice.pTMSIRAI.pTMSI.buf, ue_id->choice.pTMSIRAI.pTMSI.size)); + + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id pLMNID %d %s\n", ue_id->choice.pTMSIRAI.rAI.lAI.pLMNID.size, + osmo_hexdump(ue_id->choice.pTMSIRAI.rAI.lAI.pLMNID.buf, ue_id->choice.pTMSIRAI.rAI.lAI.pLMNID.size)); + + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id lAC %d %s\n", ue_id->choice.pTMSIRAI.rAI.lAI.lAC.size, + osmo_hexdump(ue_id->choice.pTMSIRAI.rAI.lAI.lAC.buf, ue_id->choice.pTMSIRAI.rAI.lAI.lAC.size)); + + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id rAC %d %s\n", ue_id->choice.pTMSIRAI.rAI.rAC.size, + osmo_hexdump(ue_id->choice.pTMSIRAI.rAI.rAC.buf, ue_id->choice.pTMSIRAI.rAI.rAC.size)); + + BIT_STRING_fromBuf(&reject.uE_Identity.choice.pTMSIRAI.pTMSI, + ue_id->choice.pTMSIRAI.pTMSI.buf, + ue_id->choice.pTMSIRAI.pTMSI.size * 8 + - ue_id->choice.pTMSIRAI.pTMSI.bits_unused); + OCTET_STRING_fromBuf(&reject.uE_Identity.choice.pTMSIRAI.rAI.lAI.pLMNID, + (const char *)ue_id->choice.pTMSIRAI.rAI.lAI.pLMNID.buf, + ue_id->choice.pTMSIRAI.rAI.lAI.pLMNID.size); + OCTET_STRING_fromBuf(&reject.uE_Identity.choice.pTMSIRAI.rAI.lAI.lAC, + (const char *)ue_id->choice.pTMSIRAI.rAI.lAI.lAC.buf, + ue_id->choice.pTMSIRAI.rAI.lAI.lAC.size); + OCTET_STRING_fromBuf(&reject.uE_Identity.choice.pTMSIRAI.rAI.rAC, + (const char *)ue_id->choice.pTMSIRAI.rAI.rAC.buf, + ue_id->choice.pTMSIRAI.rAI.rAC.size); + break; + + default: + LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Cannot compose UE Register Reject:" + " unsupported UE ID (present=%d)\n", ue_id->present); + return -1; + } + + LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Rejecting UE Register Request: TMSI identity registration is switched off\n"); + + reject.cause.present = HNBAP_Cause_PR_radioNetwork; + reject.cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_invalid_UE_identity; + + memset(&reject_out, 0, sizeof(reject_out)); + rc = hnbap_encode_ueregisterrejecties(&reject_out, &reject); + if (rc < 0) + return rc; + + msg = hnbap_generate_unsuccessful_outcome(HNBAP_ProcedureCode_id_UERegister, + HNBAP_Criticality_reject, + &asn_DEF_HNBAP_UERegisterReject, + &reject_out); + + /* Free copied identity IEs */ + switch (ue_id->present) { + case HNBAP_UE_Identity_PR_tMSILAI: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING, + &reject.uE_Identity.choice.tMSILAI.tMSI); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &reject.uE_Identity.choice.tMSILAI.lAI.pLMNID); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &reject.uE_Identity.choice.tMSILAI.lAI.lAC); + break; + + case HNBAP_UE_Identity_PR_pTMSIRAI: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING, + &reject.uE_Identity.choice.pTMSIRAI.pTMSI); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &reject.uE_Identity.choice.pTMSIRAI.rAI.lAI.pLMNID); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &reject.uE_Identity.choice.pTMSIRAI.rAI.lAI.lAC); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &reject.uE_Identity.choice.pTMSIRAI.rAI.rAC); + break; + + default: + /* should never happen after above switch() */ + break; + } + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterReject, &reject_out); + + return hnbgw_hnbap_tx(hnb, msg); +} + +static int hnbgw_tx_ue_register_acc_tmsi(struct hnb_context *hnb, HNBAP_UE_Identity_t *ue_id) +{ + HNBAP_UERegisterAccept_t accept_out; + HNBAP_UERegisterAcceptIEs_t accept; + struct msgb *msg; + uint32_t ctx_id; + uint32_t tmsi = 0; + struct ue_context *ue; + int rc; + + memset(&accept, 0, sizeof(accept)); + accept.uE_Identity.present = ue_id->present; + + switch (ue_id->present) { + case HNBAP_UE_Identity_PR_tMSILAI: + BIT_STRING_fromBuf(&accept.uE_Identity.choice.tMSILAI.tMSI, + ue_id->choice.tMSILAI.tMSI.buf, + ue_id->choice.tMSILAI.tMSI.size * 8 + - ue_id->choice.tMSILAI.tMSI.bits_unused); + tmsi = *(uint32_t*)accept.uE_Identity.choice.tMSILAI.tMSI.buf; + OCTET_STRING_fromBuf(&accept.uE_Identity.choice.tMSILAI.lAI.pLMNID, + (const char *)ue_id->choice.tMSILAI.lAI.pLMNID.buf, + ue_id->choice.tMSILAI.lAI.pLMNID.size); + OCTET_STRING_fromBuf(&accept.uE_Identity.choice.tMSILAI.lAI.lAC, + (const char *)ue_id->choice.tMSILAI.lAI.lAC.buf, + ue_id->choice.tMSILAI.lAI.lAC.size); + break; + + case HNBAP_UE_Identity_PR_pTMSIRAI: + BIT_STRING_fromBuf(&accept.uE_Identity.choice.pTMSIRAI.pTMSI, + ue_id->choice.pTMSIRAI.pTMSI.buf, + ue_id->choice.pTMSIRAI.pTMSI.size * 8 + - ue_id->choice.pTMSIRAI.pTMSI.bits_unused); + tmsi = *(uint32_t*)accept.uE_Identity.choice.pTMSIRAI.pTMSI.buf; + OCTET_STRING_fromBuf(&accept.uE_Identity.choice.pTMSIRAI.rAI.lAI.pLMNID, + (const char *)ue_id->choice.pTMSIRAI.rAI.lAI.pLMNID.buf, + ue_id->choice.pTMSIRAI.rAI.lAI.pLMNID.size); + OCTET_STRING_fromBuf(&accept.uE_Identity.choice.pTMSIRAI.rAI.lAI.lAC, + (const char *)ue_id->choice.pTMSIRAI.rAI.lAI.lAC.buf, + ue_id->choice.pTMSIRAI.rAI.lAI.lAC.size); + OCTET_STRING_fromBuf(&accept.uE_Identity.choice.pTMSIRAI.rAI.rAC, + (const char *)ue_id->choice.pTMSIRAI.rAI.rAC.buf, + ue_id->choice.pTMSIRAI.rAI.rAC.size); + break; + + default: + LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Unsupportedccept UE ID (present=%d)\n", ue_id->present); + return -1; + } + + tmsi = ntohl(tmsi); + LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "HNBAP register with TMSI %x\n", tmsi); + + ue = ue_context_by_tmsi(hnb->gw, tmsi); + if (!ue) + ue = ue_context_alloc(hnb, NULL, tmsi); + + asn1_u24_to_bitstring(&accept.context_ID, &ctx_id, ue->context_id); + + memset(&accept_out, 0, sizeof(accept_out)); + rc = hnbap_encode_ueregisteraccepties(&accept_out, &accept); + if (rc < 0) + return rc; + + msg = hnbap_generate_successful_outcome(HNBAP_ProcedureCode_id_UERegister, + HNBAP_Criticality_reject, + &asn_DEF_HNBAP_UERegisterAccept, + &accept_out); + + switch (ue_id->present) { + case HNBAP_UE_Identity_PR_tMSILAI: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING, + &accept.uE_Identity.choice.tMSILAI.tMSI); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &accept.uE_Identity.choice.tMSILAI.lAI.pLMNID); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &accept.uE_Identity.choice.tMSILAI.lAI.lAC); + break; + + case HNBAP_UE_Identity_PR_pTMSIRAI: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_BIT_STRING, + &accept.uE_Identity.choice.pTMSIRAI.pTMSI); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &accept.uE_Identity.choice.pTMSIRAI.rAI.lAI.pLMNID); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &accept.uE_Identity.choice.pTMSIRAI.rAI.lAI.lAC); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING, + &accept.uE_Identity.choice.pTMSIRAI.rAI.rAC); + break; + + default: + /* should never happen after above switch() */ + break; + } + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_UERegisterAccept, &accept_out); + + return hnbgw_hnbap_tx(hnb, msg); +} + +static int hnbgw_rx_hnb_deregister(struct hnb_context *ctx, ANY_t *in) +{ + HNBAP_HNBDe_RegisterIEs_t ies; + int rc; + + rc = hnbap_decode_hnbde_registeries(&ies, in); + if (rc < 0) + return rc; + + LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-DE-REGISTER cause=%s\n", hnbap_cause_str(&ies.cause)); + + hnbap_free_hnbde_registeries(&ies); + hnb_context_release(ctx); + + return 0; +} + +static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in) +{ + struct hnb_context *hnb; + HNBAP_HNBRegisterRequestIEs_t ies; + int rc; + + rc = hnbap_decode_hnbregisterrequesties(&ies, in); + if (rc < 0) { + LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Failure to decode HNB-REGISTER-REQ from %s: rc=%d\n", + ctx->identity_info, rc); + return rc; + } + + /* copy all identity parameters from the message to ctx */ + asn1_strncpy(ctx->identity_info, &ies.hnB_Identity.hNB_Identity_Info, + sizeof(ctx->identity_info)); + ctx->id.lac = asn1str_to_u16(&ies.lac); + ctx->id.sac = asn1str_to_u16(&ies.sac); + ctx->id.rac = asn1str_to_u8(&ies.rac); + ctx->id.cid = asn1bitstr_to_u28(&ies.cellIdentity); + gsm48_mcc_mnc_from_bcd(ies.plmNidentity.buf, &ctx->id.mcc, &ctx->id.mnc); + + llist_for_each_entry(hnb, &ctx->gw->hnb_list, list) { + if (hnb->hnb_registered && ctx != hnb && memcmp(&ctx->id, &hnb->id, sizeof(ctx->id)) == 0) { + struct osmo_fd *ofd = osmo_stream_srv_get_ofd(ctx->conn); + char *name = osmo_sock_get_name(ctx, ofd->fd); + LOGHNB(ctx, DHNBAP, LOGL_ERROR, "rejecting HNB-REGISTER-REQ with duplicate cell identity " + "MCC=%u,MNC=%u,LAC=%u,RAC=%u,SAC=%u,CID=%u from %s\n", + ctx->id.mcc, ctx->id.mnc, ctx->id.lac, ctx->id.rac, ctx->id.sac, ctx->id.cid, name); + talloc_free(name); + return hnbgw_tx_hnb_register_rej(ctx); + } + } + + ctx->hnb_registered = true; + + LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "HNB-REGISTER-REQ from %s\n", ctx->identity_info); + + /* Send HNBRegisterAccept */ + rc = hnbgw_tx_hnb_register_acc(ctx); + hnbap_free_hnbregisterrequesties(&ies); + return rc; +} + +static int hnbgw_rx_ue_register_req(struct hnb_context *ctx, ANY_t *in) +{ + HNBAP_UERegisterRequestIEs_t ies; + struct ue_context *ue; + char imsi[16]; + int rc; + + rc = hnbap_decode_ueregisterrequesties(&ies, in); + if (rc < 0) + return rc; + + switch (ies.uE_Identity.present) { + case HNBAP_UE_Identity_PR_iMSI: + ranap_bcd_decode(imsi, sizeof(imsi), ies.uE_Identity.choice.iMSI.buf, + ies.uE_Identity.choice.iMSI.size); + break; + case HNBAP_UE_Identity_PR_iMSIDS41: + ranap_bcd_decode(imsi, sizeof(imsi), ies.uE_Identity.choice.iMSIDS41.buf, + ies.uE_Identity.choice.iMSIDS41.size); + break; + case HNBAP_UE_Identity_PR_iMSIESN: + ranap_bcd_decode(imsi, sizeof(imsi), ies.uE_Identity.choice.iMSIESN.iMSIDS41.buf, + ies.uE_Identity.choice.iMSIESN.iMSIDS41.size); + break; + case HNBAP_UE_Identity_PR_tMSILAI: + case HNBAP_UE_Identity_PR_pTMSIRAI: + if (ctx->gw->config.hnbap_allow_tmsi) + rc = hnbgw_tx_ue_register_acc_tmsi(ctx, &ies.uE_Identity); + else + rc = hnbgw_tx_ue_register_rej_tmsi(ctx, &ies.uE_Identity); + /* all has been handled by TMSI, skip the IMSI code below */ + hnbap_free_ueregisterrequesties(&ies); + return rc; + default: + LOGHNB(ctx, DHNBAP, LOGL_NOTICE, "UE-REGISTER-REQ with unsupported UE Id type %d\n", + ies.uE_Identity.present); + hnbap_free_ueregisterrequesties(&ies); + return rc; + } + + LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-REGISTER-REQ ID_type=%d imsi=%s cause=%ld\n", + ies.uE_Identity.present, imsi, ies.registration_Cause); + + ue = ue_context_by_imsi(ctx->gw, imsi); + if (!ue) + ue = ue_context_alloc(ctx, imsi, 0); + + hnbap_free_ueregisterrequesties(&ies); + /* Send UERegisterAccept */ + return hnbgw_tx_ue_register_acc(ue); +} + +static int hnbgw_rx_ue_deregister(struct hnb_context *ctx, ANY_t *in) +{ + HNBAP_UEDe_RegisterIEs_t ies; + struct ue_context *ue; + int rc; + uint32_t ctxid; + + rc = hnbap_decode_uede_registeries(&ies, in); + if (rc < 0) + return rc; + + ctxid = asn1bitstr_to_u24(&ies.context_ID); + + LOGHNB(ctx, DHNBAP, LOGL_DEBUG, "UE-DE-REGISTER context=%u cause=%s\n", ctxid, hnbap_cause_str(&ies.cause)); + + ue = ue_context_by_id(ctx->gw, ctxid); + if (ue) + ue_context_free(ue); + + hnbap_free_uede_registeries(&ies); + return 0; +} + +static int hnbgw_rx_err_ind(struct hnb_context *hnb, ANY_t *in) +{ + HNBAP_ErrorIndicationIEs_t ies; + int rc; + + rc = hnbap_decode_errorindicationies(&ies, in); + if (rc < 0) + return rc; + + LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "HNBAP ERROR.ind, cause: %s\n", hnbap_cause_str(&ies.cause)); + + hnbap_free_errorindicationies(&ies); + return 0; +} + +static int hnbgw_rx_initiating_msg(struct hnb_context *hnb, HNBAP_InitiatingMessage_t *imsg) +{ + int rc = 0; + + switch (imsg->procedureCode) { + case HNBAP_ProcedureCode_id_HNBRegister: /* 8.2 */ + rc = hnbgw_rx_hnb_register_req(hnb, &imsg->value); + break; + case HNBAP_ProcedureCode_id_HNBDe_Register: /* 8.3 */ + rc = hnbgw_rx_hnb_deregister(hnb, &imsg->value); + break; + case HNBAP_ProcedureCode_id_UERegister: /* 8.4 */ + rc = hnbgw_rx_ue_register_req(hnb, &imsg->value); + break; + case HNBAP_ProcedureCode_id_UEDe_Register: /* 8.5 */ + rc = hnbgw_rx_ue_deregister(hnb, &imsg->value); + break; + case HNBAP_ProcedureCode_id_ErrorIndication: /* 8.6 */ + rc = hnbgw_rx_err_ind(hnb, &imsg->value); + break; + case HNBAP_ProcedureCode_id_TNLUpdate: /* 8.9 */ + case HNBAP_ProcedureCode_id_HNBConfigTransfer: /* 8.10 */ + case HNBAP_ProcedureCode_id_RelocationComplete: /* 8.11 */ + case HNBAP_ProcedureCode_id_U_RNTIQuery: /* 8.12 */ + case HNBAP_ProcedureCode_id_privateMessage: + LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unimplemented HNBAP Procedure %ld\n", imsg->procedureCode); + break; + default: + LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unknown HNBAP Procedure %ld\n", imsg->procedureCode); + break; + } + + return rc; +} + +static int hnbgw_rx_successful_outcome_msg(struct hnb_context *hnb, HNBAP_SuccessfulOutcome_t *msg) +{ + /* We don't care much about HNBAP */ + return 0; +} + +static int hnbgw_rx_unsuccessful_outcome_msg(struct hnb_context *hnb, HNBAP_UnsuccessfulOutcome_t *msg) +{ + /* We don't care much about HNBAP */ + LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Received Unsuccessful Outcome, procedureCode %ld, criticality %ld," + " cell mcc %u mnc %u lac %u rac %u sac %u cid %u\n", msg->procedureCode, msg->criticality, + hnb->id.mcc, hnb->id.mnc, hnb->id.lac, hnb->id.rac, hnb->id.sac, hnb->id.cid); + return 0; +} + + +static int _hnbgw_hnbap_rx(struct hnb_context *hnb, HNBAP_HNBAP_PDU_t *pdu) +{ + int rc = 0; + + /* it's a bit odd that we can't dispatch on procedure code, but + * that's not possible */ + switch (pdu->present) { + case HNBAP_HNBAP_PDU_PR_initiatingMessage: + rc = hnbgw_rx_initiating_msg(hnb, &pdu->choice.initiatingMessage); + break; + case HNBAP_HNBAP_PDU_PR_successfulOutcome: + rc = hnbgw_rx_successful_outcome_msg(hnb, &pdu->choice.successfulOutcome); + break; + case HNBAP_HNBAP_PDU_PR_unsuccessfulOutcome: + rc = hnbgw_rx_unsuccessful_outcome_msg(hnb, &pdu->choice.unsuccessfulOutcome); + break; + default: + LOGHNB(hnb, DHNBAP, LOGL_NOTICE, "Unknown HNBAP Presence %u\n", pdu->present); + rc = -1; + } + + return rc; +} + +int hnbgw_hnbap_rx(struct hnb_context *hnb, struct msgb *msg) +{ + HNBAP_HNBAP_PDU_t _pdu, *pdu = &_pdu; + asn_dec_rval_t dec_ret; + int rc; + + /* decode and handle to _hnbgw_hnbap_rx() */ + + memset(pdu, 0, sizeof(*pdu)); + dec_ret = aper_decode(NULL, &asn_DEF_HNBAP_HNBAP_PDU, (void **) &pdu, + msg->data, msgb_length(msg), 0, 0); + if (dec_ret.code != RC_OK) { + LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Error in ASN.1 decode\n"); + return -1; + } + + rc = _hnbgw_hnbap_rx(hnb, pdu); + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_HNBAP_HNBAP_PDU, pdu); + + return rc; +} + + +int hnbgw_hnbap_init(void) +{ + return 0; +} diff --git a/src/osmo-hnbgw/hnbgw_ranap.c b/src/osmo-hnbgw/hnbgw_ranap.c new file mode 100644 index 0000000..73b5018 --- /dev/null +++ b/src/osmo-hnbgw/hnbgw_ranap.c @@ -0,0 +1,210 @@ +/* hnb-gw specific code for RANAP */ + +/* (C) 2015 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 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 "asn1helpers.h" + +#include +#include +#include +#include +#include + +static int ranap_tx_reset_ack(struct hnb_context *hnb, + RANAP_CN_DomainIndicator_t domain) +{ + struct msgb *msg; + int rc; + + msg = ranap_new_msg_reset_ack(domain, NULL); + if (!msg) + return -1; + + rc = rua_tx_udt(hnb, msg->data, msgb_length(msg)); + + msgb_free(msg); + + return rc; +} + +static int ranap_rx_init_reset(struct hnb_context *hnb, ANY_t *in) +{ + RANAP_ResetIEs_t ies; + int rc, is_ps = 0; + + rc = ranap_decode_reseties(&ies, in); + if (rc < 0) + return rc; + + if (ies.cN_DomainIndicator == RANAP_CN_DomainIndicator_ps_domain) + is_ps=1; + + LOGHNB(hnb, DRANAP, LOGL_INFO, "Rx RESET.req(%s,%s)\n", is_ps ? "ps" : "cs", + ranap_cause_str(&ies.cause)); + + /* FIXME: Actually we have to wait for some guard time? */ + /* FIXME: Reset all resources related to this HNB/RNC */ + ranap_tx_reset_ack(hnb, ies.cN_DomainIndicator); + + return 0; +} + +static int ranap_rx_error_ind(struct hnb_context *hnb, ANY_t *in) +{ + RANAP_ErrorIndicationIEs_t ies; + int rc; + + rc = ranap_decode_errorindicationies(&ies, in); + if (rc < 0) + return rc; + + if (ies.presenceMask & ERRORINDICATIONIES_RANAP_CAUSE_PRESENT) { + LOGHNB(hnb, DRANAP, LOGL_ERROR, "Rx ERROR.ind(%s)\n", ranap_cause_str(&ies.cause)); + } else + LOGHNB(hnb, DRANAP, LOGL_ERROR, "Rx ERROR.ind\n"); + + return 0; +} + +static int ranap_rx_initiating_msg(struct hnb_context *hnb, RANAP_InitiatingMessage_t *imsg) +{ + int rc = 0; + + /* according tot the spec, we can primarily receive Overload, + * Reset, Reset ACK, Error Indication, reset Resource, Reset + * Resurce Acknowledge as connecitonless RANAP. There are some + * more messages regarding Information Transfer, Direct + * Information Transfer and Uplink Information Trnansfer that we + * can ignore. In either case, it is RANAP that we need to + * decode... */ + switch (imsg->procedureCode) { + case RANAP_ProcedureCode_id_Reset: + /* Reset request */ + rc = ranap_rx_init_reset(hnb, &imsg->value); + break; + case RANAP_ProcedureCode_id_OverloadControl: /* Overload ind */ + break; + case RANAP_ProcedureCode_id_ErrorIndication: /* Error ind */ + rc = ranap_rx_error_ind(hnb, &imsg->value); + break; + case RANAP_ProcedureCode_id_ResetResource: /* request */ + case RANAP_ProcedureCode_id_InformationTransfer: + case RANAP_ProcedureCode_id_DirectInformationTransfer: + case RANAP_ProcedureCode_id_UplinkInformationExchange: + LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received unsupported RANAP " + "Procedure %lu from HNB, ignoring\n", imsg->procedureCode); + break; + default: + LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received suspicious RANAP " + "Procedure %lu from HNB, ignoring\n", imsg->procedureCode); + break; + } + + return rc; +} + +static int ranap_rx_successful_msg(struct hnb_context *hnb, RANAP_SuccessfulOutcome_t *imsg) +{ + /* according tot the spec, we can primarily receive Overload, + * Reset, Reset ACK, Error Indication, reset Resource, Reset + * Resurce Acknowledge as connecitonless RANAP. There are some + * more messages regarding Information Transfer, Direct + * Information Transfer and Uplink Information Trnansfer that we + * can ignore. In either case, it is RANAP that we need to + * decode... */ + switch (imsg->procedureCode) { + case RANAP_ProcedureCode_id_Reset: /* Reset acknowledge */ + break; + case RANAP_ProcedureCode_id_ResetResource: /* response */ + case RANAP_ProcedureCode_id_InformationTransfer: + case RANAP_ProcedureCode_id_DirectInformationTransfer: + case RANAP_ProcedureCode_id_UplinkInformationExchange: + LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received unsupported RANAP " + "Procedure %lu from HNB, ignoring\n", imsg->procedureCode); + break; + default: + LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received suspicious RANAP " + "Procedure %lu from HNB, ignoring\n", imsg->procedureCode); + break; + } + + return 0; +} + + + +static int _hnbgw_ranap_rx(struct hnb_context *hnb, RANAP_RANAP_PDU_t *pdu) +{ + int rc = 0; + + switch (pdu->present) { + case RANAP_RANAP_PDU_PR_initiatingMessage: + rc = ranap_rx_initiating_msg(hnb, &pdu->choice.initiatingMessage); + break; + case RANAP_RANAP_PDU_PR_successfulOutcome: + rc = ranap_rx_successful_msg(hnb, &pdu->choice.successfulOutcome); + break; + case RANAP_RANAP_PDU_PR_unsuccessfulOutcome: + LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received unsupported RANAP " + "unsuccessful outcome procedure %lu from HNB, ignoring\n", + pdu->choice.unsuccessfulOutcome.procedureCode); + break; + default: + LOGHNB(hnb, DRANAP, LOGL_NOTICE, "Received suspicious RANAP " + "presence %u from HNB, ignoring\n", pdu->present); + break; + } + + return rc; +} + + +int hnbgw_ranap_rx(struct msgb *msg, uint8_t *data, size_t len) +{ + RANAP_RANAP_PDU_t _pdu, *pdu = &_pdu; + struct hnb_context *hnb = msg->dst; + asn_dec_rval_t dec_ret; + int rc; + + memset(pdu, 0, sizeof(*pdu)); + dec_ret = aper_decode(NULL,&asn_DEF_RANAP_RANAP_PDU, (void **) &pdu, + data, len, 0, 0); + if (dec_ret.code != RC_OK) { + LOGHNB(hnb, DRANAP, LOGL_ERROR, "Error in RANAP ASN.1 decode\n"); + return -1; + } + + rc = _hnbgw_ranap_rx(hnb, pdu); + + return rc; +} + +int hnbgw_ranap_init(void) +{ + return 0; +} diff --git a/src/osmo-hnbgw/hnbgw_rua.c b/src/osmo-hnbgw/hnbgw_rua.c new file mode 100644 index 0000000..24ca167 --- /dev/null +++ b/src/osmo-hnbgw/hnbgw_rua.c @@ -0,0 +1,566 @@ +/* hnb-gw specific code for RUA (Ranap User Adaption) */ + +/* (C) 2015 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 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 +#include + +#include "asn1helpers.h" + +#include +#include +#include +#include +#include +#include + +static const char *cn_domain_indicator_to_str(RUA_CN_DomainIndicator_t cN_DomainIndicator) +{ + switch (cN_DomainIndicator) { + case RUA_CN_DomainIndicator_cs_domain: + return "IuCS"; + case RUA_CN_DomainIndicator_ps_domain: + return "IuPS"; + default: + return "(unknown-domain)"; + } +} + +static int hnbgw_rua_tx(struct hnb_context *ctx, struct msgb *msg) +{ + if (!msg) + return -EINVAL; + + msgb_sctp_ppid(msg) = IUH_PPI_RUA; + osmo_stream_srv_send(ctx->conn, msg); + + return 0; +} + +int rua_tx_udt(struct hnb_context *hnb, const uint8_t *data, unsigned int len) +{ + RUA_ConnectionlessTransfer_t out; + RUA_ConnectionlessTransferIEs_t ies; + struct msgb *msg; + int rc; + + memset(&ies, 0, sizeof(ies)); + ies.ranaP_Message.buf = (uint8_t *) data; + ies.ranaP_Message.size = len; + + /* FIXME: msgb_free(msg)? ownership not yet clear */ + + memset(&out, 0, sizeof(out)); + rc = rua_encode_connectionlesstransferies(&out, &ies); + if (rc < 0) + return rc; + + msg = rua_generate_initiating_message(RUA_ProcedureCode_id_ConnectionlessTransfer, + RUA_Criticality_reject, + &asn_DEF_RUA_ConnectionlessTransfer, + &out); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_ConnectionlessTransfer, &out); + + LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA payload of %u bytes\n", msgb_length(msg)); + + return hnbgw_rua_tx(hnb, msg); +} + +int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id, + const uint8_t *data, unsigned int len) +{ + RUA_DirectTransfer_t out; + RUA_DirectTransferIEs_t ies; + uint32_t ctxidbuf; + struct msgb *msg; + int rc; + + memset(&ies, 0, sizeof(ies)); + if (is_ps) + ies.cN_DomainIndicator = RUA_CN_DomainIndicator_ps_domain; + else + ies.cN_DomainIndicator = RUA_CN_DomainIndicator_cs_domain; + asn1_u24_to_bitstring(&ies.context_ID, &ctxidbuf, context_id); + ies.ranaP_Message.buf = (uint8_t *) data; + ies.ranaP_Message.size = len; + + /* FIXME: msgb_free(msg)? ownership not yet clear */ + + memset(&out, 0, sizeof(out)); + rc = rua_encode_directtransferies(&out, &ies); + if (rc < 0) + return rc; + + msg = rua_generate_initiating_message(RUA_ProcedureCode_id_DirectTransfer, + RUA_Criticality_reject, + &asn_DEF_RUA_DirectTransfer, + &out); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_DirectTransfer, &out); + + LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA (cn=%s) payload of %u bytes\n", + is_ps ? "ps" : "cs", msgb_length(msg)); + + return hnbgw_rua_tx(hnb, msg); +} + +int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id, + const RUA_Cause_t *cause, const uint8_t *data, unsigned int len) +{ + RUA_Disconnect_t out; + RUA_DisconnectIEs_t ies; + struct msgb *msg; + uint32_t ctxidbuf; + int rc; + + memset(&ies, 0, sizeof(ies)); + if (is_ps) + ies.cN_DomainIndicator = RUA_CN_DomainIndicator_ps_domain; + else + ies.cN_DomainIndicator = RUA_CN_DomainIndicator_cs_domain; + asn1_u24_to_bitstring(&ies.context_ID, &ctxidbuf, context_id); + memcpy(&ies.cause, cause, sizeof(ies.cause)); + if (data && len) { + ies.presenceMask |= DISCONNECTIES_RUA_RANAP_MESSAGE_PRESENT; + ies.ranaP_Message.buf = (uint8_t *) data; + ies.ranaP_Message.size = len; + } + + /* FIXME: msgb_free(msg)? ownership not yet clear */ + + memset(&out, 0, sizeof(out)); + rc = rua_encode_disconnecties(&out, &ies); + if (rc < 0) + return rc; + + msg = rua_generate_initiating_message(RUA_ProcedureCode_id_Disconnect, + RUA_Criticality_reject, + &asn_DEF_RUA_Disconnect, + &out); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RUA_Disconnect, &out); + + LOGHNB(hnb, DRUA, LOGL_DEBUG, "transmitting RUA (cn=%s) payload of %u bytes\n", + is_ps ? "ps" : "cs", msgb_length(msg)); + + + return hnbgw_rua_tx(hnb, msg); +} + + + +/* forward a RUA message to the SCCP User API to SCCP */ +static int rua_to_scu(struct hnb_context *hnb, + RUA_CN_DomainIndicator_t cN_DomainIndicator, + enum osmo_scu_prim_type type, + uint32_t context_id, uint32_t cause, + const uint8_t *data, unsigned int len) +{ + struct msgb *msg; + struct osmo_scu_prim *prim; + struct hnbgw_context_map *map = NULL; + struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink; + struct osmo_sccp_addr *remote_addr; + bool is_ps; + bool release_context_map = false; + int rc; + + switch (cN_DomainIndicator) { + case RUA_CN_DomainIndicator_cs_domain: + remote_addr = &hnb->gw->sccp.iucs_remote_addr; + is_ps = false; + break; + case RUA_CN_DomainIndicator_ps_domain: + remote_addr = &hnb->gw->sccp.iups_remote_addr; + is_ps = true; + break; + default: + LOGHNB(hnb, DRUA, LOGL_ERROR, "Unsupported Domain %ld\n", cN_DomainIndicator); + return -1; + } + + if (!cn) { + LOGHNB(hnb, DRUA, LOGL_NOTICE, "CN=NULL, discarding message\n"); + return 0; + } + + msg = msgb_alloc(1500, "rua_to_sccp"); + + prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, type, PRIM_OP_REQUEST, msg); + + switch (type) { + case OSMO_SCU_PRIM_N_UNITDATA: + LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u (unitdata, no scu_conn_id)\n", + cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr), context_id); + break; + default: + map = context_map_alloc_by_hnb(hnb, context_id, is_ps, cn); + OSMO_ASSERT(map); + LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u scu_conn_id %u\n", + cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr), + map->rua_ctx_id, map->scu_conn_id); + } + + /* add primitive header */ + switch (type) { + case OSMO_SCU_PRIM_N_CONNECT: + prim->u.connect.called_addr = *remote_addr; + prim->u.connect.calling_addr = cn->gw->sccp.local_addr; + prim->u.connect.sccp_class = 2; + prim->u.connect.conn_id = map->scu_conn_id; + /* Two separate logs because of osmo_sccp_addr_dump(). */ + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: called_addr:%s\n", + osmo_sccp_addr_dump(&prim->u.connect.called_addr)); + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: calling_addr:%s\n", + osmo_sccp_addr_dump(&prim->u.connect.calling_addr)); + break; + case OSMO_SCU_PRIM_N_DATA: + prim->u.data.conn_id = map->scu_conn_id; + break; + case OSMO_SCU_PRIM_N_DISCONNECT: + prim->u.disconnect.conn_id = map->scu_conn_id; + prim->u.disconnect.cause = cause; + release_context_map = true; + break; + case OSMO_SCU_PRIM_N_UNITDATA: + prim->u.unitdata.called_addr = *remote_addr; + prim->u.unitdata.calling_addr = cn->gw->sccp.local_addr; + /* Two separate logs because of osmo_sccp_addr_dump(). */ + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_UNITDATA: called_addr:%s\n", + osmo_sccp_addr_dump(&prim->u.unitdata.called_addr)); + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_UNITDATA: calling_addr:%s\n", + osmo_sccp_addr_dump(&prim->u.unitdata.calling_addr)); + break; + default: + return -EINVAL; + } + + /* add optional data section, if needed */ + if (data && len) { + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + } + + rc = osmo_sccp_user_sap_down(cn->sccp_user, &prim->oph); + + if (map && release_context_map) + context_map_deactivate(map); + + return rc; +} + +static uint32_t rua_to_scu_cause(RUA_Cause_t *in) +{ + /* FIXME: Implement this! */ +#if 0 + switch (in->present) { + case RUA_Cause_PR_NOTHING: + break; + case RUA_Cause_PR_radioNetwork: + switch (in->choice.radioNetwork) { + case RUA_CauseRadioNetwork_normal: + case RUA_CauseRadioNetwork_connect_failed: + case RUA_CauseRadioNetwork_network_release: + case RUA_CauseRadioNetwork_unspecified: + } + break; + case RUA_Cause_PR_transport: + switch (in->choice.transport) { + case RUA_CauseTransport_transport_resource_unavailable: + break; + case RUA_CauseTransport_unspecified: + break; + } + break; + case RUA_Cause_PR_protocol: + switch (in->choice.protocol) { + case RUA_CauseProtocol_transfer_syntax_error: + break; + case RUA_CauseProtocol_abstract_syntax_error_reject: + break; + case RUA_CauseProtocol_abstract_syntax_error_ignore_and_notify: + break; + case RUA_CauseProtocol_message_not_compatible_with_receiver_state: + break; + case RUA_CauseProtocol_semantic_error: + break; + case RUA_CauseProtocol_unspecified: + break; + case RUA_CauseProtocol_abstract_syntax_error_falsely_constructed_message: + break; + } + break; + case RUA_Cause_PR_misc: + switch (in->choice.misc) { + case RUA_CauseMisc_processing_overload: + break; + case RUA_CauseMisc_hardware_failure: + break; + case RUA_CauseMisc_o_and_m_intervention: + break; + case RUA_CauseMisc_unspecified: + break; + } + break; + default: + break; + } +#else + return 0; +#endif + +} + +static int rua_rx_init_connect(struct msgb *msg, ANY_t *in) +{ + RUA_ConnectIEs_t ies; + struct hnb_context *hnb = msg->dst; + uint32_t context_id; + int rc; + + rc = rua_decode_connecties(&ies, in); + if (rc < 0) + return rc; + + context_id = asn1bitstr_to_u24(&ies.context_ID); + + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA %s Connect.req(ctx=0x%x, %s)\n", + cn_domain_indicator_to_str(ies.cN_DomainIndicator), context_id, + ies.establishment_Cause == RUA_Establishment_Cause_emergency_call ? "emergency" : "normal"); + + rc = rua_to_scu(hnb, ies.cN_DomainIndicator, OSMO_SCU_PRIM_N_CONNECT, + context_id, 0, ies.ranaP_Message.buf, + ies.ranaP_Message.size); + + rua_free_connecties(&ies); + + return rc; +} + +static int rua_rx_init_disconnect(struct msgb *msg, ANY_t *in) +{ + RUA_DisconnectIEs_t ies; + struct hnb_context *hnb = msg->dst; + uint32_t context_id; + uint32_t scu_cause; + uint8_t *ranap_data = NULL; + unsigned int ranap_len = 0; + int rc; + + rc = rua_decode_disconnecties(&ies, in); + if (rc < 0) + return rc; + + context_id = asn1bitstr_to_u24(&ies.context_ID); + scu_cause = rua_to_scu_cause(&ies.cause); + + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA Disconnect.req(ctx=0x%x,cause=%s)\n", context_id, + rua_cause_str(&ies.cause)); + + if (ies.presenceMask & DISCONNECTIES_RUA_RANAP_MESSAGE_PRESENT) { + ranap_data = ies.ranaP_Message.buf; + ranap_len = ies.ranaP_Message.size; + } + + rc = rua_to_scu(hnb, ies.cN_DomainIndicator, + OSMO_SCU_PRIM_N_DISCONNECT, + context_id, scu_cause, ranap_data, ranap_len); + + rua_free_disconnecties(&ies); + + return rc; +} + +static int rua_rx_init_dt(struct msgb *msg, ANY_t *in) +{ + RUA_DirectTransferIEs_t ies; + struct hnb_context *hnb = msg->dst; + uint32_t context_id; + int rc; + + rc = rua_decode_directtransferies(&ies, in); + if (rc < 0) + return rc; + + context_id = asn1bitstr_to_u24(&ies.context_ID); + + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA Data.req(ctx=0x%x)\n", context_id); + + rc = rua_to_scu(hnb, + ies.cN_DomainIndicator, + OSMO_SCU_PRIM_N_DATA, + context_id, 0, ies.ranaP_Message.buf, + ies.ranaP_Message.size); + + rua_free_directtransferies(&ies); + + return rc; +} + +static int rua_rx_init_udt(struct msgb *msg, ANY_t *in) +{ + RUA_ConnectionlessTransferIEs_t ies; + struct hnb_context *hnb = msg->dst; + int rc; + + rc = rua_decode_connectionlesstransferies(&ies, in); + if (rc < 0) + return rc; + + LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA UData.req()\n"); + + /* according tot the spec, we can primarily receive Overload, + * Reset, Reset ACK, Error Indication, reset Resource, Reset + * Resurce Acknowledge as connecitonless RANAP. There are some + * more messages regarding Information Transfer, Direct + * Information Transfer and Uplink Information Trnansfer that we + * can ignore. In either case, it is RANAP that we need to + * decode... */ + rc = hnbgw_ranap_rx(msg, ies.ranaP_Message.buf, ies.ranaP_Message.size); + rua_free_connectionlesstransferies(&ies); + + return rc; +} + + +static int rua_rx_init_err_ind(struct msgb *msg, ANY_t *in) +{ + RUA_ErrorIndicationIEs_t ies; + struct hnb_context *hnb = msg->dst; + int rc; + + rc = rua_decode_errorindicationies(&ies, in); + if (rc < 0) + return rc; + + LOGHNB(hnb, DRUA, LOGL_ERROR, "RUA UData.ErrorInd(%s)\n", rua_cause_str(&ies.cause)); + + rua_free_errorindicationies(&ies); + return rc; +} + +static int rua_rx_initiating_msg(struct msgb *msg, RUA_InitiatingMessage_t *imsg) +{ + struct hnb_context *hnb = msg->dst; + int rc; + + switch (imsg->procedureCode) { + case RUA_ProcedureCode_id_Connect: + rc = rua_rx_init_connect(msg, &imsg->value); + break; + case RUA_ProcedureCode_id_DirectTransfer: + rc = rua_rx_init_dt(msg, &imsg->value); + break; + case RUA_ProcedureCode_id_Disconnect: + rc = rua_rx_init_disconnect(msg, &imsg->value); + break; + case RUA_ProcedureCode_id_ConnectionlessTransfer: + rc = rua_rx_init_udt(msg, &imsg->value); + break; + case RUA_ProcedureCode_id_ErrorIndication: + rc = rua_rx_init_err_ind(msg, &imsg->value); + break; + case RUA_ProcedureCode_id_privateMessage: + LOGHNB(hnb, DRUA, LOGL_NOTICE, "Unhandled: RUA Initiating Msg: Private Msg\n"); + rc = 0; + break; + default: + LOGHNB(hnb, DRUA, LOGL_NOTICE, "Unknown RUA Procedure %lu\n", imsg->procedureCode); + rc = -1; + } + + return rc; +} + +static int rua_rx_successful_outcome_msg(struct msgb *msg, RUA_SuccessfulOutcome_t *in) +{ + struct hnb_context *hnb = msg->dst; + /* FIXME */ + LOGHNB(hnb, DRUA, LOGL_NOTICE, "Unexpected RUA Successful Outcome\n"); + return -1; +} + +static int rua_rx_unsuccessful_outcome_msg(struct msgb *msg, RUA_UnsuccessfulOutcome_t *in) +{ + struct hnb_context *hnb = msg->dst; + /* FIXME */ + LOGHNB(hnb, DRUA, LOGL_NOTICE, "Unexpected RUA Unsucessful Outcome\n"); + return -1; +} + + +static int _hnbgw_rua_rx(struct msgb *msg, RUA_RUA_PDU_t *pdu) +{ + struct hnb_context *hnb = msg->dst; + int rc; + + /* it's a bit odd that we can't dispatch on procedure code, but + * that's not possible */ + switch (pdu->present) { + case RUA_RUA_PDU_PR_initiatingMessage: + rc = rua_rx_initiating_msg(msg, &pdu->choice.initiatingMessage); + break; + case RUA_RUA_PDU_PR_successfulOutcome: + rc = rua_rx_successful_outcome_msg(msg, &pdu->choice.successfulOutcome); + break; + case RUA_RUA_PDU_PR_unsuccessfulOutcome: + rc = rua_rx_unsuccessful_outcome_msg(msg, &pdu->choice.unsuccessfulOutcome); + break; + default: + LOGHNB(hnb, DRUA, LOGL_NOTICE, "Unknown RUA presence %u\n", pdu->present); + rc = -1; + } + + return rc; +} + +int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg) +{ + RUA_RUA_PDU_t _pdu, *pdu = &_pdu; + asn_dec_rval_t dec_ret; + int rc; + + /* decode and handle to _hnbgw_hnbap_rx() */ + + memset(pdu, 0, sizeof(*pdu)); + dec_ret = aper_decode(NULL, &asn_DEF_RUA_RUA_PDU, (void **) &pdu, + msg->data, msgb_length(msg), 0, 0); + if (dec_ret.code != RC_OK) { + LOGHNB(hnb, DRUA, LOGL_ERROR, "Error in ASN.1 decode\n"); + return -1; + } + + rc = _hnbgw_rua_rx(msg, pdu); + + return rc; +} + + +int hnbgw_rua_init(void) +{ + return 0; +} diff --git a/src/osmo-hnbgw/hnbgw_vty.c b/src/osmo-hnbgw/hnbgw_vty.c new file mode 100644 index 0000000..4ad1ddb --- /dev/null +++ b/src/osmo-hnbgw/hnbgw_vty.c @@ -0,0 +1,418 @@ +/* HNB-GW interface to quagga VTY */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 +#include +#include + +static void *tall_hnb_ctx = NULL; +static struct hnb_gw *g_hnb_gw = NULL; + +static struct cmd_node hnbgw_node = { + HNBGW_NODE, + "%s(config-hnbgw)# ", + 1, +}; + +DEFUN(cfg_hnbgw, cfg_hnbgw_cmd, + "hnbgw", "Configure HNBGW options") +{ + vty->node = HNBGW_NODE; + return CMD_SUCCESS; +} + +static struct cmd_node iuh_node = { + IUH_NODE, + "%s(config-hnbgw-iuh)# ", + 1, +}; + +DEFUN(cfg_hnbgw_iuh, cfg_hnbgw_iuh_cmd, + "iuh", "Configure Iuh options") +{ + vty->node = IUH_NODE; + return CMD_SUCCESS; +} + +static struct cmd_node iucs_node = { + IUCS_NODE, + "%s(config-hnbgw-iucs)# ", + 1, +}; + +DEFUN(cfg_hnbgw_iucs, cfg_hnbgw_iucs_cmd, + "iucs", "Configure IuCS options") +{ + vty->node = IUCS_NODE; + return CMD_SUCCESS; +} + +static struct cmd_node iups_node = { + IUPS_NODE, + "%s(config-hnbgw-iups)# ", + 1, +}; + +DEFUN(cfg_hnbgw_iups, cfg_hnbgw_iups_cmd, + "iups", "Configure IuPS options") +{ + vty->node = IUPS_NODE; + return CMD_SUCCESS; +} + +int hnbgw_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case IUH_NODE: + case IUCS_NODE: + case IUPS_NODE: + vty->node = HNBGW_NODE; + vty->index = NULL; + break; + case HNBGW_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case CONFIG_NODE: + vty->node = ENABLE_NODE; + vty->index = NULL; + break; + default: + osmo_ss7_vty_go_parent(vty); + break; + } + + return vty->node; +} + +DEFUN(show_cnlink, show_cnlink_cmd, "show cnlink", + SHOW_STR "Display information on core network link\n") +{ + struct osmo_ss7_route *rt; + struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(g_hnb_gw->sccp.client); +#define GUARD(STR) \ + STR ? STR : "", \ + STR ? ":" : "" + + vty_out(vty, "IuCS: %s <->", + osmo_sccp_user_name(g_hnb_gw->sccp.cnlink->sccp_user)); + vty_out(vty, " %s%s%s%s", + GUARD(g_hnb_gw->config.iucs_remote_addr_name), + osmo_sccp_inst_addr_name(g_hnb_gw->sccp.client, &g_hnb_gw->sccp.iucs_remote_addr), + VTY_NEWLINE); + + rt = osmo_ss7_route_lookup(ss7, g_hnb_gw->sccp.iucs_remote_addr.pc); + vty_out(vty, " SS7 route: %s%s", osmo_ss7_route_name(rt, true), VTY_NEWLINE); + + vty_out(vty, "IuPS: %s <->", + osmo_sccp_user_name(g_hnb_gw->sccp.cnlink->sccp_user)); + vty_out(vty, " %s%s%s%s", + GUARD(g_hnb_gw->config.iups_remote_addr_name), + osmo_sccp_inst_addr_name(g_hnb_gw->sccp.client, &g_hnb_gw->sccp.iups_remote_addr), + VTY_NEWLINE); + + rt = osmo_ss7_route_lookup(ss7, g_hnb_gw->sccp.iups_remote_addr.pc); + vty_out(vty, " SS7 route: %s%s", osmo_ss7_route_name(rt, true), VTY_NEWLINE); + +#undef GUARD + return CMD_SUCCESS; +} + +static void vty_out_ofd_addr(struct vty *vty, struct osmo_fd *ofd) +{ + char *name; + if (!ofd || ofd->fd < 0 + || !(name = osmo_sock_get_name(vty, ofd->fd))) { + vty_out(vty, "(no addr)"); + return; + } + vty_out(vty, "%s", name); + talloc_free(name); +} + +static void vty_dump_hnb_info__map_states(struct vty *vty, const char *name, unsigned int count, + unsigned int state_count[]) +{ + unsigned int i; + if (!count) + return; + vty_out(vty, " %s: %u contexts:", name, count); + for (i = 0; i <= MAP_S_NUM_STATES; i++) { + if (!state_count[i]) + continue; + vty_out(vty, " %s:%u", hnbgw_context_map_state_name(i), state_count[i]); + } + vty_out(vty, VTY_NEWLINE); +} + +static void vty_dump_hnb_info(struct vty *vty, struct hnb_context *hnb) +{ + struct hnbgw_context_map *map; + unsigned int map_count[2] = {}; + unsigned int state_count[2][MAP_S_NUM_STATES + 1] = {}; + + vty_out(vty, "HNB "); + vty_out_ofd_addr(vty, hnb->conn? osmo_stream_srv_get_ofd(hnb->conn) : NULL); + vty_out(vty, " \"%s\"%s", hnb->identity_info, VTY_NEWLINE); + vty_out(vty, " MCC %u MNC %u LAC %u RAC %u SAC %u CID %u SCTP-stream:HNBAP=%u,RUA=%u%s", + hnb->id.mcc, hnb->id.mnc, hnb->id.lac, hnb->id.rac, hnb->id.sac, hnb->id.cid, + hnb->hnbap_stream, hnb->rua_stream, VTY_NEWLINE); + + llist_for_each_entry(map, &hnb->map_list, hnb_list) { + map_count[map->is_ps? 1 : 0]++; + state_count[map->is_ps? 1 : 0] + [(map->state >= 0 && map->state < MAP_S_NUM_STATES)? + map->state : MAP_S_NUM_STATES]++; + } + vty_dump_hnb_info__map_states(vty, "IuCS", map_count[0], state_count[0]); + vty_dump_hnb_info__map_states(vty, "IuPS", map_count[1], state_count[1]); +} + +static void vty_dump_ue_info(struct vty *vty, struct ue_context *ue) +{ + vty_out(vty, "UE IMSI \"%s\" context ID %u%s", ue->imsi, ue->context_id, VTY_NEWLINE); +} + +DEFUN(show_hnb, show_hnb_cmd, "show hnb all", SHOW_STR "Display information about all HNB") +{ + struct hnb_context *hnb; + unsigned int count = 0; + + if (llist_empty(&g_hnb_gw->hnb_list)) { + vty_out(vty, "No HNB connected%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + + llist_for_each_entry(hnb, &g_hnb_gw->hnb_list, list) { + vty_dump_hnb_info(vty, hnb); + count++; + } + + vty_out(vty, "%u HNB connected%s", count, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(show_one_hnb, show_one_hnb_cmd, "show hnb NAME ", SHOW_STR "Display information about a HNB") +{ + struct hnb_context *hnb; + const char *identity_info = argv[0]; + + if (llist_empty(&g_hnb_gw->hnb_list)) { + vty_out(vty, "No HNB connected%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + + hnb = hnb_context_by_identity_info(g_hnb_gw, identity_info); + if (hnb == NULL) { + vty_out(vty, "No HNB found with identity '%s'%s", identity_info, VTY_NEWLINE); + return CMD_SUCCESS; + } + + vty_dump_hnb_info(vty, hnb); + return CMD_SUCCESS; +} + +DEFUN(show_ue, show_ue_cmd, "show ue all", SHOW_STR "Display information about a UE") +{ + struct ue_context *ue; + + llist_for_each_entry(ue, &g_hnb_gw->ue_list, list) { + vty_dump_ue_info(vty, ue); + } + + return CMD_SUCCESS; +} + +DEFUN(show_talloc, show_talloc_cmd, "show talloc", SHOW_STR "Display talloc info") +{ + talloc_report_full(tall_hnb_ctx, stderr); + talloc_report_full(talloc_asn1_ctx, stderr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_hnbgw_rnc_id, cfg_hnbgw_rnc_id_cmd, + "rnc-id <0-65535>", + "Configure the HNBGW's RNC Id, the common RNC Id used for all connected hNodeB. It is sent to" + " each hNodeB upon HNBAP HNB-Register-Accept, and the hNodeB will subsequently send this as" + " RANAP InitialUE Messages' GlobalRNC-ID IE. Takes effect as soon as the hNodeB re-registers.\n" + "RNC Id value\n") +{ + g_hnb_gw->config.rnc_id = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_hnbgw_iuh_local_ip, cfg_hnbgw_iuh_local_ip_cmd, "local-ip A.B.C.D", + "Accept Iuh connections on local interface\n" + "Local interface IP address (default: " HNBGW_LOCAL_IP_DEFAULT ")") +{ + talloc_free((void*)g_hnb_gw->config.iuh_local_ip); + g_hnb_gw->config.iuh_local_ip = talloc_strdup(tall_hnb_ctx, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_hnbgw_iuh_local_port, cfg_hnbgw_iuh_local_port_cmd, "local-port <1-65535>", + "Accept Iuh connections on local port\n" + "Local interface port (default: 29169)") +{ + g_hnb_gw->config.iuh_local_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_hnbgw_iuh_hnbap_allow_tmsi, cfg_hnbgw_iuh_hnbap_allow_tmsi_cmd, + "hnbap-allow-tmsi (0|1)", + "Allow HNBAP UE Register messages with TMSI or PTMSI identity\n" + "Only accept IMSI identity, reject TMSI or PTMSI\n" + "Accept IMSI, TMSI or PTMSI as UE identity\n") +{ + g_hnb_gw->config.hnbap_allow_tmsi = (*argv[0] == '1'); + return CMD_SUCCESS; +} + +DEFUN(cfg_hnbgw_log_prefix, cfg_hnbgw_log_prefix_cmd, + "log-prefix (hnb-id|umts-cell-id)", + "Configure the log message prefix\n" + "Use the hNB-ID as log message prefix\n" + "Use the UMTS Cell ID as log message prefix\n") +{ + if (!strcmp(argv[0], "hnb-id")) + g_hnb_gw->config.log_prefix_hnb_id = true; + else + g_hnb_gw->config.log_prefix_hnb_id = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_hnbgw_iucs_remote_addr, + cfg_hnbgw_iucs_remote_addr_cmd, + "remote-addr NAME", + "SCCP address to send IuCS to (MSC)\n" + "SCCP address book entry name (see 'cs7-instance')\n") +{ + g_hnb_gw->config.iucs_remote_addr_name = talloc_strdup(g_hnb_gw, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_hnbgw_iups_remote_addr, + cfg_hnbgw_iups_remote_addr_cmd, + "remote-addr NAME", + "SCCP address to send IuPS to (SGSN)\n" + "SCCP address book entry name (see 'cs7-instance')\n") +{ + g_hnb_gw->config.iups_remote_addr_name = talloc_strdup(g_hnb_gw, argv[0]); + return CMD_SUCCESS; +} + +static int config_write_hnbgw(struct vty *vty) +{ + vty_out(vty, "hnbgw%s", VTY_NEWLINE); + vty_out(vty, " log-prefix %s%s", g_hnb_gw->config.log_prefix_hnb_id ? "hnb-id" : "umts-cell-id", + VTY_NEWLINE); + return CMD_SUCCESS; +} + +static int config_write_hnbgw_iuh(struct vty *vty) +{ + const char *addr; + uint16_t port; + + vty_out(vty, " iuh%s", VTY_NEWLINE); + + addr = g_hnb_gw->config.iuh_local_ip; + if (addr && (strcmp(addr, HNBGW_LOCAL_IP_DEFAULT) != 0)) + vty_out(vty, " local-ip %s%s", addr, VTY_NEWLINE); + + port = g_hnb_gw->config.iuh_local_port; + if (port && port != IUH_DEFAULT_SCTP_PORT) + vty_out(vty, " local-port %u%s", port, VTY_NEWLINE); + + if (g_hnb_gw->config.hnbap_allow_tmsi) + vty_out(vty, " hnbap-allow-tmsi 1%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +static int config_write_hnbgw_iucs(struct vty *vty) +{ + if (!g_hnb_gw->config.iucs_remote_addr_name) + return CMD_SUCCESS; + + vty_out(vty, " iucs%s", VTY_NEWLINE); + vty_out(vty, " remote-addr %s%s", g_hnb_gw->config.iucs_remote_addr_name, + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +static int config_write_hnbgw_iups(struct vty *vty) +{ + if (!g_hnb_gw->config.iups_remote_addr_name) + return CMD_SUCCESS; + + vty_out(vty, " iups%s", VTY_NEWLINE); + vty_out(vty, " remote-addr %s%s", g_hnb_gw->config.iups_remote_addr_name, + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx) +{ + g_hnb_gw = gw; + tall_hnb_ctx = tall_ctx; + + install_element(CONFIG_NODE, &cfg_hnbgw_cmd); + install_node(&hnbgw_node, config_write_hnbgw); + + install_element(HNBGW_NODE, &cfg_hnbgw_rnc_id_cmd); + install_element(HNBGW_NODE, &cfg_hnbgw_log_prefix_cmd); + + install_element(HNBGW_NODE, &cfg_hnbgw_iuh_cmd); + install_node(&iuh_node, config_write_hnbgw_iuh); + + install_element(IUH_NODE, &cfg_hnbgw_iuh_local_ip_cmd); + install_element(IUH_NODE, &cfg_hnbgw_iuh_local_port_cmd); + install_element(IUH_NODE, &cfg_hnbgw_iuh_hnbap_allow_tmsi_cmd); + + install_element(HNBGW_NODE, &cfg_hnbgw_iucs_cmd); + install_node(&iucs_node, config_write_hnbgw_iucs); + + install_element(IUCS_NODE, &cfg_hnbgw_iucs_remote_addr_cmd); + + install_element(HNBGW_NODE, &cfg_hnbgw_iups_cmd); + install_node(&iups_node, config_write_hnbgw_iups); + + install_element(IUPS_NODE, &cfg_hnbgw_iups_remote_addr_cmd); + + install_element_ve(&show_cnlink_cmd); + install_element_ve(&show_hnb_cmd); + install_element_ve(&show_one_hnb_cmd); + install_element_ve(&show_ue_cmd); + install_element_ve(&show_talloc_cmd); +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..899e436 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,77 @@ +SUBDIRS = \ + $(NULL) + +# The `:;' works around a Bash 3.2 bug when the output is not writeable. +$(srcdir)/package.m4: $(top_srcdir)/configure.ac + :;{ \ + echo '# Signature of the current package.' && \ + echo 'm4_define([AT_PACKAGE_NAME],' && \ + echo ' [$(PACKAGE_NAME)])' && \ + echo 'm4_define([AT_PACKAGE_TARNAME],' && \ + echo ' [$(PACKAGE_TARNAME)])' && \ + echo 'm4_define([AT_PACKAGE_VERSION],' && \ + echo ' [$(PACKAGE_VERSION)])' && \ + echo 'm4_define([AT_PACKAGE_STRING],' && \ + echo ' [$(PACKAGE_STRING)])' && \ + echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \ + echo ' [$(PACKAGE_BUGREPORT)])'; \ + echo 'm4_define([AT_PACKAGE_URL],' && \ + echo ' [$(PACKAGE_URL)])'; \ + } >'$(srcdir)/package.m4' + +EXTRA_DIST = \ + testsuite.at \ + $(srcdir)/package.m4 \ + $(TESTSUITE) \ + ctrl_test_runner.py \ + osmo-hnbgw.vty \ + $(NULL) + +TESTSUITE = $(srcdir)/testsuite + +DISTCLEANFILES = \ + atconfig \ + $(NULL) + +if ENABLE_EXT_TESTS +python-tests: $(BUILT_SOURCES) + echo "" +# TODO: Enable once we have a VTY/CTRL interface: +# $(MAKE) vty-test +# osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v +# osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v +# $(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v +else +python-tests: $(BUILT_SOURCES) + echo "Not running python-based tests (determined at configure-time)" +endif + +# Run a specific test with: 'make vty-test VTY_TEST=osmo-hnbgw.vty' +VTY_TEST ?= *.vty + +# To update the VTY script from current application behavior, +# pass -u to vty_script_runner.py by doing: +# make vty-test U=-u +vty-test: + osmo_verify_transcript_vty.py -v \ + -n OsmoHNBGW -p 4261 \ + -r "$(top_builddir)/src/osmo-hnbgw/osmo-hnbgw -c $(top_srcdir)/doc/examples/osmo-hnbgw/osmo-hnbgw.cfg" \ + $(U) $(srcdir)/$(VTY_TEST) + +check-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) + $(MAKE) $(AM_MAKEFLAGS) python-tests + +installcheck-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \ + $(TESTSUITEFLAGS) + +clean-local: + test ! -f '$(TESTSUITE)' || \ + $(SHELL) '$(TESTSUITE)' --clean + +AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te +AUTOTEST = $(AUTOM4TE) --language=autotest +$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4 + $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at + mv $@.tmp $@ diff --git a/tests/atlocal.in b/tests/atlocal.in new file mode 100644 index 0000000..e69de29 diff --git a/tests/ctrl_test_runner.py b/tests/ctrl_test_runner.py new file mode 100755 index 0000000..701bd89 --- /dev/null +++ b/tests/ctrl_test_runner.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 + +# (C) 2013 by Jacob Erlbeck +# (C) 2014 by Holger Hans Peter Freyther +# based on vty_test_runner.py: +# (C) 2013 by Katerina Barone-Adesi +# (C) 2013 by Holger Hans Peter Freyther +# based on bsc_control.py. + +# 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 . + +import os +import time +import unittest +import socket +import sys +import struct + +import osmopy.obscvty as obscvty +import osmopy.osmoutil as osmoutil +from osmopy.osmo_ipa import Ctrl, IPA + +# to be able to find $top_srcdir/doc/... +confpath = os.path.join(sys.path[0], '..') +verbose = False + +class TestCtrlBase(unittest.TestCase): + + def ctrl_command(self): + raise Exception("Needs to be implemented by a subclass") + + def ctrl_app(self): + raise Exception("Needs to be implemented by a subclass") + + def setUp(self): + osmo_ctrl_cmd = self.ctrl_command()[:] + config_index = osmo_ctrl_cmd.index('-c') + if config_index: + cfi = config_index + 1 + osmo_ctrl_cmd[cfi] = os.path.join(confpath, osmo_ctrl_cmd[cfi]) + + try: + self.proc = osmoutil.popen_devnull(osmo_ctrl_cmd) + except OSError: + print("Current directory: %s" % os.getcwd(), file=sys.stderr) + print("Consider setting -b", file=sys.stderr) + + appstring = self.ctrl_app()[2] + appport = self.ctrl_app()[0] + self.connect("127.0.0.1", appport) + self.next_id = 1000 + + def tearDown(self): + self.disconnect() + osmoutil.end_proc(self.proc) + + def disconnect(self): + if not (self.sock is None): + self.sock.close() + + def connect(self, host, port): + if verbose: + print("Connecting to host %s:%i" % (host, port)) + + retries = 30 + while True: + try: + sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sck.setblocking(1) + sck.connect((host, port)) + except IOError: + retries -= 1 + if retries <= 0: + raise + time.sleep(.1) + continue + break + self.sock = sck + return sck + + def send(self, data): + if verbose: + print("Sending \"%s\"" %(data)) + data = Ctrl().add_header(data) + return self.sock.send(data) == len(data) + + def send_set(self, var, value, id): + setmsg = "SET %s %s %s" %(id, var, value) + return self.send(setmsg) + + def send_get(self, var, id): + getmsg = "GET %s %s" %(id, var) + return self.send(getmsg) + + def do_set(self, var, value): + id = self.next_id + self.next_id += 1 + self.send_set(var, value, id) + return self.recv_msgs()[id] + + def do_get(self, var): + id = self.next_id + self.next_id += 1 + self.send_get(var, id) + return self.recv_msgs()[id] + + def recv_msgs(self): + responses = {} + data = self.sock.recv(4096) + while (len(data)>0): + (head, data) = IPA().split_combined(data) + answer = Ctrl().rem_header(head).decode() + if verbose: + print("Got message:", answer) + (mtype, id, msg) = answer.split(None, 2) + id = int(id) + rsp = {'mtype': mtype, 'id': id} + if mtype == "ERROR": + rsp['error'] = msg + else: + split = msg.split(None, 1) + rsp['var'] = split[0] + if len(split) > 1: + rsp['value'] = split[1] + else: + rsp['value'] = None + responses[id] = rsp + + if verbose: + print("Decoded replies: ", responses) + + return responses + + +class TestCtrlHNB(TestCtrlBase): + + def tearDown(self): + TestCtrlBase.tearDown(self) + os.unlink("tmp_dummy_sock") + + def ctrl_command(self): + return ["./src/osmo-hnbgw/osmo-hnbgw", "-r", "tmp_dummy_sock", "-c", + "doc/examples/osmo-hnbgw/osmo-hnbgw.cfg"] + + def ctrl_app(self): + return (4249, "./src/osmo-hnbgw/osmo-hnbgw", "OsmoHNBGW", "hnb") + + def testCtrlErrs(self): + r = self.do_get('invalid') + self.assertEqual(r['mtype'], 'ERROR') + self.assertEqual(r['error'], 'Command not found') + + r = self.do_get('hnbgw.999') + self.assertEqual(r['mtype'], 'ERROR') + self.assertEqual(r['error'], 'Error while resolving object') + +def add_hnbgw_test(suite, workdir, klass): + if not os.path.isfile(os.path.join(workdir, "src/osmo-hnbgw/osmo-hnbgw")): + print("Skipping the HNBGW test") + return + test = unittest.TestLoader().loadTestsFromTestCase(klass) + suite.addTest(test) + +if __name__ == '__main__': + import argparse + import sys + + workdir = '.' + + parser = argparse.ArgumentParser() + parser.add_argument("-v", "--verbose", dest="verbose", + action="store_true", help="verbose mode") + parser.add_argument("-p", "--pythonconfpath", dest="p", + help="searchpath for config") + parser.add_argument("-w", "--workdir", dest="w", + help="Working directory") + args = parser.parse_args() + + verbose_level = 1 + if args.verbose: + verbose_level = 2 + verbose = True + + if args.w: + workdir = args.w + + if args.p: + confpath = args.p + + print("confpath %s, workdir %s" % (confpath, workdir)) + os.chdir(workdir) + print("Running tests for specific control commands") + suite = unittest.TestSuite() + add_hnbgw_test(suite, workdir, TestCtrlHNB) + res = unittest.TextTestRunner(verbosity=verbose_level).run(suite) + sys.exit(len(res.errors) + len(res.failures)) diff --git a/tests/osmo-hnbgw.vty b/tests/osmo-hnbgw.vty new file mode 100644 index 0000000..b8d562c --- /dev/null +++ b/tests/osmo-hnbgw.vty @@ -0,0 +1,2 @@ +OsmoHNBGW> enable + diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..65d1ca0 --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,8 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +#AT_SETUP([foobar]) +#AT_KEYWORDS([foobar]) +#cat $abs_srcdir/foobar/foobar_test.ok > expout +#AT_CHECK([$abs_top_builddir/tests/foobar/foobar_test], [], [expout], [ignore]) +#AT_CLEANUP