pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/upf-benchmark/+/38327?usp=email )
Change subject: Initial version of improved osmo-pfcp-tool from osmo-upf.git. ......................................................................
Initial version of improved osmo-pfcp-tool from osmo-upf.git.
Import version of osmo-pfcp-tool initially developed in master branch of osmo-upf.git (6859de09d2ae0e363070e152489d9f8579085aa8), and later on improved on a WIP branch there for a while.
Related: SYS#7096 Change-Id: I179d575f53ef5797f296e13cd4d52a043fc4c1c1 --- A .gitignore A .gitreview A AUTHORS A COPYING A Makefile.am A README.md A TODO-RELEASE A configure.ac A contrib/jenkins.sh A debian/changelog A debian/compat A debian/control A debian/copyright A debian/postinst A debian/rules A debian/source/format A debian/upf-benchmark.install A doc/Makefile.am A doc/manuals/Makefile.am A doc/manuals/chapters/overview.adoc A doc/manuals/upfbenchmark-usermanual-docinfo.xml A doc/manuals/upfbenchmark-usermanual.adoc A git-version-gen A include/Makefile.am A include/osmocom/Makefile.am A include/osmocom/pfcptool/Makefile.am A include/osmocom/pfcptool/checksum.h A include/osmocom/pfcptool/gtp_flood.h A include/osmocom/pfcptool/pfcp_tool.h A include/osmocom/pfcptool/range.h A src/Makefile.am A src/osmo-pfcp-tool/Makefile.am A src/osmo-pfcp-tool/checksum.c A src/osmo-pfcp-tool/gtp_flood.c A src/osmo-pfcp-tool/osmo_pfcp_tool_main.c A src/osmo-pfcp-tool/pfcp_tool.c A src/osmo-pfcp-tool/pfcp_tool_vty.c A src/osmo-pfcp-tool/range.c 38 files changed, 5,305 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/upf-benchmark refs/changes/27/38327/1
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af49656 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +debian/*.log +*.o +*.lo +*.a +.deps +Makefile +Makefile.in +config.h +config.h.in +*.pc +*~ +*.png + +*.*~ +*.sw? +.libs +*.pyc +*.gcda +*.gcno + +.vscode/ +**/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 +upf-benchmark-*.tar.bz2 +upf-benchmark-*.tar.gz + +tags +/deps + +src/osmo-upf/osmo-upf + +#tests +tests/testsuite.dir +tests/*/*_test + +tests/atconfig +tests/atlocal +tests/package.m4 +tests/testsuite +tests/testsuite.log + diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..8bd7063 --- /dev/null +++ b/.gitreview @@ -0,0 +1,3 @@ +[gerrit] +host=gerrit.osmocom.org +project=upf-benchmark diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..cd0a623 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Neels Hofmeyr nhofmeyr@sysmocom.de +Pau Espin Pedrol pespin@sysmocom.de 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. http://fsf.org/ + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +http://www.gnu.org/licenses/. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..8d0c448 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,28 @@ +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 \ + doc \ + $(NULL) + +BUILT_SOURCES = $(top_srcdir)/.version +EXTRA_DIST = \ + .version \ + debian \ + git-version-gen \ + $(NULL) +@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..98588aa --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +upf-bencharmk - Tools and configs to set up UPF benchmarking +============================================================ + +Homepage +-------- + +The official homepage of the project is +https://osmocom.org/ + +GIT Repository +-------------- + +You can clone from the official upf-benchmark.git repository using + + git clone https://gitea.osmocom.org/cellular-infrastructure/upf-benchmark + +There is a web interface at https://gitea.osmocom.org/cellular-infrastructure/upf-benchmark. + +To submit patches, see "Contributing" below. + +Documentation +------------- + +User Manual 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/upfbenchmark-usermanual.pdf) + + +Mailing List +------------ + +Discussions related to osmo-bts are happening on the +osmocom-net-gprs@lists.osmocom.org mailing list, please see +https://lists.osmocom.org/postorius/lists/osmocom-net-gprs@lists.osmocom.org... +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 + +Submit patches at https://gerrit.osmocom.org/ +See also https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit + +The current patch queue for OsmoUPF can be seen at +https://gerrit.osmocom.org/#/q/project:upf-benchmark+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.... +# 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..a10b07c --- /dev/null +++ b/configure.ac @@ -0,0 +1,171 @@ +AC_INIT([upf-benchmark], + m4_esyscmd([./git-version-gen .tarball-version]), + [osmocom-net-gprs@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 checks for libraries +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(LIBOSMOPFCP, libosmo-pfcp >= 0.1.0) +PKG_CHECK_MODULES(LIBGTPNL, libgtpnl >= 1.2.0) +PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2) + +AC_ARG_ENABLE([uring], [AS_HELP_STRING([--disable-uring], [Build without io_uring support])], + [ + ENABLE_URING=$enableval + ], + [ + ENABLE_URING="yes" + ]) +AS_IF([test "x$ENABLE_URING" = "xyes"], [ + PKG_CHECK_MODULES(LIBURING, [liburing >= 0.7]) + AC_DEFINE([HAVE_URING],[1],[Build with io_uring support for GTP flood commands]) +]) +AM_CONDITIONAL(ENABLE_URING, test "x$ENABLE_URING" = "xyes") +AC_SUBST(ENABLE_URING) + +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) + +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 + +# 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 + +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/pfcptool/Makefile + src/Makefile + src/osmo-pfcp-tool/Makefile + doc/Makefile + doc/manuals/Makefile + Makefile) diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh new file mode 100755 index 0000000..74d15eb --- /dev/null +++ b/contrib/jenkins.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# jenkins build helper script for upf-benchmark. 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-pfcp +osmo-build-dep.sh libgtpnl + +# build libnftnl and libnftables from git.netfilter.org +build_from_netfilter() { +### TODO: enable osmo-build-dep.sh to build from git.netfilter.org URL? + project="$1" + set +x + echo + echo + echo + echo " =============================== $project ===============================" + echo + set -x + if [ -d "./$project" ]; then + rm -rf "./$project" + fi + git clone "git://git.netfilter.org/$project" "$project" + cd "$project" + autoreconf --install --force + ./configure \ + --prefix="$inst/stow/$project" \ + --without-cli \ + --disable-man-doc \ + --enable-python=no + $MAKE $PARALLEL_MAKE install + STOW_DIR="$inst/stow" stow --restow $project +} +build_from_netfilter libnftnl +build_from_netfilter nftables + +# Additional configure options and depends +CONFIG="" +if [ "$WITH_MANUALS" = "1" ]; then + CONFIG="--enable-manuals" +fi + +set +x +echo +echo +echo +echo " =============================== upf-benchmark ===============================" +echo +set -x + +cd "$base" +autoreconf --install --force +./configure --enable-sanitize $CONFIG +$MAKE $PARALLEL_MAKE +LD_LIBRARY_PATH="$inst/lib" $MAKE check \ + || 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/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..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..d5f2e80 --- /dev/null +++ b/debian/control @@ -0,0 +1,47 @@ +Source: upf-benchmark +Section: net +Priority: extra +Maintainer: Osmocom team openbsc@lists.osmocom.org +# liburing-dev: don't try to install it on debian 10 and ubuntu 20.04 +Build-Depends: debhelper (>= 10), + dh-autoreconf, + autotools-dev, + autoconf, + autoconf-archive, + automake, + libtool, + pkg-config, + python3-minimal, + libtalloc-dev, + libgtpnl-dev (>= 1.2.0), + libnftables-dev (>= 1.0.2), + libosmocore-dev (>= 1.6.0), + libosmo-pfcp-dev (>= 0.1.0), + osmo-gsm-manuals-dev (>= 1.2.0), + liburing-dev | base-files (<< 11) | ubuntu-keyring (<< 2021), +Standards-Version: 3.9.8 +Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/upf-benchmark +Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/upf-benchmark +Homepage: https://projects.osmocom.org/projects/upf-benchmark + +Package: upf-benchmark +Architecture: any +Multi-Arch: foreign +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: OsmoUPF: Osmocom User Plane Function + +Package: upf-benchmark-dbg +Section: debug +Architecture: any +Multi-Arch: same +Depends: upf-benchmark (= ${binary:Version}), ${misc:Depends} +Description: OsmoUPF: Osmocom User Plane Function + +Package: upf-benchmark-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..d7b2fab --- /dev/null +++ b/debian/copyright @@ -0,0 +1,19 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: upf-benchmark +Source: https://gitea.osmocom.org/cellular-infrastructure/osmo-upf + +Files: * +Copyright: 2024 sysmocom - s.f.m.c. GmbH info@sysmocom.de +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 http://www.gnu.org/licenses/. diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000..d4857a2 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,22 @@ +#!/bin/sh -e +case "$1" in + configure) + # Create the osmocom group and user (if it doesn't exist yet) + if ! getent group osmocom >/dev/null; then + groupadd --system osmocom + fi + if ! getent passwd osmocom >/dev/null; then + useradd \ + --system \ + --gid osmocom \ + --home-dir /var/lib/osmocom \ + --shell /sbin/nologin \ + --comment "Open Source Mobile Communications" \ + osmocom + fi + ;; +esac + +# dh_installdeb(1) will replace this with shell code automatically +# generated by other debhelper scripts. +#DEBHELPER# diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..32a80bd --- /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 += --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-practice... +override_dh_strip: + dh_strip -pupf-benchmark --dbg-package=upf-benchmark-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/debian/upf-benchmark.install b/debian/upf-benchmark.install new file mode 100644 index 0000000..6653611 --- /dev/null +++ b/debian/upf-benchmark.install @@ -0,0 +1 @@ +usr/bin/osmo-pfcp-tool diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..2232154 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + manuals \ + $(NULL) diff --git a/doc/manuals/Makefile.am b/doc/manuals/Makefile.am new file mode 100644 index 0000000..3ed2e7f --- /dev/null +++ b/doc/manuals/Makefile.am @@ -0,0 +1,13 @@ +EXTRA_DIST = \ + upfbenchmark-usermanual.adoc \ + upfbenchmark-usermanual-docinfo.xml \ + chapters + +if BUILD_MANUALS + ASCIIDOC = upfbenchmark-usermanual.adoc + include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.asciidoc.inc + upfbenchmark-usermanual.pdf: $(srcdir)/chapters/*.adoc + + OSMO_REPOSITORY = upf-benchmark + 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..157c6dc --- /dev/null +++ b/doc/manuals/chapters/overview.adoc @@ -0,0 +1,12 @@ +OsmoUPF + +[[overview]] +== Overview + +This manual should help you getting started with the several tools and +testsuites available in up-benchmark. + +[[intro_overview]] +=== About upf-benchmark + + TODO \ No newline at end of file diff --git a/doc/manuals/upfbenchmark-usermanual-docinfo.xml b/doc/manuals/upfbenchmark-usermanual-docinfo.xml new file mode 100644 index 0000000..ae0d2db --- /dev/null +++ b/doc/manuals/upfbenchmark-usermanual-docinfo.xml @@ -0,0 +1,47 @@ +<revhistory> + <revision> + <revnumber>1</revnumber> + <date>December 2021</date> + <authorinitials>PEP</authorinitials> + <revremark> + Initial upf-benchmark manual + </revremark> + </revision> +</revhistory> + +<authorgroup> + <author> + <firstname>Pau</firstname> + <surname>Espin Pedrol</surname> + <email>pespin@sysmocom.de</email> + <authorinitials>PEP</authorinitials> + <affiliation> + <shortaffil>sysmocom</shortaffil> + <orgname>sysmocom - s.f.m.c. GmbH</orgname> + <jobtitle>Software Developer</jobtitle> + </affiliation> + </author> +</authorgroup> + +<copyright> + <year>2024</year> + <holder>sysmocom - s.f.m.c. GmbH</holder> +</copyright> + +<legalnotice> + <para> + 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". + </para> + <para> + The Asciidoc source code of this manual can be found at + <ulink url="http://git.osmocom.org/osmo-gsm-manuals/"> + http://git.osmocom.org/osmo-gsm-manuals/ + </ulink> + </para> +</legalnotice> diff --git a/doc/manuals/upfbenchmark-usermanual.adoc b/doc/manuals/upfbenchmark-usermanual.adoc new file mode 100644 index 0000000..eace4dd --- /dev/null +++ b/doc/manuals/upfbenchmark-usermanual.adoc @@ -0,0 +1,28 @@ +:gfdl-enabled: +:program-name: OsmoUPF + +OsmoUPF User Manual +=================== +Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + +include::./common/chapters/preface.adoc[] + +include::{srcdir}/chapters/overview.adoc[] + +include::./common/chapters/vty.adoc[] + +include::./common/chapters/logging.adoc[] + +include::./common/chapters/counters-overview.adoc[] + +include::./common/chapters/control_if.adoc[] + +include::./common/chapters/vty_cpu_sched.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/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 http://www.gnu.org/licenses/. + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 $srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + 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..1e3c7ce --- /dev/null +++ b/include/osmocom/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + pfcptool \ + $(NULL) diff --git a/include/osmocom/pfcptool/Makefile.am b/include/osmocom/pfcptool/Makefile.am new file mode 100644 index 0000000..7778630 --- /dev/null +++ b/include/osmocom/pfcptool/Makefile.am @@ -0,0 +1,6 @@ +noinst_HEADERS = \ + checksum.h \ + gtp_flood.h \ + pfcp_tool.h \ + range.h \ + $(NULL) diff --git a/include/osmocom/pfcptool/checksum.h b/include/osmocom/pfcptool/checksum.h new file mode 100644 index 0000000..4b22431 --- /dev/null +++ b/include/osmocom/pfcptool/checksum.h @@ -0,0 +1,13 @@ +#pragma once +#include <stdint.h> +#include <netinet/in.h> + +uint16_t ip_fast_csum(const void *iph, unsigned int ihl); +uint32_t csum_partial(const void *buff, int len, uint32_t wsum); +uint16_t ip_compute_csum(const void *buff, int len); + +uint16_t csum_ipv6_magic(const struct in6_addr *saddr, + const struct in6_addr *daddr, + uint32_t len, uint8_t proto, uint32_t csum); + +uint16_t csum_fold(uint32_t csum); diff --git a/include/osmocom/pfcptool/gtp_flood.h b/include/osmocom/pfcptool/gtp_flood.h new file mode 100644 index 0000000..ddfa345 --- /dev/null +++ b/include/osmocom/pfcptool/gtp_flood.h @@ -0,0 +1,59 @@ +#pragma once + +#include <osmocom/core/socket.h> + +/* According to 3GPP TS 29.060. */ +struct gtp1u_hdr { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t pn:1, s:1, e:1, spare:1, pt:1, version:3; +#else + uint8_t version:3, pt:1, spare:1, e:1, s:1, pn:1; +#endif + uint8_t type; + uint16_t length; + uint32_t tei; +}; + +struct gtp_flood; +struct udp_port; + +struct gtp_flood_cfg { + unsigned int num_rx_workers; + unsigned int num_tx_workers; + unsigned int queue_size; + unsigned int slew_us; +}; +struct gtp_flood *gtp_flood_alloc(void *ctx, const struct gtp_flood_cfg *cfg); + +/* information passed on within generated GTP payload. Main purpose is to allow echoing payloads back into a GTP tunnel + * by osmo-udp-responder, which requires knowledge of the counterpart TEID. + * (future: add in-band instructions for the responder to shape traffic in certain ways: multiple echos or modified + * packet size...) + */ +struct gtp_flood_payload_info { + char mark[4]; + /* ordered exactly as it should be returned in a GTP header (network byte order) */ + uint32_t return_teid; +} __attribute__((packed)); + +struct gtp_flood_flow_cfg { + bool rx; + + struct udp_port *gtp_local; + + /* below used only for rx == false */ + struct osmo_sockaddr gtp_remote; + uint32_t gtp_remote_teid; + struct osmo_sockaddr payload_src; + struct osmo_sockaddr payload_dst; + unsigned int num_packets; + + bool append_payload_info; + struct gtp_flood_payload_info payload_info; +}; + +void gtp_flood_add_flow(struct gtp_flood *gtp_flood, + const struct gtp_flood_flow_cfg *flow_cfg); + +void gtp_flood_start(struct gtp_flood *gtp_flood); +bool gtp_flood_is_busy(struct gtp_flood *gtp_flood); diff --git a/include/osmocom/pfcptool/pfcp_tool.h b/include/osmocom/pfcptool/pfcp_tool.h new file mode 100644 index 0000000..699fd87 --- /dev/null +++ b/include/osmocom/pfcptool/pfcp_tool.h @@ -0,0 +1,205 @@ +/* Global definitions for osmo-pfcp-tool */ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/core/select.h> +#include <osmocom/vty/command.h> + +#include <osmocom/pfcp/pfcp_msg.h> + +#include <osmocom/pfcptool/range.h> +#include <osmocom/pfcptool/gtp_flood.h> + +struct osmo_tdef; +struct ctrl_handle; + +enum up_gtp_action_kind { + UP_GTP_DROP, + UP_GTP_U_TUNEND, + UP_GTP_U_TUNMAP, +}; + +enum pfcp_tool_vty_node { + PEER_NODE = _LAST_OSMOVTY_NODE + 1, + SESSION_NODE, + GTP_FLOOD_NODE, +}; + +extern struct osmo_tdef g_pfcp_tool_tdefs[]; +extern struct osmo_tdef_group g_pfcp_tool_tdef_groups[]; + +struct pfcp_tool_peer { + struct llist_head entry; + + struct osmo_sockaddr remote_addr; + struct osmo_pfcp_msg last_req; + struct osmo_pfcp_msg last_resp; + + uint64_t next_seid_state; + + struct llist_head sessions; +}; + +struct pfcp_tool_gtp_tun_ep { + struct osmo_sockaddr_str addr; + uint32_t teid; +}; + +struct pfcp_tool_gtp_tun { + struct pfcp_tool_gtp_tun_ep local; + struct pfcp_tool_gtp_tun_ep remote; +}; + +struct pfcp_tool_tunend { + struct pfcp_tool_gtp_tun access; + struct { + struct osmo_sockaddr_str ue_local_addr; + } core; +}; + +struct pfcp_tool_tunmap { + struct pfcp_tool_gtp_tun access; + struct pfcp_tool_gtp_tun core; +}; + +struct pfcp_tool_session { + struct llist_head entry; + + struct pfcp_tool_peer *peer; + uint64_t cp_seid; + struct osmo_pfcp_ie_f_seid up_f_seid; + + enum up_gtp_action_kind kind; + union { + /* En-/De-capsulate GTP: add/remove a GTP header and forward the GTP payload from/to plain IP. */ + struct pfcp_tool_tunend tunend; + + /* Tunnel-map GTP: translate from one TEID to another and forward */ + struct pfcp_tool_tunmap tunmap; + }; +}; + +struct udp_port { + struct llist_head entry; + + /* IP address and UDP port from user input */ + struct osmo_sockaddr osa; + + /* In case this is a locally bound port, this is the fd for the socket. */ + struct osmo_fd ofd; +}; + +struct g_pfcp_tool { + struct ctrl_handle *ctrl; + + struct { + char *local_ip; + uint16_t local_port; + } vty_cfg; + + struct osmo_pfcp_endpoint *ep; + struct llist_head peers; + + uint32_t next_teid_state; + + struct { + struct range ip_range; + } ue; + + struct { + /* list of struct udp_port */ + struct llist_head gtp_local_addrs; + struct udp_port *gtp_local_addrs_next; + + /* list of struct udp_port */ + struct llist_head gtp_core_addrs; + void *gtp_core_addrs_next; + + struct { + struct { + /* source address is always the UE IP address */ + struct range udp_port_range; + } source; + struct { + struct range ip_range; + struct range udp_port_range; + } target; + bool append_info; + } payload; + + struct { + struct gtp_flood_cfg cfg; + + unsigned int flows_per_session; + unsigned int packets_per_flow; + + struct gtp_flood *state; + } flood; + } gtp; +}; + +extern struct g_pfcp_tool *g_pfcp_tool; + +void g_pfcp_tool_alloc(void *ctx); +void pfcp_tool_vty_init_cfg(); +void pfcp_tool_vty_init_cmds(); + +int pfcp_tool_mainloop(int poll); + +struct pfcp_tool_peer *pfcp_tool_peer_find_or_create(const struct osmo_sockaddr *remote_addr); +struct pfcp_tool_session *pfcp_tool_session_find_or_create(struct pfcp_tool_peer *peer, uint64_t cp_seid, + enum up_gtp_action_kind kind); +void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req); + +int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m); +uint64_t peer_new_seid(struct pfcp_tool_peer *peer); + +uint32_t pfcp_tool_new_teid(void); +int pfcp_tool_next_ue_addr(struct osmo_sockaddr *dst); + +struct udp_port *pfcp_tool_have_local_udp_port_by_str(const struct osmo_sockaddr_str *addr, uint16_t fallback_port); +struct udp_port *pfcp_tool_have_core_udp_port_by_str(const struct osmo_sockaddr_str *addr, uint16_t fallback_port); + +void pfcp_tool_gtp_flood_start(void); + +int pfcp_tool_vty_go_parent(struct vty *vty); + +static inline struct llist_head *_llist_round_robin(struct llist_head *list, void **state) +{ + struct llist_head *e = *state; + if (!e || e->next == list) + e = list; + e = e->next; + if (e == list) + e = NULL; + *state = e; + return e; +} +#define llist_round_robin(LIST, STATE, STRUCT, ENTRY_NAME) \ + llist_entry(_llist_round_robin(LIST, STATE), STRUCT, ENTRY_NAME) diff --git a/include/osmocom/pfcptool/range.h b/include/osmocom/pfcptool/range.h new file mode 100644 index 0000000..5191a67 --- /dev/null +++ b/include/osmocom/pfcptool/range.h @@ -0,0 +1,38 @@ +#pragma once + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +struct osmo_sockaddr; + +/* A value large enough for an IPv6 address (128bit) */ +struct range_val { + uint64_t buf[2]; + size_t size; +}; + +/* Manage stepping through a defined range of values large enough to handle IPv6 addresses. */ +struct range { + struct range_val first; + struct range_val last; + struct range_val next; + bool wrapped; +}; + +/* struct range r = {}; + * range_val_set_int(&r.first, 23); + * range_val_set_int(&r.last, 42); + * while (1) { + * range_next(); + * unsigned int val = range_val_get_int(&r.next); + * printf("%u\n", val); + * } + */ +void range_next(struct range *r); +void range_val_set_int(struct range_val *rv, uint32_t val); +uint32_t range_val_get_int(const struct range_val *rv); +void range_val_set_addr(struct range_val *rv, const struct osmo_sockaddr *val); +void range_val_get_addr(struct osmo_sockaddr *dst, const struct range_val *rv); +void range_val_inc(struct range_val *rv); +int range_val_cmp(const struct range_val *a, const struct range_val *b); diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..fecf898 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + osmo-pfcp-tool \ + $(NULL) diff --git a/src/osmo-pfcp-tool/Makefile.am b/src/osmo-pfcp-tool/Makefile.am new file mode 100644 index 0000000..ea16f72 --- /dev/null +++ b/src/osmo-pfcp-tool/Makefile.am @@ -0,0 +1,38 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOPFCP_CFLAGS) \ + $(LIBURING_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOPFCP_LIBS) \ + $(LIBURING_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-pfcp-tool \ + $(NULL) + +osmo_pfcp_tool_SOURCES = \ + checksum.c \ + gtp_flood.c \ + osmo_pfcp_tool_main.c \ + pfcp_tool.c \ + pfcp_tool_vty.c \ + range.c \ + $(NULL) diff --git a/src/osmo-pfcp-tool/checksum.c b/src/osmo-pfcp-tool/checksum.c new file mode 100644 index 0000000..c6abe27 --- /dev/null +++ b/src/osmo-pfcp-tool/checksum.c @@ -0,0 +1,211 @@ +/* + * + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * IP/TCP/UDP checksumming routines + * + * Authors: Jorge Cwik, jorge@laser.satlink.net + * Arnt Gulbrandsen, agulbra@nvg.unit.no + * Tom May, ftom@netcom.com + * Andreas Schwab, schwab@issan.informatik.uni-dortmund.de + * Lots of code moved from tcp.c and ip.c; see those files + * for more names. + * + * 03/02/96 Jes Sorensen, Andreas Schwab, Roman Hodek: + * Fixed some nasty bugs, causing some horrible crashes. + * A: At some points, the sum (%0) was used as + * length-counter instead of the length counter + * (%1). Thanks to Roman Hodek for pointing this out. + * B: GCC seems to mess up if one uses too many + * data-registers to hold input values and one tries to + * specify d0 and d1 as scratch registers. Letting gcc + * choose these registers itself solves the problem. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* Revised by Kenneth Albanowski for m68knommu. Basic problem: unaligned access + kills, so most of the assembly has to go. */ + +#if defined(__FreeBSD__) +#define _KERNEL /* needed on FreeBSD 10.x for s6_addr32 */ +#include <sys/types.h> +#include <netinet/in.h> +#include <sys/endian.h> +#endif + +#include <osmocom/pfcptool/checksum.h> +#include <arpa/inet.h> + +static inline unsigned short from32to16(unsigned int x) +{ + /* add up 16-bit and 16-bit for 16+c bit */ + x = (x & 0xffff) + (x >> 16); + /* add up carry.. */ + x = (x & 0xffff) + (x >> 16); + return x; +} + +static unsigned int do_csum(const unsigned char *buff, int len) +{ + int odd; + unsigned int result = 0; + + if (len <= 0) + goto out; + odd = 1 & (unsigned long) buff; + if (odd) { +#if BYTE_ORDER == LITTLE_ENDIAN + result += (*buff << 8); +#else + result = *buff; +#endif + len--; + buff++; + } + if (len >= 2) { + if (2 & (unsigned long) buff) { + result += *(unsigned short *) buff; + len -= 2; + buff += 2; + } + if (len >= 4) { + const unsigned char *end = buff + ((unsigned)len & ~3); + unsigned int carry = 0; + do { + unsigned int w = *(unsigned int *) buff; + buff += 4; + result += carry; + result += w; + carry = (w > result); + } while (buff < end); + result += carry; + result = (result & 0xffff) + (result >> 16); + } + if (len & 2) { + result += *(unsigned short *) buff; + buff += 2; + } + } + if (len & 1) +#if BYTE_ORDER == LITTLE_ENDIAN + result += *buff; +#else + result += (*buff << 8); +#endif + result = from32to16(result); + if (odd) + result = ((result >> 8) & 0xff) | ((result & 0xff) << 8); +out: + return result; +} + +/* + * This is a version of ip_compute_csum() optimized for IP headers, + * which always checksum on 4 octet boundaries. + */ +uint16_t ip_fast_csum(const void *iph, unsigned int ihl) +{ + return (uint16_t)~do_csum(iph, ihl*4); +} + +/* + * computes the checksum of a memory block at buff, length len, + * and adds in "sum" (32-bit) + * + * returns a 32-bit number suitable for feeding into itself + * or csum_tcpudp_magic + * + * this function must be called with even lengths, except + * for the last fragment, which may be odd + * + * it's best to have buff aligned on a 32-bit boundary + */ +uint32_t csum_partial(const void *buff, int len, uint32_t wsum) +{ + unsigned int sum = (unsigned int)wsum; + unsigned int result = do_csum(buff, len); + + /* add in old sum, and carry.. */ + result += sum; + if (sum > result) + result += 1; + return (uint32_t)result; +} + +/* + * this routine is used for miscellaneous IP-like checksums, mainly + * in icmp.c + */ +uint16_t ip_compute_csum(const void *buff, int len) +{ + return (uint16_t)~do_csum(buff, len); +} + +uint16_t csum_ipv6_magic(const struct in6_addr *saddr, + const struct in6_addr *daddr, + uint32_t len, uint8_t proto, uint32_t csum) +{ + int carry; + uint32_t ulen; + uint32_t uproto; + uint32_t sum = (uint32_t)csum; + + sum += (uint32_t)saddr->s6_addr32[0]; + carry = (sum < (uint32_t)saddr->s6_addr32[0]); + sum += carry; + + sum += (uint32_t)saddr->s6_addr32[1]; + carry = (sum < (uint32_t)saddr->s6_addr32[1]); + sum += carry; + + sum += (uint32_t)saddr->s6_addr32[2]; + carry = (sum < (uint32_t)saddr->s6_addr32[2]); + sum += carry; + + sum += (uint32_t)saddr->s6_addr32[3]; + carry = (sum < (uint32_t)saddr->s6_addr32[3]); + sum += carry; + + sum += (uint32_t)daddr->s6_addr32[0]; + carry = (sum < (uint32_t)daddr->s6_addr32[0]); + sum += carry; + + sum += (uint32_t)daddr->s6_addr32[1]; + carry = (sum < (uint32_t)daddr->s6_addr32[1]); + sum += carry; + + sum += (uint32_t)daddr->s6_addr32[2]; + carry = (sum < (uint32_t)daddr->s6_addr32[2]); + sum += carry; + + sum += (uint32_t)daddr->s6_addr32[3]; + carry = (sum < (uint32_t)daddr->s6_addr32[3]); + sum += carry; + + ulen = (uint32_t)htonl((uint32_t) len); + sum += ulen; + carry = (sum < ulen); + sum += carry; + + uproto = (uint32_t)htonl(proto); + sum += uproto; + carry = (sum < uproto); + sum += carry; + + return csum_fold((uint32_t)sum); +} + +/* fold a partial checksum */ +uint16_t csum_fold(uint32_t csum) +{ + uint32_t sum = (uint32_t)csum; + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + return (uint16_t)~sum; +} diff --git a/src/osmo-pfcp-tool/gtp_flood.c b/src/osmo-pfcp-tool/gtp_flood.c new file mode 100644 index 0000000..69cbf4b --- /dev/null +++ b/src/osmo-pfcp-tool/gtp_flood.c @@ -0,0 +1,797 @@ +/* GTP-U traffic/load generator. Generates a configurable amount of UDP/IP flows using io_uring. + * + * Based on gtp-load-gen.c from https://gitea.osmocom.org/cellular-infrastructure/gtp-load-gen + * which is marked (C) 2021 by Harald Welte laforge@osmocom.org + */ +/* + * (C) 2024 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include "config.h" + +#include <osmocom/pfcptool/gtp_flood.h> +#include <osmocom/core/logging.h> + +#define HAVE_URING 1 +#if HAVE_URING + +#include <unistd.h> +#include <liburing.h> +#include <pthread.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/udp.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/vty/cpu_sched_vty.h> +#include <osmocom/pfcptool/pfcp_tool.h> +#include <osmocom/pfcptool/checksum.h> + +#define PKT_BUF_SIZE 1452 + +struct gtp_flood_worker; + +static volatile bool rx_started; +static struct timespec ts_start; + +struct gtp_flood { + struct gtp_flood_cfg cfg; + + /* list of struct gtp_flood_worker */ + struct llist_head tx_workers; + /* state for round robin */ + void *next_tx_worker; + int tx_workers_started; + int tx_workers_running; + + /* list of struct gtp_flood_worker */ + struct llist_head rx_workers; + void *next_rx_worker; + int rx_workers_started; + int rx_workers_running; +}; + +struct counter { + uint64_t count; + uint64_t last; +}; + +struct traffic_counter { + struct counter packets; + struct counter bytes; +}; + +uint64_t counter_get(struct counter *c) +{ + uint64_t val = c->count - c->last; + c->last = c->count; + return val; +} + +struct gtp_flood_worker { + struct llist_head entry; + + /* backpointer */ + struct gtp_flood *gtp_flood; + + unsigned int id; + + /* list of struct gtp_flood_flow */ + struct llist_head flows; + + struct io_uring ring; + unsigned last_ring_cq_koverflow; + unsigned last_ring_sq_kdropped; + pthread_t pthread_id; + + struct counter submitted_packets; + struct counter submitted_packets2; + struct traffic_counter tx; + struct traffic_counter rx; + struct { + unsigned long long rx_packets; + unsigned long long rx_bytes; + unsigned long long tx_packets; + unsigned long long tx_bytes; + } ctr; +}; + +struct gtp_flood_flow { + struct llist_head entry; + + /* backpointer */ + struct gtp_flood_worker *worker; + + struct gtp_flood_flow_cfg cfg; + + /* for logging and included in generated payloads */ + unsigned int id; + + /* must live until completion */ + struct iovec iov[1]; + struct msghdr msgh; + + /* flow-private packet buffer */ + uint8_t *pkt_buf; + + unsigned int submitted_gtp_packets; + unsigned int sent_gtp_packets; + + unsigned int received_udp_packets; + unsigned int received_udp_bytes; + + bool stop; +}; + +static void gtp_flood_worker_init(struct gtp_flood *gtp_flood, struct gtp_flood_worker *worker, unsigned int idx) +{ + worker->id = idx; + worker->gtp_flood = gtp_flood; + INIT_LLIST_HEAD(&worker->flows); +} + +struct gtp_flood *gtp_flood_alloc(void *ctx, const struct gtp_flood_cfg *cfg) +{ + struct gtp_flood *gtp_flood; + struct gtp_flood_cfg *c; + int i; + + gtp_flood = talloc_zero(ctx, struct gtp_flood); + *gtp_flood = (struct gtp_flood){ + .cfg = *cfg, + }; + INIT_LLIST_HEAD(>p_flood->tx_workers); + INIT_LLIST_HEAD(>p_flood->rx_workers); + c = >p_flood->cfg; + + for (i = 0; i < c->num_tx_workers; i++) { + struct gtp_flood_worker *w = talloc_zero(gtp_flood, struct gtp_flood_worker); + gtp_flood_worker_init(gtp_flood, w, i); + llist_add(&w->entry, >p_flood->tx_workers); + } + LOGP(DLGLOBAL, LOGL_NOTICE, "tx workers: %u\n", llist_count(>p_flood->tx_workers)); + + for (i = 0; i < c->num_rx_workers; i++) { + struct gtp_flood_worker *w = talloc_zero(gtp_flood, struct gtp_flood_worker); + gtp_flood_worker_init(gtp_flood, w, i); + llist_add(&w->entry, >p_flood->rx_workers); + } + LOGP(DLGLOBAL, LOGL_NOTICE, "rx workers: %u\n", llist_count(>p_flood->rx_workers)); + return gtp_flood; +} + +static void gtp_flood_flow_init_payload(struct gtp_flood_flow *flow) +{ + struct gtp1u_hdr *gtp_hdr = (void *)flow->pkt_buf; + unsigned int udp_payload_len; + + *gtp_hdr = (struct gtp1u_hdr) { + .pn = 0, + .s = 0, + .e = 0, + .spare = 0, + .pt = 1, + .version = 1, + .type = 0xff, /* G-PDU */ + .length = 0, /* filled in later */ + .tei = htonl(flow->cfg.gtp_remote_teid), + }; + + uint8_t *cur = flow->pkt_buf + sizeof(*gtp_hdr); + + struct iphdr *iph; + struct ip6_hdr *ip6h; + struct udphdr *uh; + struct osmo_sockaddr *src = &flow->cfg.payload_src; + struct osmo_sockaddr *dst = &flow->cfg.payload_dst; + + if (src->u.sa.sa_family == AF_INET) { + udp_payload_len = PKT_BUF_SIZE - (sizeof(struct gtp1u_hdr) + sizeof(struct udphdr) + sizeof(struct iphdr)); + iph = (struct iphdr *) cur; + cur += sizeof(*iph); + + iph->ihl = 5; + iph->version = 4; + iph->tos = 0; + iph->tot_len = htons(udp_payload_len + sizeof(struct udphdr) + sizeof(*iph)); + iph->id = 0; + iph->frag_off = 0; + iph->ttl = 32; + iph->protocol = IPPROTO_UDP; + iph->saddr = src->u.sin.sin_addr.s_addr; + iph->daddr = dst->u.sin.sin_addr.s_addr; + iph->check = ip_fast_csum(iph, iph->ihl); + } else { + udp_payload_len = PKT_BUF_SIZE - (sizeof(struct gtp1u_hdr) + sizeof(struct udphdr) + sizeof(struct ip6_hdr)); + ip6h = (struct ip6_hdr *) cur; + cur += sizeof(*ip6h); + + ip6h->ip6_flow = htonl((6 << 28)); + ip6h->ip6_plen = htons(udp_payload_len + sizeof(struct udphdr) + sizeof(*ip6h)); + ip6h->ip6_nxt = IPPROTO_UDP; + ip6h->ip6_hlim = 32; + ip6h->ip6_src = src->u.sin6.sin6_addr; + ip6h->ip6_dst = dst->u.sin6.sin6_addr; + } + + uh = (struct udphdr *) cur; + cur += sizeof(*uh); + + uh->source = htons(osmo_sockaddr_port(&src->u.sa)); + uh->dest = htons(osmo_sockaddr_port(&dst->u.sa)); + uh->len = htons(udp_payload_len); + uh->check = 0; // TODO + + gtp_hdr->length = htons(udp_payload_len + (cur - flow->pkt_buf) - sizeof(*gtp_hdr)); + + /* initialize this once, so we have it ready for each transmit */ + flow->msgh.msg_name = &flow->cfg.gtp_remote.u.sa; + flow->msgh.msg_namelen = sizeof(flow->cfg.gtp_remote.u.sa); + flow->msgh.msg_iov = flow->iov; + flow->msgh.msg_iovlen = ARRAY_SIZE(flow->iov); + flow->msgh.msg_control = NULL; + flow->msgh.msg_controllen = 0; + flow->msgh.msg_flags = 0; + + struct iovec *iov = &flow->iov[0]; + iov->iov_base = flow->pkt_buf; + iov->iov_len = udp_payload_len + (cur - flow->pkt_buf); + OSMO_ASSERT(iov->iov_len <= PKT_BUF_SIZE); + + /* write some payload */ + struct osmo_strbuf sb = { .buf = (void *)cur, .len = udp_payload_len }; + OSMO_STRBUF_PRINTF(sb, "osmo-pfcp-tool gtp flood, emitted from %s to %s teid 0x%08x flow %u\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &flow->cfg.gtp_local->osa), + osmo_sockaddr_to_str_c(OTC_SELECT, &flow->cfg.gtp_remote), + flow->cfg.gtp_remote_teid, + flow->id); + + if (flow->cfg.append_payload_info) { + if (iov->iov_len < (sizeof(flow->cfg.payload_info) + 1)) { + OSMO_ASSERT(PKT_BUF_SIZE > sizeof(flow->cfg.payload_info)); + iov->iov_len = sizeof(flow->cfg.payload_info) + 1; + } + uint8_t *len = ((uint8_t *)iov->iov_base) + iov->iov_len - 1; + struct gtp_flood_payload_info *info = (void *)(len - sizeof(flow->cfg.payload_info)); + *len = sizeof(flow->cfg.payload_info); + *info = flow->cfg.payload_info; + memcpy(info->mark, "info", 4); + } +} + +static void gtp_flood_flow_init_rxbuf(struct gtp_flood_flow *flow) +{ + flow->msgh.msg_iov = flow->iov; + flow->msgh.msg_iovlen = ARRAY_SIZE(flow->iov); + flow->msgh.msg_control = NULL; + flow->msgh.msg_controllen = 0; + flow->msgh.msg_flags = 0; + + flow->iov[0].iov_base = flow->pkt_buf; + flow->iov[0].iov_len = PKT_BUF_SIZE; +} + +struct gtp_flood_worker *gtp_flood_next_tx_worker(struct gtp_flood *gtp_flood) +{ + return llist_round_robin(>p_flood->tx_workers, >p_flood->next_tx_worker, + struct gtp_flood_worker, entry); +} + +struct gtp_flood_worker *gtp_flood_next_rx_worker(struct gtp_flood *gtp_flood) +{ + return llist_round_robin(>p_flood->rx_workers, >p_flood->next_rx_worker, + struct gtp_flood_worker, entry); +} + +void gtp_flood_worker_add_flow(struct gtp_flood *gtp_flood, + struct gtp_flood_worker *worker, + const struct gtp_flood_flow_cfg *flow_cfg) +{ + static unsigned int next_flow_id = 0; + + struct gtp_flood_flow *flow = talloc_zero(gtp_flood, struct gtp_flood_flow); + flow->cfg = *flow_cfg; + flow->id = next_flow_id++; + + flow->pkt_buf = talloc_zero_size(flow, PKT_BUF_SIZE); + OSMO_ASSERT(flow->pkt_buf); + + flow->worker = worker; + if (flow->cfg.rx == false) + gtp_flood_flow_init_payload(flow); + else + gtp_flood_flow_init_rxbuf(flow); + llist_add_tail(&flow->entry, &worker->flows); +} + +void gtp_flood_add_flow(struct gtp_flood *gtp_flood, + const struct gtp_flood_flow_cfg *flow_cfg) +{ + struct gtp_flood_worker *w; + if (flow_cfg->rx == false) + w = gtp_flood_next_tx_worker(gtp_flood); + else + w = gtp_flood_next_rx_worker(gtp_flood); + gtp_flood_worker_add_flow(gtp_flood, w, flow_cfg); +} + +/* transmit one packet for a given flow */ +static bool gtp_flow_tx_one(struct gtp_flood_flow *flow) +{ + struct gtp_flood_worker *worker = flow->worker; + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(&worker->ring); + if (!sqe) + return false; + io_uring_prep_sendmsg(sqe, flow->cfg.gtp_local->ofd.fd, &flow->msgh, 0); + io_uring_sqe_set_data(sqe, flow); + return true; +} + +static void tx_completion(struct gtp_flood_worker *worker, struct io_uring_cqe *cqe, int *tx_flows_ended) +{ + struct gtp_flood_flow *flow; + flow = io_uring_cqe_get_data(cqe); + + if (OSMO_LIKELY(cqe->res >= 0)) { + flow->sent_gtp_packets++; + + worker->ctr.tx_packets++; + worker->ctr.tx_bytes += cqe->res; + + if (flow->cfg.num_packets + && flow->sent_gtp_packets >= flow->cfg.num_packets) { + flow->stop = true; + (*tx_flows_ended)++; + } + } else { + flow->submitted_gtp_packets--; + } + io_uring_cqe_seen(&worker->ring, cqe); +} + +static void apply_thread_name_cpu_affinity(const struct gtp_flood_worker *worker, const char *thread_name) +{ + if (pthread_setname_np(worker->pthread_id, thread_name) != 0) { + char buf[256]; + int err = errno; + char *err_str = strerror_r(err, buf, sizeof(buf)); + LOGP(DLGLOBAL, LOGL_ERROR, "rx worker %u: failed setting thread name: %s\n", + worker->id, err_str); + } + OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0); +} + +static void *gtp_flood_tx_worker_thread(void *_worker) +{ + struct gtp_flood_worker *worker = (struct gtp_flood_worker *)_worker; + struct gtp_flood *gtp_flood = worker->gtp_flood; + char thread_name[128]; + void *next_flow = NULL; + + osmo_ctx_init(__func__); + + gtp_flood->tx_workers_started++; + gtp_flood->tx_workers_running++; + + LOGP(DLGLOBAL, LOGL_INFO, "gtp flood tx worker %d starting (%u started, %u running)\n", + worker->id, + gtp_flood->tx_workers_started, + gtp_flood->tx_workers_running); + + int tx_flows_count = llist_count(&worker->flows); + int tx_flows_ended = 0; + + int num_submitted_total = 0; + int num_submitted2_total = 0; + + struct __kernel_timespec ts_zero = {}; + struct __kernel_timespec ts_timeout = { .tv_nsec = 0.5e9 }; + + snprintf(thread_name, sizeof(thread_name), "GtpFloodTx%u", worker->id); + apply_thread_name_cpu_affinity(worker, thread_name); + + while (tx_flows_ended < tx_flows_count) { + uint32_t num_submitted = 0; + int num_submitted2; + if (gtp_flood->cfg.slew_us) + usleep(gtp_flood->cfg.slew_us); + + /* fill up sqe with transmit submissions */ + bool keep_submitting = true; + while (keep_submitting) { + int submitted_was = num_submitted; + + struct gtp_flood_flow *flow; + flow = llist_round_robin(&worker->flows, &next_flow, struct gtp_flood_flow, entry); + if (flow->stop) + continue; + + if (flow->cfg.num_packets + && flow->submitted_gtp_packets >= flow->cfg.num_packets) + continue; + + if (gtp_flow_tx_one(flow)) { + flow->submitted_gtp_packets++; + num_submitted++; + + worker->submitted_packets.count++; + } else { + /* out of sqe. */ + keep_submitting = false; + break; + } + + /* No change in number of submitted PDUs, all flows are done submitting for this round. */ + if (submitted_was == num_submitted) + keep_submitting = false; + } + + /* actually submit */ + num_submitted2 = io_uring_submit(&worker->ring); + worker->submitted_packets2.count += num_submitted2; + + /* process all pending completions */ + int completed = 0; + struct io_uring_cqe *cqe; + while (io_uring_wait_cqe_timeout(&worker->ring, &cqe, &ts_zero) == 0) { + tx_completion(worker, cqe, &tx_flows_ended); + completed++; + } + +#define LOG_TX 1 +#if LOG_TX + /* periodically log tx stats */ + static struct timespec tx_last_info_log = {.tv_sec = 0, .tv_nsec = 0}; + struct timespec ts_now; + clock_gettime(CLOCK_MONOTONIC, &ts_now); + /* the resolution is in seconds, output stats once per second. */ + if (OSMO_UNLIKELY(ts_now.tv_sec - tx_last_info_log.tv_sec >= 10)) { + struct timespec ts_elapsed, ts_diff; + unsigned long long elapsed_usec, diff_usec; + uint64_t diff_rx_packets, diff_rx_bytes, diff_tx_packets, diff_tx_bytes; + uint64_t elapsed_rx_packets = 0; + uint64_t elapsed_rx_bytes = 0; + uint64_t elapsed_tx_packets = 0; + uint64_t elapsed_tx_bytes = 0; + + timespecsub(&ts_now, &ts_start, &ts_elapsed); + timespecsub(&ts_now, &tx_last_info_log, &ts_diff); + tx_last_info_log = ts_now; + + struct gtp_flood_worker *w_it; + llist_for_each_entry(w_it, >p_flood->tx_workers, entry) { + elapsed_tx_packets += w_it->ctr.tx_packets; + elapsed_tx_bytes += w_it->ctr.tx_bytes; + } + llist_for_each_entry(w_it, >p_flood->rx_workers, entry) { + elapsed_rx_packets += w_it->ctr.rx_packets; + elapsed_rx_bytes += w_it->ctr.rx_bytes; + } + worker->tx.packets.count = elapsed_tx_packets; + worker->tx.bytes.count = elapsed_tx_bytes; + worker->rx.packets.count = elapsed_rx_packets; + worker->rx.bytes.count = elapsed_rx_bytes; + + diff_tx_packets = counter_get(&worker->tx.packets); + diff_tx_bytes = counter_get(&worker->tx.bytes); + diff_rx_packets = counter_get(&worker->rx.packets); + diff_rx_bytes = counter_get(&worker->rx.bytes); + + if (diff_tx_packets || diff_tx_bytes) { + elapsed_usec = (ts_elapsed.tv_sec * 1000 * 1000) + (ts_elapsed.tv_nsec / 1000); + diff_usec = (ts_diff.tv_sec * 1000 * 1000) + (ts_diff.tv_nsec / 1000); + if (elapsed_usec == 0) + elapsed_usec = 1; + if (diff_usec == 0) + diff_usec = 1; + printf("%u: DIFF: %12llu Tx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps) | Rx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps)\n", + worker->id, + diff_usec, + diff_tx_packets, + diff_tx_packets * 1000 / diff_usec, + diff_tx_bytes * 8 / diff_usec, + diff_rx_packets, + diff_rx_packets * 1000 / diff_usec, + diff_rx_bytes * 8 / diff_usec); + printf("%u: TOTAL: %12llu Tx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps) | Rx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps)\n", + worker->id, + elapsed_usec, + elapsed_tx_packets, + elapsed_tx_packets * 1000 / elapsed_usec, + elapsed_tx_bytes * 8 / elapsed_usec, + elapsed_rx_packets, + elapsed_rx_packets * 1000 / elapsed_usec, + elapsed_rx_bytes * 8 / elapsed_usec); + if (io_uring_cq_has_overflow(&worker->ring)) + printf("%u: CQ IN OVERFLOW STATE!\n", worker->id); + unsigned val = *worker->ring.cq.koverflow; + if (val > worker->last_ring_cq_koverflow) { + printf("%u: CQ OVERFLOW! %u -> %u\n", worker->id, worker->last_ring_cq_koverflow, val); + worker->last_ring_cq_koverflow = val; + } + //fflush(stdout); + /* mark that something happened to not enter the wait below */ + completed++; + } + } +#endif + + if (!num_submitted2 && !completed) { + /* There are currently no slots available for submitting more packets, wait until the next slot + * becomes available. After a timeout, re-check whether the worker should exit. */ + if (io_uring_wait_cqe_timeout(&worker->ring, &cqe, &ts_timeout) == 0) { + tx_completion(worker, cqe, &tx_flows_ended); + completed++; + } + } + + num_submitted_total += num_submitted; + if (num_submitted2 > 0) + num_submitted2_total += num_submitted2; + } + + gtp_flood->tx_workers_running--; + LOGP(DLGLOBAL, LOGL_INFO, "gtp flood tx worker %d done (%u started, %u running) (%"PRIu64" packets not submitted)\n", + worker->id, + gtp_flood->tx_workers_started, + gtp_flood->tx_workers_running, + worker->submitted_packets.count - worker->tx.packets.count); + return NULL; +} + +/* receive one packet for a given flow */ +static bool gtp_flow_rx_one(struct gtp_flood_flow *flow) +{ + struct gtp_flood_worker *worker = flow->worker; + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(&worker->ring); + if (!sqe) + return false; + io_uring_prep_recvmsg(sqe, flow->cfg.gtp_local->ofd.fd, &flow->msgh, 0); + io_uring_sqe_set_data(sqe, flow); + return true; +} + +void rx_completion(struct gtp_flood_worker *worker, struct io_uring_cqe *cqe) +{ + int len = cqe->res; + struct gtp_flood_flow *flow; + flow = io_uring_cqe_get_data(cqe); + + /* done reading */ + if (OSMO_UNLIKELY(!rx_started)) { + rx_started = true; + OSMO_ASSERT(clock_gettime(CLOCK_MONOTONIC, &ts_start) == 0); + } + + if (OSMO_LIKELY(len > 0)) { + flow->received_udp_packets++; + flow->received_udp_bytes += len; + worker->ctr.rx_packets++; + worker->ctr.rx_bytes += len; + } else if (OSMO_UNLIKELY(len < 0)) { + LOGP(DLGLOBAL, LOGL_ERROR, "%u: rx error rc=%d flags=0x%x\n", + worker->id, len, cqe->flags); + } + io_uring_cqe_seen(&worker->ring, cqe); + + /* reschedule */ + gtp_flow_rx_one(flow); +} + +static void *gtp_flood_rx_worker_thread(void *_worker) +{ + struct gtp_flood_worker *worker = (struct gtp_flood_worker *)_worker; + struct gtp_flood *gtp_flood = worker->gtp_flood; + char thread_name[128]; + + osmo_ctx_init(__func__); + + gtp_flood->rx_workers_started++; + gtp_flood->rx_workers_running++; + + LOGP(DLGLOBAL, LOGL_INFO, "gtp rx worker %u starting (%u started, %u running)\n", + worker->id, + gtp_flood->rx_workers_started, + gtp_flood->rx_workers_running); + + struct gtp_flood_flow *flow; + void *next_flow = NULL; + + struct __kernel_timespec ts_zero = {}; + struct __kernel_timespec ts_1s = { .tv_sec = 1 }; + + snprintf(thread_name, sizeof(thread_name), "GtpFloodRx%u", worker->id); + apply_thread_name_cpu_affinity(worker, thread_name); + + /* submit all rx flows N times until the queue is full */ + do { + flow = llist_round_robin(&worker->flows, &next_flow, struct gtp_flood_flow, entry); + } while (gtp_flow_rx_one(flow)); + + /* service completions and resubmit sqe */ + while (1) { + struct io_uring_cqe *cqe; + int submitted; + int completed = 0; + + /* submit batch of pending reads */ + submitted = io_uring_submit(&worker->ring); + + /* process all pending completions */ + while (io_uring_wait_cqe_timeout(&worker->ring, &cqe, &ts_zero) == 0) { + rx_completion(worker, cqe); + completed++; + } + +#define LOG_RX 0 +#if LOG_RX + /* periodically log rx stats */ + static struct timespec last_info_log = {.tv_sec = 0, .tv_nsec = 0}; + struct timespec ts_now; + + clock_gettime(CLOCK_MONOTONIC, &ts_now); + /* the resolution is in seconds, output stats once per second. */ + if (OSMO_UNLIKELY(ts_now.tv_sec - tx_last_info_log.tv_sec >= 10)) { + struct timespec ts_elapsed, ts_diff; + unsigned long long elapsed_usec, diff_usec; + uint64_t diff_rx_packets, diff_rx_bytes, diff_tx_packets, diff_tx_bytes; + uint64_t elapsed_rx_packets = 0; + uint64_t elapsed_rx_bytes = 0; + uint64_t elapsed_tx_packets = 0; + uint64_t elapsed_tx_bytes = 0; + + timespecsub(&ts_now, &ts_start, &ts_elapsed); + timespecsub(&ts_now, &last_info_log, &ts_diff); + last_info_log = ts_now; + + + struct gtp_flood_worker *w_it; + llist_for_each_entry(w_it, >p_flood->tx_workers, entry) { + elapsed_tx_packets += w_it->ctr.tx_packets; + elapsed_tx_bytes += w_it->ctr.tx_bytes; + } + llist_for_each_entry(w_it, >p_flood->rx_workers, entry) { + elapsed_rx_packets += w_it->ctr.rx_packets; + elapsed_rx_bytes += w_it->ctr.rx_bytes; + } + worker->tx.packets.count = elapsed_tx_packets; + worker->tx.bytes.count = elapsed_tx_bytes; + worker->rx.packets.count = elapsed_rx_packets; + worker->rx.bytes.count = elapsed_rx_bytes; + + diff_tx_packets = counter_get(&worker->tx.packets); + diff_tx_bytes = counter_get(&worker->tx.bytes); + diff_rx_packets = counter_get(&worker->rx.packets); + diff_rx_bytes = counter_get(&worker->rx.bytes); + + if (diff_rx_packets || diff_tx_packets) { + elapsed_usec = (ts_elapsed.tv_sec * 1000 * 1000) + (ts_elapsed.tv_nsec / 1000); + diff_usec = (ts_diff.tv_sec * 1000 * 1000) + (ts_diff.tv_nsec / 1000); + if (elapsed_usec == 0) + elapsed_usec = 1; + if (diff_usec == 0) + diff_usec = 1; + printf("%u: DIFF: %12llu Tx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps) | Rx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps)\n", + worker->id, + diff_usec, + diff_tx_packets, + diff_tx_packets * 1000 / diff_usec, + diff_tx_bytes * 8 / diff_usec, + diff_rx_packets, + diff_rx_packets * 1000 / diff_usec, + diff_rx_bytes * 8 / diff_usec); + printf("%u: TOTAL: %12llu Tx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps) | Rx: %10"PRIu64" packets (%6llu kPPS, %8llu Mbps)\n", + worker->id, + elapsed_usec, + elapsed_tx_packets, + elapsed_tx_packets * 1000 / elapsed_usec, + elapsed_tx_bytes * 8 / elapsed_usec, + elapsed_rx_packets, + elapsed_rx_packets * 1000 / elapsed_usec, + elapsed_rx_bytes * 8 / elapsed_usec); + if (io_uring_cq_has_overflow(&worker->ring)) + printf("%u: CQ IN OVERFLOW STATE!\n", worker->id); + unsigned val = *worker->ring.cq.koverflow; + if (val > worker->last_ring_cq_koverflow) { + printf("%u: CQ OVERFLOW! %u -> %u\n", worker->id, worker->last_ring_cq_koverflow, val); + worker->last_ring_cq_koverflow = val; + } + //fflush(stdout); + /* mark that something happened to not enter the wait below */ + completed++; + } + } +#endif + + /* Nothing happened in this loop, wait for the next event */ + if (!submitted && !completed) { + if (io_uring_wait_cqe_timeout(&worker->ring, &cqe, &ts_1s) == 0) { + rx_completion(worker, cqe); + completed++; + } + } + } + + gtp_flood->rx_workers_running--; + return NULL; +} + +static void gtp_flood_worker_start(struct gtp_flood_worker *worker, + void *(*worker_func)(void *)) +{ + int rc; + rc = io_uring_queue_init(worker->gtp_flood->cfg.queue_size, &worker->ring, 0); + OSMO_ASSERT(rc >= 0); + unsigned int vals[2] = {1, 1}; + rc = io_uring_register_iowq_max_workers(&worker->ring, &vals[0]); + OSMO_ASSERT(rc == 0); + rc = pthread_create(&worker->pthread_id, NULL, worker_func, worker); + OSMO_ASSERT(rc >= 0); +} + +void gtp_flood_start(struct gtp_flood *gtp_flood) +{ + struct gtp_flood_worker *w; + llist_for_each_entry(w, >p_flood->tx_workers, entry) + gtp_flood_worker_start(w, gtp_flood_tx_worker_thread); + llist_for_each_entry(w, >p_flood->rx_workers, entry) + gtp_flood_worker_start(w, gtp_flood_rx_worker_thread); +} + +bool gtp_flood_is_busy(struct gtp_flood *gtp_flood) +{ + if (!gtp_flood) + return false; + return gtp_flood->tx_workers_started && gtp_flood->tx_workers_running; +} + +#else /* HAVE_URING */ + +struct gtp_flood *gtp_flood_alloc(void *ctx, unsigned int workers) +{ + LOGP(DLGLOBAL, LOGL_ERROR, "Cannot start GTP flood: built without liburing support\n"); + return NULL; +} + +void gtp_flood_add_flow(struct gtp_flood *gtp_flood, + const struct gtp_flood_flow_cfg *flow_cfg) +{ +} + +void gtp_flood_start(struct gtp_flood *gtp_flood) +{ +} + +bool gtp_flood_is_busy(struct gtp_flood *gtp_flood) +{ + return false; +} + +#endif /* HAVE_URING */ diff --git a/src/osmo-pfcp-tool/osmo_pfcp_tool_main.c b/src/osmo-pfcp-tool/osmo_pfcp_tool_main.c new file mode 100644 index 0000000..d61c951 --- /dev/null +++ b/src/osmo-pfcp-tool/osmo_pfcp_tool_main.c @@ -0,0 +1,375 @@ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <osmocom/core/application.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/msgb.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/cpu_sched_vty.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/ports.h> +#include <osmocom/vty/tdef_vty.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/ctrl/control_vty.h> +#include <osmocom/ctrl/ports.h> +#include <osmocom/core/sockaddr_str.h> + +#include <osmocom/pfcp/pfcp_endpoint.h> + +#include <osmocom/pfcptool/pfcp_tool.h> +#include <osmocom/pfcptool/gtp_flood.h> + +#include <getopt.h> + +/* build switches from the configure script */ +#include "config.h" + +#include <signal.h> +#include <stdio.h> +#include <string.h> + +extern void *tall_vty_ctx; + +void *tall_pfcp_tool_ctx = NULL; +static int quit = 0; + +static struct { + const char *config_file; + int daemonize; + enum vty_ref_gen_mode vty_ref_gen_mode; + const char *command_file; +} pfcp_tool_cmdline_config = { + .config_file = "osmo-pfcp-tool.cfg", + .vty_ref_gen_mode = VTY_REF_GEN_MODE_DEFAULT, +}; + +static void print_usage() +{ + printf("Usage: osmo-pfcp-tool [command-file.vty]\n telnet localhost %d\n", OSMO_VTY_PORT_PFCP_TOOL); +} + +static void print_help() +{ + const struct value_string *vty_ref_gen_mode_name; + + printf("Some useful options:\n"); + printf(" -h --help This text.\n"); + printf(" -D --daemonize Fork the process into a background daemon.\n"); + printf(" -c --config-file filename The config file to use, for logging etc.\n"); + printf(" -V --version Print the version of OsmoMSC.\n"); + + printf("\nVTY reference generation:\n"); + printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n"); + printf(" --vty-ref-mode MODE Mode for --vty-ref-xml:\n"); + /* List all VTY ref gen modes */ + for (vty_ref_gen_mode_name = vty_ref_gen_mode_names; vty_ref_gen_mode_name->str; vty_ref_gen_mode_name++) + printf(" %s: %s\n", + vty_ref_gen_mode_name->str, + get_value_string(vty_ref_gen_mode_desc, vty_ref_gen_mode_name->value)); +} + +static void handle_long_options(const char *prog_name, const int long_option) +{ + switch (long_option) { + case 1: + pfcp_tool_cmdline_config.vty_ref_gen_mode = get_string_value(vty_ref_gen_mode_names, optarg); + if (pfcp_tool_cmdline_config.vty_ref_gen_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, pfcp_tool_cmdline_config.vty_ref_gen_mode), + get_value_string(vty_ref_gen_mode_desc, pfcp_tool_cmdline_config.vty_ref_gen_mode)); + vty_dump_xml_ref_mode(stdout, pfcp_tool_cmdline_config.vty_ref_gen_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'}, + {"daemonize", 0, 0, 'D'}, + {"config-file", 1, 0, 'c'}, + {"version", 0, 0, 'V' }, + {"vty-ref-mode", 1, &long_option, 1}, + {"vty-ref-xml", 0, &long_option, 2}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hDc:V", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 0: + handle_long_options(argv[0], long_option); + break; + case 'D': + pfcp_tool_cmdline_config.daemonize = 1; + break; + case 'c': + pfcp_tool_cmdline_config.config_file = optarg; + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "%s: Error in command line options. Exiting.\n", argv[0]); + exit(-1); + } + } + + if (argc > optind) { + pfcp_tool_cmdline_config.command_file = argv[optind]; + optind++; + } + + if (argc > optind) { + fprintf(stderr, "%s: Unsupported positional arguments on command line\n", argv[optind]); + exit(2); + } +} + +static void signal_handler(int signum) +{ + fprintf(stdout, "signal %u received\n", signum); + + switch (signum) { + case SIGINT: + case SIGTERM: + LOGP(DLGLOBAL, LOGL_NOTICE, "Terminating due to signal %d\n", signum); + quit++; + break; + case SIGABRT: + osmo_generate_backtrace(); + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_pfcp_tool_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_pfcp_tool_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +static struct vty_app_info pfcp_tool_vty_app_info = { + .name = "osmo-pfcp-tool", + .version = PACKAGE_VERSION, + .copyright = + "OsmoPFCPTool - Osmocom Packet Forwarding Control Protocol tool for testing\r\n" + "Copyright (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de\r\n" + "License AGPLv3+: GNU AGPL version 3 or later http://gnu.org/licenses/agpl-3.0.html\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n", + .go_parent_cb = pfcp_tool_vty_go_parent, +}; + +static const struct log_info_cat pfcp_tool_default_categories[] = { +}; + +const struct log_info log_info = { + .cat = pfcp_tool_default_categories, + .num_cat = ARRAY_SIZE(pfcp_tool_default_categories), +}; + +int pfcp_tool_mainloop(int poll) +{ + int rc; + log_reset_context(); + rc = osmo_select_main_ctx(poll); + + /* If the user hits Ctrl-C the third time, just terminate immediately. */ + if (quit >= 3) + return -1; + + /* Has SIGTERM been received (and not yet been handled)? */ + if (quit && !osmo_select_shutdown_requested()) { + osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); + + /* Request write-only mode in osmo_select_main_ctx() */ + osmo_select_shutdown_request(); + /* continue the main select loop until all write queues are serviced. */ + } + return rc; +} + +int main(int argc, char **argv) +{ + int rc; + + /* Track the use of talloc NULL memory contexts */ + talloc_enable_null_tracking(); + + osmo_fsm_set_dealloc_ctx(OTC_SELECT); + + tall_pfcp_tool_ctx = talloc_named_const(NULL, 1, "osmo-pfcp-tool"); + pfcp_tool_vty_app_info.tall_ctx = tall_pfcp_tool_ctx; + + msgb_talloc_ctx_init(tall_pfcp_tool_ctx, 0); + osmo_signal_talloc_ctx_init(tall_pfcp_tool_ctx); + + osmo_init_logging2(tall_pfcp_tool_ctx, &log_info); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_level(osmo_stderr_target, 1); + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); + log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); + log_set_print_extended_timestamp(osmo_stderr_target, 1); + + osmo_fsm_log_timeouts(true); + osmo_fsm_log_addr(true); + + osmo_stats_init(tall_pfcp_tool_ctx); + + g_pfcp_tool_alloc(tall_pfcp_tool_ctx); + + /* For --version, vty_init() must be called before handling options */ + vty_init(&pfcp_tool_vty_app_info); + + ctrl_vty_init(tall_pfcp_tool_ctx); + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_cpu_sched_vty_init(tall_pfcp_tool_ctx); + osmo_fsm_vty_add_cmds(); + osmo_tdef_vty_groups_init(CONFIG_NODE, g_pfcp_tool_tdef_groups); + + pfcp_tool_vty_init_cfg(); + + /* Parse options */ + handle_options(argc, argv); + + if (pfcp_tool_cmdline_config.config_file) { + rc = vty_read_config_file(pfcp_tool_cmdline_config.config_file, NULL); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Failed to parse the config file: '%s'\n", + pfcp_tool_cmdline_config.config_file); + } + } + + /* start telnet VTY */ + rc = telnet_init_default(tall_pfcp_tool_ctx, &g_pfcp_tool, OSMO_VTY_PORT_PFCP_TOOL); + if (rc < 0) + return 2; + + /* start control interface, after reading config for ctrl_vty_get_bind_addr() */ + g_pfcp_tool->ctrl = ctrl_interface_setup(g_pfcp_tool, OSMO_CTRL_PORT_PFCP_TOOL, NULL); + if (!g_pfcp_tool->ctrl) { + fprintf(stderr, "Failed to initialize control interface. Exiting.\n"); + return -1; + } + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (pfcp_tool_cmdline_config.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + return 6; + } + } + + pfcp_tool_mainloop(1); + + pfcp_tool_vty_init_cmds(); + + if (pfcp_tool_cmdline_config.command_file) { + printf("Reading '%s'\n", pfcp_tool_cmdline_config.command_file); + rc = vty_read_config_file(pfcp_tool_cmdline_config.command_file, NULL); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_FATAL, "Failed to parse the command file: '%s'\n", + pfcp_tool_cmdline_config.command_file); + return 1; + } + printf("Done reading '%s', waiting for tasks to conclude...\n", + pfcp_tool_cmdline_config.command_file); + do { + if (pfcp_tool_mainloop(0) == -1) + break; + } while (osmo_pfcp_endpoint_retrans_queue_is_busy(g_pfcp_tool->ep) + || gtp_flood_is_busy(g_pfcp_tool->gtp.flood.state)); + LOGP(DLGLOBAL, LOGL_NOTICE, "Done\n"); + } else { + printf("Listening for commands on VTY...\n"); + do { + if (pfcp_tool_mainloop(0) == -1) + break; + } while (!osmo_select_shutdown_done()); + } + + osmo_pfcp_endpoint_free(&g_pfcp_tool->ep); + + log_fini(); + + /* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */ + //talloc_report_full(tall_pfcp_tool_ctx, stderr); + talloc_free(tall_pfcp_tool_ctx); + + //talloc_report_full(tall_vty_ctx, stderr); + talloc_free(tall_vty_ctx); + + //talloc_report_full(NULL, stderr); + talloc_disable_null_tracking(); + return 0; +} diff --git a/src/osmo-pfcp-tool/pfcp_tool.c b/src/osmo-pfcp-tool/pfcp_tool.c new file mode 100644 index 0000000..6ba8d27 --- /dev/null +++ b/src/osmo-pfcp-tool/pfcp_tool.c @@ -0,0 +1,440 @@ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <errno.h> +#include <netinet/in.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/pfcp/pfcp_endpoint.h> + +#include <osmocom/pfcptool/pfcp_tool.h> +#include <osmocom/pfcptool/gtp_flood.h> + +struct g_pfcp_tool *g_pfcp_tool = NULL; + +struct osmo_tdef_group g_pfcp_tool_tdef_groups[] = { + { .name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP" }, + {} +}; + +void g_pfcp_tool_alloc(void *ctx) +{ + OSMO_ASSERT(g_pfcp_tool == NULL); + g_pfcp_tool = talloc_zero(ctx, struct g_pfcp_tool); + + *g_pfcp_tool = (struct g_pfcp_tool){ + .vty_cfg = { + .local_ip = talloc_strdup(g_pfcp_tool, "0.0.0.0"), + .local_port = OSMO_PFCP_PORT, + }, + .next_teid_state = 23, + .gtp.flood = { + .cfg = { + .num_rx_workers = 1, + .num_tx_workers = 1, + .queue_size = 4096, + .slew_us = 0, + }, + .flows_per_session = 1, + .packets_per_flow = 0, + }, + }; + + INIT_LLIST_HEAD(&g_pfcp_tool->peers); + INIT_LLIST_HEAD(&g_pfcp_tool->gtp.gtp_local_addrs); + INIT_LLIST_HEAD(&g_pfcp_tool->gtp.gtp_core_addrs); +} + +struct pfcp_tool_peer *pfcp_tool_peer_find(const struct osmo_sockaddr *remote_addr) +{ + struct pfcp_tool_peer *peer; + llist_for_each_entry(peer, &g_pfcp_tool->peers, entry) { + if (osmo_sockaddr_cmp(&peer->remote_addr, remote_addr) == 0) + return peer; + } + return NULL; +} + +struct pfcp_tool_peer *pfcp_tool_peer_find_or_create(const struct osmo_sockaddr *remote_addr) +{ + struct pfcp_tool_peer *peer = pfcp_tool_peer_find(remote_addr); + if (peer) + return peer; + + peer = talloc_zero(g_pfcp_tool, struct pfcp_tool_peer); + peer->remote_addr = *remote_addr; + peer->next_seid_state = 0x1234567; + INIT_LLIST_HEAD(&peer->sessions); + llist_add(&peer->entry, &g_pfcp_tool->peers); + return peer; +} + +struct pfcp_tool_session *pfcp_tool_session_find(struct pfcp_tool_peer *peer, uint64_t cp_seid) +{ + struct pfcp_tool_session *session; + llist_for_each_entry(session, &peer->sessions, entry) { + if (session->cp_seid == cp_seid) + return session; + } + return NULL; +} + +struct pfcp_tool_session *pfcp_tool_session_find_or_create(struct pfcp_tool_peer *peer, uint64_t cp_seid, + enum up_gtp_action_kind kind) +{ + struct pfcp_tool_session *session = pfcp_tool_session_find(peer, cp_seid); + if (session) + return session; + + session = talloc(peer, struct pfcp_tool_session); + *session = (struct pfcp_tool_session){ + .peer = peer, + .cp_seid = cp_seid, + .kind = kind, + }; + llist_add(&session->entry, &peer->sessions); + return session; +} + +static void rx_assoc_setup_resp(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m) +{ + if (m->ies.assoc_setup_resp.up_function_features_present) + OSMO_LOG_PFCP_MSG(m, LOGL_NOTICE, "Associated. UP Peer features: %s\n", + osmo_pfcp_bits_to_str_c(OTC_SELECT, + m->ies.assoc_setup_resp.up_function_features.bits, + osmo_pfcp_up_feature_strs)); + + if (m->ies.assoc_setup_resp.cp_function_features_present) + OSMO_LOG_PFCP_MSG(m, LOGL_NOTICE, "Associated. CP Peer features: %s\n", + osmo_pfcp_bits_to_str_c(OTC_SELECT, + m->ies.assoc_setup_resp.cp_function_features.bits, + osmo_pfcp_cp_feature_strs)); +} + +static void rx_session_est_resp(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m) +{ + struct pfcp_tool_peer *peer; + struct pfcp_tool_session *session; + enum osmo_pfcp_cause *cause = osmo_pfcp_msg_cause(m); + if (!cause) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response should contain a Cause\n"); + return; + } + if (*cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer responds that Session Establishment failed\n"); + return; + } + if (!m->h.seid_present) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response should contain a SEID\n"); + return; + } + if (!m->ies.session_est_resp.up_f_seid_present) { + OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response without UP F-SEID\n"); + return; + } + peer = pfcp_tool_peer_find(&m->remote_addr); + if (!peer) + return; + session = pfcp_tool_session_find(peer, m->h.seid); + if (!session) + return; + session->up_f_seid = m->ies.session_est_resp.up_f_seid; +} + +void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req) +{ + switch (m->h.message_type) { + case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP: + rx_assoc_setup_resp(ep, m); + break; + case OSMO_PFCP_MSGT_SESSION_EST_RESP: + rx_session_est_resp(ep, m); + break; + default: break; + } +} + +static void copy_msg(struct osmo_pfcp_msg *dst, const struct osmo_pfcp_msg *m) +{ + *dst = *m; + dst->encoded = NULL; + dst->ctx.peer_use_token = NULL; + dst->ctx.session_use_token = NULL; + dst->ctx.resp_cb = NULL; +} + +int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m) +{ + int rc; + if (m->is_response) + copy_msg(&peer->last_resp, m); + else + copy_msg(&peer->last_req, m); + rc = osmo_pfcp_endpoint_tx(g_pfcp_tool->ep, m); + if (rc) + LOGP(DLPFCP, LOGL_ERROR, "Failed to transmit PFCP: %s\n", strerror(-rc)); + return rc; +} + +uint64_t peer_new_seid(struct pfcp_tool_peer *peer) +{ + return peer->next_seid_state++; +} + +uint32_t pfcp_tool_new_teid(void) +{ + return g_pfcp_tool->next_teid_state++; +} + +int pfcp_tool_next_ue_addr(struct osmo_sockaddr *dst) +{ + range_next(&g_pfcp_tool->ue.ip_range); + if (g_pfcp_tool->ue.ip_range.wrapped) { + LOGP(DLGLOBAL, LOGL_ERROR, "insufficient UE IP addresses, wrapped back to first\n"); + g_pfcp_tool->ue.ip_range.wrapped = false; + } + range_val_get_addr(dst, &g_pfcp_tool->ue.ip_range.next); + return 0; +} + +struct udp_port *pfcp_tool_have_udp_port_by_osa(struct llist_head *list, const struct osmo_sockaddr *_osa, + uint16_t fallback_port) +{ + struct udp_port *port; + /* copy osa and have a non-const pointer */ + struct osmo_sockaddr osa_mutable = *_osa; + struct osmo_sockaddr *osa = &osa_mutable; + + if (!osmo_sockaddr_port(&osa->u.sa)) + osmo_sockaddr_set_port(&osa->u.sa, fallback_port); + OSMO_ASSERT(osmo_sockaddr_port(&osa->u.sa)); + + llist_for_each_entry(port, list, entry) { + if (osmo_sockaddr_cmp(&port->osa, osa) == 0) + return port; + } + + port = talloc_zero(g_pfcp_tool, struct udp_port); + port->osa = *osa; + port->ofd.fd = -1; + + llist_add_tail(&port->entry, list); + return port; +} + +struct udp_port *pfcp_tool_have_udp_port_by_str(struct llist_head *list, const struct osmo_sockaddr_str *addr, + uint16_t fallback_port) +{ + struct osmo_sockaddr osa; + if (osmo_sockaddr_str_to_osa(addr, &osa)) + return NULL; + return pfcp_tool_have_udp_port_by_osa(list, &osa, fallback_port); +} + +struct udp_port *pfcp_tool_have_local_udp_port_by_osa(const struct osmo_sockaddr *osa, uint16_t fallback_port) +{ + struct udp_port *port = pfcp_tool_have_udp_port_by_osa(&g_pfcp_tool->gtp.gtp_local_addrs, osa, fallback_port); + + /* already bound? */ + if (port->ofd.fd >= 0) + return port; + + /* create and bind socket */ + int rc; + rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, &port->osa, NULL, OSMO_SOCK_F_BIND); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Failed to bind socket on UDP %s\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &port->osa)); + exit(1); + } + port->ofd.fd = rc; + +#if 0 + int a_lot; + a_lot = 1024 * (1024*1024); + rc = setsockopt(port->ofd.fd, SOL_SOCKET, SO_SNDBUF, &a_lot, sizeof(a_lot)); + a_lot = 1024 * (1024*1024); + rc = setsockopt(port->ofd.fd, SOL_SOCKET, SO_RCVBUF, &a_lot, sizeof(a_lot)); +#endif + + LOGP(DLGLOBAL, LOGL_NOTICE, "bound UDP %s fd=%d\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &port->osa), rc); + + /* Set Don't Fragment (DF) bit on IP packets transmitted by socket: */ + int val = IP_PMTUDISC_DO; + rc = setsockopt(port->ofd.fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)); + if (rc == -1) + LOGP(DLGLOBAL, LOGL_ERROR, "setsockopt(IPPROTO_IP, IP_DONTFRAG) failed errno=%d", errno); + return port; +} + +struct udp_port *pfcp_tool_have_local_udp_port_by_str(const struct osmo_sockaddr_str *addr, uint16_t fallback_port) +{ + struct osmo_sockaddr osa; + if (osmo_sockaddr_str_to_osa(addr, &osa)) + return NULL; + return pfcp_tool_have_local_udp_port_by_osa(&osa, fallback_port); +} + +struct udp_port *pfcp_tool_have_core_udp_port_by_osa(const struct osmo_sockaddr *osa, uint16_t fallback_port) +{ + return pfcp_tool_have_udp_port_by_osa(&g_pfcp_tool->gtp.gtp_core_addrs, osa, fallback_port); +} + +struct udp_port *pfcp_tool_have_core_udp_port_by_str(const struct osmo_sockaddr_str *addr, uint16_t fallback_port) +{ + struct osmo_sockaddr osa; + if (osmo_sockaddr_str_to_osa(addr, &osa)) + return NULL; + return pfcp_tool_have_core_udp_port_by_osa(&osa, fallback_port); +} + +static bool add_session_to_gtp_flow(struct gtp_flood *gf, struct pfcp_tool_session *session) +{ + struct range *r; + struct gtp_flood_flow_cfg cfg = {}; + const struct pfcp_tool_gtp_tun *tun_access; + const struct pfcp_tool_gtp_tun *tun_core = NULL; + const struct osmo_sockaddr_str *ue_local_addr = NULL; + + switch (session->kind) { + case UP_GTP_U_TUNEND: + tun_access = &session->tunend.access; + ue_local_addr = &session->tunend.core.ue_local_addr; + break; + case UP_GTP_U_TUNMAP: + tun_access = &session->tunmap.access; + tun_core = &session->tunmap.core; + break; + default: + OSMO_ASSERT(false); + } + + /* The 'local' and 'remote' naming clashes here. struct pfcp_tool_gtp_tun names its items from the UPF's point + * of view: so the GTP port of osmo-pfcp-tool is 'remote'. + * Here we are setting up a port to emit GTP from osmo-pfcp-tool, the same port is called 'gtp_local'. + */ + cfg.gtp_local = pfcp_tool_have_local_udp_port_by_str(&tun_access->remote.addr, 2152); + if (!cfg.gtp_local) { + LOGP(DLGLOBAL, LOGL_ERROR, "Cannot set up GTP flow for session, failed to setup local GTP port\n"); + return false; + } + + /* The 'local' and 'remote' naming clashes here. struct pfcp_tool_gtp_tun names its items from the UPF's point + * of view: so the GTP port of UPF is 'local'. + * Here we are reading the GTP port that the UPF has opened to receive GTP traffic; the same port is called + * 'gtp_remote' in cfg, which is remote from osmo-pfcp-tool's point of view. + */ + if (osmo_sockaddr_str_to_osa(&tun_access->local.addr, &cfg.gtp_remote)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Cannot set up GTP flow for session, failed to identify UPF's GTP port\n"); + return false; + } + if (!osmo_sockaddr_port(&cfg.gtp_remote.u.sa)) + osmo_sockaddr_set_port(&cfg.gtp_remote.u.sa, 2152); + cfg.gtp_remote_teid = tun_access->local.teid; + + if (ue_local_addr) { + osmo_sockaddr_str_to_osa(ue_local_addr, &cfg.payload_src); + } else if (pfcp_tool_next_ue_addr(&cfg.payload_src)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Cannot set up GTP flow for session, failed to identify UE's IP address\n"); + return false; + } + r = &g_pfcp_tool->gtp.payload.source.udp_port_range; + range_next(r); + osmo_sockaddr_set_port(&cfg.payload_src.u.sa, range_val_get_int(&r->next)); + + r = &g_pfcp_tool->gtp.payload.target.ip_range; + range_next(r); + range_val_get_addr(&cfg.payload_dst, &r->next); + r = &g_pfcp_tool->gtp.payload.target.udp_port_range; + range_next(r); + osmo_sockaddr_set_port(&cfg.payload_dst.u.sa, range_val_get_int(&r->next)); + + cfg.num_packets = g_pfcp_tool->gtp.flood.packets_per_flow; + + if (g_pfcp_tool->gtp.payload.append_info) { + cfg.append_payload_info = true; + cfg.payload_info = (struct gtp_flood_payload_info){}; + if (tun_core) + cfg.payload_info.return_teid = htonl(tun_core->local.teid); + } + + for (int i = 0; i < g_pfcp_tool->gtp.flood.flows_per_session; i++) + gtp_flood_add_flow(gf, &cfg); + + return true; +} + +void pfcp_tool_gtp_flood_start(void) +{ + struct gtp_flood *gf; + struct pfcp_tool_peer *peer; + struct udp_port *port; + + if (g_pfcp_tool->gtp.flood.state) { + LOGP(DLGLOBAL, LOGL_ERROR, + "another 'gtp flood' is still running; currently we can run only one gtp flood at a time\n"); + return; + } + + gf = g_pfcp_tool->gtp.flood.state = gtp_flood_alloc(g_pfcp_tool, &g_pfcp_tool->gtp.flood.cfg); + if (!gf) + return; + + /* add transmitter flows */ + llist_for_each_entry(peer, &g_pfcp_tool->peers, entry) { + struct pfcp_tool_session *session; + llist_for_each_entry(session, &peer->sessions, entry) { + add_session_to_gtp_flow(gf, session); + } + } + + unsigned int rx_flows_added = 0; + /* add listener flows, one per local UDP port (like a GTP port from a 'gtp local 1.2.3.4' vty command) */ + llist_for_each_entry(port, &g_pfcp_tool->gtp.gtp_local_addrs, entry) { + struct gtp_flood_flow_cfg cfg = { + .rx = true, + .gtp_local = port, + }; + gtp_flood_add_flow(gf, &cfg); + rx_flows_added++; + } + /* If there's still free Rx workers, use them to spread the load among the Rx flows: */ + do { + llist_for_each_entry(port, &g_pfcp_tool->gtp.gtp_local_addrs, entry) { + if (rx_flows_added >= g_pfcp_tool->gtp.flood.cfg.num_rx_workers) + goto done_filling_rx_workers; + struct gtp_flood_flow_cfg cfg = { + .rx = true, + .gtp_local = port, + }; + gtp_flood_add_flow(gf, &cfg); + rx_flows_added++; + } + } while (true); +done_filling_rx_workers: + + gtp_flood_start(gf); +} diff --git a/src/osmo-pfcp-tool/pfcp_tool_vty.c b/src/osmo-pfcp-tool/pfcp_tool_vty.c new file mode 100644 index 0000000..6348850 --- /dev/null +++ b/src/osmo-pfcp-tool/pfcp_tool_vty.c @@ -0,0 +1,1514 @@ +/* osmo-pfcp-tool interface to quagga VTY */ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <stdlib.h> +#include <unistd.h> + +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/core/socket.h> + +#include <osmocom/pfcp/pfcp_endpoint.h> +#include <osmocom/pfcp/pfcp_msg.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> + +#include <osmocom/pfcptool/pfcp_tool.h> + +DEFUN(c_local_addr, c_local_addr_cmd, + "local-addr IP_ADDR", + "Set the local IP address to bind on for PFCP; see also 'listen'\n" + "IP address\n") +{ + if (g_pfcp_tool->ep != NULL) { + vty_out(vty, "Already listening on %s%s", + osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)), + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(g_pfcp_tool, &g_pfcp_tool->vty_cfg.local_ip, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(c_listen, c_listen_cmd, + "listen", + "Bind local PFCP port and listen; see also 'local-addr'\n") +{ + struct osmo_sockaddr_str local_addr; + struct osmo_pfcp_endpoint_cfg cfg; + int rc; + + OSMO_ASSERT(g_pfcp_tool); + if (g_pfcp_tool->ep != NULL) { + vty_out(vty, "Already listening on %s%s", + osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)), + VTY_NEWLINE); + return CMD_WARNING; + } + + cfg = (struct osmo_pfcp_endpoint_cfg){ + .rx_msg_cb = pfcp_tool_rx_msg, + }; + + /* Translate address string from VTY config to osmo_sockaddr: first read into osmo_sockaddr_str, then write to + * osmo_sockaddr. */ + osmo_sockaddr_str_from_str(&local_addr, g_pfcp_tool->vty_cfg.local_ip, + g_pfcp_tool->vty_cfg.local_port); + osmo_sockaddr_str_to_sockaddr(&local_addr, &cfg.local_addr.u.sas); + + /* Also use this address as the local PFCP Node Id */ + osmo_pfcp_ie_node_id_from_osmo_sockaddr(&cfg.local_node_id, &cfg.local_addr); + + g_pfcp_tool->ep = osmo_pfcp_endpoint_create(g_pfcp_tool, &cfg); + if (!g_pfcp_tool->ep) { + vty_out(vty, "Failed to allocate PFCP endpoint.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_pfcp_endpoint_set_seq_nr_state(g_pfcp_tool->ep, rand()); + + rc = osmo_pfcp_endpoint_bind(g_pfcp_tool->ep); + if (rc) { + vty_out(vty, "Failed to bind PFCP endpoint on %s: %s%s", + osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)), + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(c_sleep, c_sleep_cmd, + "sleep <0-999999> [<0-999>]", + "Let some time pass\n" + "Seconds to wait\n" "Additional milliseconds to wait\n") +{ + int secs = atoi(argv[0]); + int msecs = 0; + struct osmo_timer_list t = {}; + if (argc > 1) + msecs = atoi(argv[1]); + + vty_out(vty, "zzZ %d.%03ds...%s", secs, msecs, VTY_NEWLINE); + vty_flush(vty); + + osmo_timer_setup(&t, NULL, NULL); + osmo_timer_schedule(&t, secs, msecs * 1000); + + /* Still operate the message pump while waiting for time to pass */ + while (t.active && !osmo_select_shutdown_done()) { + if (pfcp_tool_mainloop(0) == -1) + break; + } + + osmo_timer_del(&t); + vty_out(vty, "...zzZ %d.%03ds%s", secs, msecs, VTY_NEWLINE); + vty_flush(vty); + return CMD_SUCCESS; +} + +DEFUN(c_date, c_date_cmd, + "date", + "print a timestamp\n") +{ + struct timeval tv = {}; + struct tm tm = {}; + + osmo_gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &tm); + + vty_out(vty, "%04d-%02d-%02d,%02d:%02d:%02d.%03d%s", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (int)(tv.tv_usec / 1000), VTY_NEWLINE); + vty_flush(vty); + return CMD_SUCCESS; +} + +static bool parse_ip_to_range_val(struct vty *vty, struct range_val *val, const char *ip_addr_str) +{ + struct osmo_sockaddr_str a; + struct osmo_sockaddr osa; + if (osmo_sockaddr_str_from_str(&a, ip_addr_str, 0) + || osmo_sockaddr_str_to_osa(&a, &osa)) { + vty_out(vty, "%% Error: invalid IP address: '%s'%s", ip_addr_str, VTY_NEWLINE); + return false; + } + range_val_set_addr(val, &osa); + return true; +} + +static bool parse_ip_range(struct vty *vty, struct range *dst, const char *argv[]) +{ + return parse_ip_to_range_val(vty, &dst->first, argv[0]) + && parse_ip_to_range_val(vty, &dst->last, argv[1]); +} + +#define IP_RANGE_STR \ + "Set a range with first and last address\n" \ + "First IP address in the range, 1.2.3.4 or 1:2:3::4\n" \ + "Last IP address in the range, 1.2.3.4 or 1:2:3::4\n" + +DEFUN(c_ue_ip_range, c_ue_ip_range_cmd, + "ue ip range IP_ADDR_FIRST IP_ADDR_LAST", + "UE\n" + "Set the IP address range used for the UE IP addresses\n" + IP_RANGE_STR) +{ + if (!parse_ip_range(vty, &g_pfcp_tool->ue.ip_range, &argv[0])) + return CMD_WARNING; + return CMD_SUCCESS; +} + +#define GTP_STR "Configure GTP\n" +#define GTP_IP_STR "Add a GTP IP address\n" +#define IP_ADDR_STR "IP address, 1.2.3.4 or 1:2:3:4::1\n" + +DEFUN(c_gtp_local, c_gtp_local_cmd, + "gtp local IP_ADDR [<1-65535>]", + GTP_STR + "Add a local GTP port, to emit GTP traffic from\n" + IP_ADDR_STR + "Specific UDP port to use, 2152 if omitted\n") +{ + const char *ip_str = argv[0]; + uint16_t port_nr; + struct osmo_sockaddr_str addr_str; + struct udp_port *p; + + if (argc > 1) + port_nr = atoi(argv[1]); + else + port_nr = 2152; + + if (osmo_sockaddr_str_from_str(&addr_str, ip_str, port_nr)) + goto invalid_ip; + vty_out(vty, "local GTP port: " OSMO_SOCKADDR_STR_FMT "%s", OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(&addr_str), VTY_NEWLINE); + p = pfcp_tool_have_local_udp_port_by_str(&addr_str, port_nr); + if (!p) + goto invalid_ip; + return CMD_SUCCESS; + +invalid_ip: + vty_out(vty, "%% Error: invalid IP address: '%s'%s", ip_str, VTY_NEWLINE); + return CMD_WARNING; +} + +DEFUN(c_gtp_core, c_gtp_core_cmd, + "gtp core IP_ADDR [<1-65535>]", + GTP_STR + "Add a core GTP port, to emit GTP traffic from\n" + IP_ADDR_STR + "Specific UDP port to use, 2152 if omitted\n") +{ + const char *ip_str = argv[0]; + uint16_t port_nr; + struct osmo_sockaddr_str addr_str; + struct udp_port *p; + + if (argc > 1) + port_nr = atoi(argv[1]); + else + port_nr = 2152; + + if (osmo_sockaddr_str_from_str(&addr_str, ip_str, port_nr)) + goto invalid_ip; + vty_out(vty, "core GTP port: " OSMO_SOCKADDR_STR_FMT "%s", OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(&addr_str), VTY_NEWLINE); + p = pfcp_tool_have_core_udp_port_by_str(&addr_str, port_nr); + if (!p) + goto invalid_ip; + return CMD_SUCCESS; + +invalid_ip: + vty_out(vty, "%% Error: invalid IP address: '%s'%s", ip_str, VTY_NEWLINE); + return CMD_WARNING; +} + +static struct cmd_node gtp_flood_node = { + GTP_FLOOD_NODE, + "%s(gtp-flood)# ", + 1, +}; + +DEFUN(gtp_flood, gtp_flood_cmd, + "gtp flood", + GTP_STR + "Setup GTP traffic\n") +{ + vty->node = GTP_FLOOD_NODE; + return CMD_SUCCESS; +} + +DEFUN(gtpf_workers, gtpf_workers_cmd, + "workers <1-999>", + "Number of worker threads (Rx and Tx) to launch, to emit GTP traffic from\n" + "Number\n") +{ + g_pfcp_tool->gtp.flood.cfg.num_rx_workers = atoi(argv[0]); + g_pfcp_tool->gtp.flood.cfg.num_tx_workers = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(gtpf_rx_workers, gtpf_rx_workers_cmd, + "rx-workers <1-999>", + "Number of Rx worker threads to launch, to receive GTP traffic\n" + "Number\n") +{ + g_pfcp_tool->gtp.flood.cfg.num_rx_workers = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(gtpf_tx_workers, gtpf_tx_workers_cmd, + "tx-workers <1-999>", + "Number of Tx worker threads to launch, to emit GTP traffic from\n" + "Number\n") +{ + g_pfcp_tool->gtp.flood.cfg.num_tx_workers = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(gtpf_io_uring_queue_size, gtpf_io_uring_queue_size_cmd, + "io-uring queue-size <1-65536>", + "Fine-tune io-uring usage for optimal GTP packet flooding\n" + "Maximum number of packets to submit for sending simultaneously.\n" + "Number\n") +{ + g_pfcp_tool->gtp.flood.cfg.queue_size = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(gtpf_flows, gtpf_flows_cmd, + "flows-per-session <1-999999>", + "Set number of GTP packet flows emitted per PFCP session's GTP tunnel\n" + "Number\n") +{ + g_pfcp_tool->gtp.flood.flows_per_session = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(gtpf_packets, gtpf_packets_cmd, + "packets-per-flow (<1-2000000000>|infinite)", + "Set number of GTP packet flows emitted per PFCP session's GTP tunnel\n" + "Number\n") +{ + unsigned int val; + if (!strcmp("infinite", argv[0])) + val = 0; + else + val = atoi(argv[0]); + g_pfcp_tool->gtp.flood.packets_per_flow = val; + return CMD_SUCCESS; +} + +#define PAYLOAD_STR "Configure GTP payload\n" + +#define RANGE_STR \ + "Set a range to cycle through\n" \ + "First value of the range\n" \ + "Last value of the range\n" + +DEFUN(gtpf_payload_src_port, gtpf_payload_src_port_cmd, + "payload source port udp range <1-65535> <1-65535>", + PAYLOAD_STR + "Source port of payload packets. Note: the source IP will always be the UE IP for that session.\n" + "Port\n" + "UDP port\n" + RANGE_STR) +{ + range_val_set_int(&g_pfcp_tool->gtp.payload.source.udp_port_range.first, + atoi(argv[0])); + range_val_set_int(&g_pfcp_tool->gtp.payload.source.udp_port_range.last, + atoi(argv[1])); + return CMD_SUCCESS; +} + +#define PAYLOAD_TARGET_STR PAYLOAD_STR "Target of payload packets\n" + +DEFUN(gtpf_payload_target_ip, gtpf_payload_target_ip_cmd, + "payload target ip range IP_ADDR_FIRST IP_ADDR_LAST", + PAYLOAD_TARGET_STR + "IP address\n" + RANGE_STR) +{ + if (!parse_ip_range(vty, &g_pfcp_tool->gtp.payload.target.ip_range, &argv[0])) + return CMD_WARNING; + return CMD_SUCCESS; +} + +DEFUN(gtpf_payload_target_port, gtpf_payload_target_port_cmd, + "payload target port udp range <1-65535> <1-65535>", + PAYLOAD_STR + "Target port of payload packets\n" + "Port\n" + "UDP port\n" + RANGE_STR) +{ + range_val_set_int(&g_pfcp_tool->gtp.payload.target.udp_port_range.first, + atoi(argv[0])); + range_val_set_int(&g_pfcp_tool->gtp.payload.target.udp_port_range.last, + atoi(argv[1])); + return CMD_SUCCESS; +} + +DEFUN(gtpf_payload_append_payload_info, gtpf_payload_append_payload_info_cmd, + "payload append-info", + PAYLOAD_STR + "Append info about the data stream to the emitted payload." + " In a tunmap scenario, append the UPF's core side's TEID to the end of the generated payload," + " to allow the remote receiver to echo back payload using the correct TEID, without the need" + " for out-of-band communication (see osmo-udp-responder)\n") +{ + g_pfcp_tool->gtp.payload.append_info = true; + return CMD_SUCCESS; +} + +DEFUN(gtpf_slew, gtpf_slew_cmd, + "slew <0-1000000000>", + "Wait N microseconds after each io_uring transmission submission\n" + "microseconds to wait (1000000 == 1 second)\n") +{ + g_pfcp_tool->gtp.flood.cfg.slew_us = atoi(argv[0]); + return CMD_SUCCESS; +} + +static struct cmd_node peer_node = { + PEER_NODE, + "%s(peer)# ", + 1, +}; + +DEFUN(peer, peer_cmd, + "pfcp-peer REMOTE_ADDR", + "Enter the 'peer' node for the given remote address\n" + "Remote PFCP peer's IP address\n") +{ + struct pfcp_tool_peer *peer; + struct osmo_sockaddr_str remote_addr_str; + struct osmo_sockaddr remote_addr; + + osmo_sockaddr_str_from_str(&remote_addr_str, argv[0], OSMO_PFCP_PORT); + osmo_sockaddr_str_to_sockaddr(&remote_addr_str, (struct sockaddr_storage *)&remote_addr); + + peer = pfcp_tool_peer_find_or_create(&remote_addr); + + vty->index = peer; + vty->node = PEER_NODE; + + return CMD_SUCCESS; +} + +#define TX_STR "Send a PFCP message to a peer\n" + +DEFUN(peer_tx_heartbeat, peer_tx_heartbeat_cmd, + "tx heartbeat", + TX_STR "Send a Heartbeat Request\n") +{ + struct pfcp_tool_peer *peer = vty->index; + int rc; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "Tx Heartbeat Request to %s%s", + osmo_sockaddr_to_str_c(OTC_SELECT, &peer->remote_addr), VTY_NEWLINE); + + rc = osmo_pfcp_endpoint_tx_heartbeat_req(g_pfcp_tool->ep, &peer->remote_addr); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(peer_tx_assoc_setup_req, peer_tx_assoc_setup_req_cmd, + "tx assoc-setup-req", + TX_STR "Send an Association Setup Request\n") +{ + struct pfcp_tool_peer *peer = vty->index; + int rc; + struct osmo_pfcp_msg *m; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_ASSOC_SETUP_REQ); + m->ies.assoc_setup_req.recovery_time_stamp = osmo_pfcp_endpoint_get_recovery_timestamp(g_pfcp_tool->ep); + + m->ies.assoc_setup_req.cp_function_features_present = true; + osmo_pfcp_bits_set(m->ies.assoc_setup_req.cp_function_features.bits, OSMO_PFCP_CP_FEAT_BUNDL, true); + + rc = peer_tx(peer, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(peer_retrans_req, peer_retrans_req_cmd, + "retrans (req|resp)", + "Retransmit the last sent message\n" "Retransmit the last sent PFCP Request\n" + "Retransmit the last sent PFCP Response\n") +{ + struct pfcp_tool_peer *peer = vty->index; + int rc; + struct osmo_pfcp_msg *m; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Allocate using API function to have the usual talloc destructor set up, then copy the last request or + * response over it. */ + m = osmo_pfcp_msg_alloc_rx(OTC_SELECT, &peer->remote_addr); + if (strcmp(argv[0], "req") == 0) + *m = peer->last_req; + else + *m = peer->last_resp; + + OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "retrans %s\n", argv[0]); + + rc = osmo_pfcp_endpoint_tx_data(g_pfcp_tool->ep, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +static struct cmd_node session_node = { + SESSION_NODE, + "%s(session)# ", + 1, +}; + +#define SESSION_STR "Enter the 'session' node for the given SEID\n" +#define TUNEND_STR "Set up GTP tunnel encapsulation/decapsulation (default)\n" +#define TUNMAP_STR "Set up GTP tunnel mapping\n" +#define SEID_STR "local Session Endpoint ID\n" + +DEFUN(session, session_cmd, + "session [(tunend|tunmap)] [<0-18446744073709551615>]", + SESSION_STR TUNEND_STR TUNMAP_STR SEID_STR) +{ + struct pfcp_tool_peer *peer = vty->index; + struct pfcp_tool_session *session; + enum up_gtp_action_kind kind = UP_GTP_U_TUNEND; + + if (argc > 0 && !strcmp(argv[0], "tunmap")) + kind = UP_GTP_U_TUNMAP; + + if (argc > 1) + session = pfcp_tool_session_find_or_create(peer, atoll(argv[1]), kind); + else + session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), kind); + + vty->index = session; + vty->node = SESSION_NODE; + + return CMD_SUCCESS; +} + +/* legacy compat: "tunend" was originally named "endecaps" */ +DEFUN_CMD_ELEMENT(session, session_endecaps_cmd, + "session (endecaps) [<0-18446744073709551615>]", + SESSION_STR TUNEND_STR SEID_STR, CMD_ATTR_HIDDEN, 0); + +DEFUN(s_ue, s_ue_cmd, + "ue ip A.B.C.D", + "Setup the UE as it appears towards the Core network in plain IP traffic\n" + "IP address assigned to the UE\n") +{ + struct pfcp_tool_session *session = vty->index; + + if (session->kind != UP_GTP_U_TUNEND) { + vty_out(vty, "%% Error: 'ue ip' makes no sense in a 'tunmap' session%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (osmo_sockaddr_str_from_str2(&session->tunend.core.ue_local_addr, argv[0])) { + vty_out(vty, "Error setting UE IP address%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +#define GTP_ACCESS_CORE_STRS \ + "Setup GTP\n" \ + "Setup GTP towards ACCESS (towards the radio network and the actual UE)\n" \ + "Setup GTP towards CORE (towards the internet)\n" +#define GTP_LOCAL_STR "Setup GTP on the local side (UPF's local GTP endpoint)\n" +#define GTP_REMOTE_STR "Setup GTP on the remote side (UPF's remote GTP peer)\n" +#define F_TEID_STR "Set the fully-qualified TEID, i.e. GTP IP address and TEID\n" + +DEFUN(s_f_teid, s_f_teid_cmd, + "gtp (access|core) (local|remote) f-teid A.B.C.D <0-4294967295>", + GTP_ACCESS_CORE_STRS + GTP_LOCAL_STR GTP_REMOTE_STR + F_TEID_STR + "GTP peer IP address\n" + "GTP TEID\n") +{ + struct pfcp_tool_session *session = vty->index; + const char *tun_side = argv[0]; + const char *local_remote = argv[1]; + const char *addr_str = argv[2]; + const char *teid_str = argv[3]; + struct pfcp_tool_gtp_tun_ep *dst; + + switch (session->kind) { + case UP_GTP_U_TUNEND: + if (!strcmp(tun_side, "access")) { + if (!strcmp(local_remote, "local")) + dst = &session->tunend.access.local; + else + dst = &session->tunend.access.remote; + } else { + vty_out(vty, "%% Error: 'gtp core (local|remote) f-teid': 'tunend' only has GTP on" + " the 'access' side%s", VTY_NEWLINE); + return CMD_WARNING; + } + break; + case UP_GTP_U_TUNMAP: + if (!strcmp(tun_side, "access")) { + if (!strcmp(local_remote, "local")) + dst = &session->tunmap.access.local; + else + dst = &session->tunmap.access.remote; + } else { + if (!strcmp(local_remote, "local")) + dst = &session->tunmap.core.local; + else + dst = &session->tunmap.core.remote; + } + break; + default: + OSMO_ASSERT(0); + } + + if (osmo_sockaddr_str_from_str2(&dst->addr, addr_str)) { + vty_out(vty, "Error setting GTP IP address from %s%s", + osmo_quote_cstr_c(OTC_SELECT, addr_str, -1), VTY_NEWLINE); + return CMD_WARNING; + } + dst->teid = atoi(teid_str); + return CMD_SUCCESS; +} + +DEFUN(s_f_teid_choose, s_f_teid_choose_cmd, + "gtp (access|core) local f-teid choose", + GTP_ACCESS_CORE_STRS + GTP_LOCAL_STR + F_TEID_STR + "Send F-TEID with CHOOSE=1, i.e. the UPF shall return the local F-TEID in a PFCP Created PDR IE\n") +{ + struct pfcp_tool_session *session = vty->index; + const char *tun_side = argv[0]; + struct pfcp_tool_gtp_tun_ep *dst; + + switch (session->kind) { + case UP_GTP_U_TUNEND: + if (!strcmp(tun_side, "access")) { + dst = &session->tunend.access.local; + } else { + vty_out(vty, "%% Error: 'gtp core local choose': 'tunend' only has GTP on" + " the 'access' side%s", VTY_NEWLINE); + return CMD_WARNING; + } + break; + case UP_GTP_U_TUNMAP: + if (!strcmp(tun_side, "access")) + dst = &session->tunmap.access.local; + else + dst = &session->tunmap.core.local; + break; + default: + OSMO_ASSERT(0); + } + + *dst = (struct pfcp_tool_gtp_tun_ep){}; + return CMD_SUCCESS; +} + +enum pdr_id_fixed { + PDR_ID_CORE = 1, + PDR_ID_ACCESS = 2, +}; + +const char * const gtp_ip_core = "10.99.0.1"; +const char * const fallback_gtp_ip_access = "10.99.0.2"; + +int session_tunend_tx_est_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb) +{ + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + struct osmo_pfcp_ie_f_teid f_teid_access_local; + struct osmo_pfcp_ie_outer_header_creation ohc_access; + struct osmo_pfcp_ie_apply_action aa = {}; + struct osmo_sockaddr ue_addr; + struct osmo_pfcp_ie_f_seid cp_f_seid; + + OSMO_ASSERT(session->kind == UP_GTP_U_TUNEND); + + if (!g_pfcp_tool->ep) { + LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n"); + return CMD_WARNING; + } + + if (forw) + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); + else + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); + +#define STR_TO_ADDR(DST, SRC) do { \ + if (osmo_sockaddr_str_to_sockaddr(&SRC, &DST.u.sas)) { \ + LOGP(DLGLOBAL, LOGL_ERROR, "Error in " #SRC ": " OSMO_SOCKADDR_STR_FMT "\n", \ + OSMO_SOCKADDR_STR_FMT_ARGS(&SRC)); \ + return CMD_WARNING; \ + } \ + } while (0) + + STR_TO_ADDR(ue_addr, session->tunend.core.ue_local_addr); + + if (session->tunend.access.local.teid == 0) { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .choose_flag = true, + .choose = { + .ipv4_addr = true, + }, + }; + } else { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .fixed = { + .teid = session->tunend.access.local.teid, + .ip_addr = { + .v4_present = true, + }, + }, + }; + STR_TO_ADDR(f_teid_access_local.fixed.ip_addr.v4, session->tunend.access.local.addr); + } + + ohc_access = (struct osmo_pfcp_ie_outer_header_creation){ + .teid_present = true, + .teid = session->tunend.access.remote.teid, + .ip_addr.v4_present = true, + }; + osmo_pfcp_bits_set(ohc_access.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); + STR_TO_ADDR(ohc_access.ip_addr.v4, session->tunend.access.remote.addr); + + cp_f_seid = (struct osmo_pfcp_ie_f_seid){ + .seid = session->cp_seid, + }; + osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)); + + m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_EST_REQ); + + m->ctx.resp_cb = resp_cb; + m->ctx.priv = session; + + m->h.seid_present = true; + /* the UPF has yet to assign a SEID for itself, no matter what SEID we (the CPF) use for this session */ + m->h.seid = 0; + /* GTP encapsulation decapsulation: remove header from ACCESS to CORE, add header from CORE towards ACCESS */ + m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){ + .node_id = m->ies.session_est_req.node_id, + .cp_f_seid_present = true, + .cp_f_seid = cp_f_seid, + .create_pdr_count = 2, + .create_pdr = { + { + .pdr_id = PDR_ID_CORE, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_CORE, + .ue_ip_address_present = true, + .ue_ip_address = { + .ip_is_destination = true, + .ip_addr = { + .v4_present = true, + .v4 = ue_addr, + }, + }, + }, + .far_id_present = true, + .far_id = 1, + }, + { + .pdr_id = PDR_ID_ACCESS, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS, + .local_f_teid_present = true, + .local_f_teid = f_teid_access_local, + }, + .outer_header_removal_present = true, + .outer_header_removal = { + .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, + }, + .far_id_present = true, + .far_id = 2, + }, + }, + .create_far_count = 2, + .create_far = { + { + .far_id = 1, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_ACCESS, + .outer_header_creation_present = true, + .outer_header_creation = ohc_access, + }, + .apply_action = aa, + }, + { + .far_id = 2, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_CORE, + }, + .apply_action = aa, + }, + }, + }; + + rc = peer_tx(peer, m); + if (rc) + return CMD_WARNING; + return CMD_SUCCESS; +} + +int session_tunmap_tx_est_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb) +{ + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + + struct osmo_pfcp_ie_f_seid cp_f_seid; + + struct osmo_pfcp_ie_f_teid f_teid_access_local; + struct osmo_pfcp_ie_outer_header_creation ohc_access; + + struct osmo_pfcp_ie_f_teid f_teid_core_local; + struct osmo_pfcp_ie_outer_header_creation ohc_core; + + struct osmo_pfcp_ie_apply_action aa = {}; + + if (!g_pfcp_tool->ep) { + LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n"); + return CMD_WARNING; + } + + if (forw) + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); + else + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); + + if (session->tunmap.access.local.teid == 0) { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .choose_flag = true, + .choose = { + .ipv4_addr = true, + }, + }; + } else { + f_teid_access_local = (struct osmo_pfcp_ie_f_teid){ + .fixed = { + .teid = session->tunmap.access.local.teid, + .ip_addr = { + .v4_present = true, + .v4 = osmo_pfcp_endpoint_get_cfg(g_pfcp_tool->ep)->local_addr, + }, + }, + }; + STR_TO_ADDR(f_teid_access_local.fixed.ip_addr.v4, session->tunmap.access.local.addr); + } + + ohc_access = (struct osmo_pfcp_ie_outer_header_creation){ + .teid_present = true, + .teid = session->tunmap.access.remote.teid, + .ip_addr.v4_present = true, + }; + osmo_pfcp_bits_set(ohc_access.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); + STR_TO_ADDR(ohc_access.ip_addr.v4, session->tunmap.access.remote.addr); + + if (session->tunmap.core.local.teid == 0) { + f_teid_core_local = (struct osmo_pfcp_ie_f_teid){ + .choose_flag = true, + .choose = { + .ipv4_addr = true, + }, + }; + } else { + f_teid_core_local = (struct osmo_pfcp_ie_f_teid){ + .fixed = { + .teid = session->tunmap.core.local.teid, + .ip_addr = { + .v4_present = true, + }, + }, + }; + STR_TO_ADDR(f_teid_core_local.fixed.ip_addr.v4, session->tunmap.core.local.addr); + } + ohc_core = (struct osmo_pfcp_ie_outer_header_creation){ + .teid_present = true, + .teid = session->tunmap.core.remote.teid, + .ip_addr.v4_present = true, + }; + osmo_pfcp_bits_set(ohc_core.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); + STR_TO_ADDR(ohc_core.ip_addr.v4, session->tunmap.core.remote.addr); + + cp_f_seid = (struct osmo_pfcp_ie_f_seid){ + .seid = session->cp_seid, + }; + osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)); + + m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_EST_REQ); + + m->ctx.resp_cb = resp_cb; + m->ctx.priv = session; + + m->h.seid_present = true; + m->h.seid = 0; + /* GTP tunmap: remove header from both directions, and add header in both directions */ + m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){ + .node_id = m->ies.session_est_req.node_id, + .cp_f_seid_present = true, + .cp_f_seid = cp_f_seid, + .create_pdr_count = 2, + .create_pdr = { + { + .pdr_id = PDR_ID_CORE, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_CORE, + .local_f_teid_present = true, + .local_f_teid = f_teid_core_local, + }, + .outer_header_removal_present = true, + .outer_header_removal = { + .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, + }, + .far_id_present = true, + .far_id = 1, + }, + { + .pdr_id = PDR_ID_ACCESS, + .precedence = 255, + .pdi = { + .source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS, + .local_f_teid_present = true, + .local_f_teid = f_teid_access_local, + }, + .outer_header_removal_present = true, + .outer_header_removal = { + .desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, + }, + .far_id_present = true, + .far_id = 2, + }, + }, + .create_far_count = 2, + .create_far = { + { + .far_id = 1, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_ACCESS, + .outer_header_creation_present = true, + .outer_header_creation = ohc_access, + }, + .apply_action = aa, + }, + { + .far_id = 2, + .forw_params_present = true, + .forw_params = { + .destination_iface = OSMO_PFCP_DEST_IFACE_CORE, + .outer_header_creation_present = true, + .outer_header_creation = ohc_core, + }, + .apply_action = aa, + }, + }, + }; + + rc = peer_tx(peer, m); + if (rc) + return CMD_WARNING; + return CMD_SUCCESS; +} + +DEFUN(session_tx_est_req, session_tx_est_req_cmd, + "tx session-est-req [(forw|drop)]", + TX_STR "Send a Session Establishment Request\n" + "Set FAR to FORW = 1 (default)\n" + "Set FAR to DROP = 1\n") +{ + struct pfcp_tool_session *session = vty->index; + bool forw = (argc == 0 || !strcmp("forw", argv[0])); + switch (session->kind) { + case UP_GTP_U_TUNEND: + return session_tunend_tx_est_req(session, forw, NULL); + case UP_GTP_U_TUNMAP: + return session_tunmap_tx_est_req(session, forw, NULL); + default: + vty_out(vty, "unknown gtp action%s", VTY_NEWLINE); + return CMD_WARNING; + } +} + +int session_tunmap_tx_mod_req(struct pfcp_tool_session *session, bool forw, osmo_pfcp_resp_cb resp_cb) +{ + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + struct osmo_pfcp_ie_apply_action aa = {}; + struct osmo_pfcp_ie_f_seid cp_f_seid; + + if (!g_pfcp_tool->ep) { + LOGP(DLGLOBAL, LOGL_ERROR, "PFCP endpoint not configured\n"); + return CMD_WARNING; + } + + if (forw) + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); + else + osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_DROP, true); + + cp_f_seid = (struct osmo_pfcp_ie_f_seid){ + .seid = session->cp_seid, + }; + osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, osmo_pfcp_endpoint_get_local_addr(g_pfcp_tool->ep)); + + m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_MOD_REQ); + + m->ctx.resp_cb = resp_cb; + m->ctx.priv = session; + + m->h.seid_present = true; + m->h.seid = session->up_f_seid.seid; + m->ies.session_mod_req = (struct osmo_pfcp_msg_session_mod_req){ + .cp_f_seid_present = true, + .cp_f_seid = cp_f_seid, + .upd_far_count = 2, + .upd_far = { + { + .far_id = 1, + .apply_action_present = true, + .apply_action = aa, + }, + { + .far_id = 2, + .apply_action_present = true, + .apply_action = aa, + }, + }, + }; + + rc = peer_tx(peer, m); + if (rc) + return CMD_WARNING; + return CMD_SUCCESS; +} + +DEFUN(session_tx_mod_req, session_tx_mod_req_cmd, + "tx session-mod-req far [(forw|drop)]", + TX_STR "Send a Session Modification Request\n" + "Set FAR to FORW = 1\n" + "Set FAR to DROP = 1\n") +{ + struct pfcp_tool_session *session = vty->index; + bool forw = (argc == 0 || !strcmp("forw", argv[0])); + int rc = session_tunmap_tx_mod_req(session, forw, NULL); + if (rc != CMD_SUCCESS) + vty_out(vty, "Failed to send Session Modification Request%s", VTY_NEWLINE); + return rc; +} + +DEFUN(session_tx_del_req, session_tx_del_req_cmd, + "tx session-del-req", + TX_STR "Send a Session Deletion Request\n") +{ + struct pfcp_tool_session *session = vty->index; + struct pfcp_tool_peer *peer = session->peer; + int rc; + struct osmo_pfcp_msg *m; + + if (!g_pfcp_tool->ep) { + vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_DEL_REQ); + m->h.seid_present = true; + m->h.seid = session->up_f_seid.seid; + + rc = peer_tx(peer, m); + if (rc) { + vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +/* N SESSIONS */ + +static int responses_pending = 0; + +DEFUN(wait_responses, wait_responses_cmd, + "wait responses", + "Let some time pass until events have occurred\n" + "Wait for all PFCP responses for pending PFCP requests\n") +{ + if (!responses_pending) { + vty_out(vty, "no responses pending, not waiting.%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + + vty_out(vty, "waiting for %d responses...%s", responses_pending, VTY_NEWLINE); + vty_flush(vty); + + /* Still operate the message pump while waiting for time to pass */ + while (!osmo_select_shutdown_done()) { + if (pfcp_tool_mainloop(0) == -1) + break; + if (responses_pending == 0) + break; + } + + vty_out(vty, "...done waiting for responses%s", VTY_NEWLINE); + vty_flush(vty); + return CMD_SUCCESS; +} + +/* N SESSIONS TUNEND */ + +int one_session_mod_tunend_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg) +{ + responses_pending--; + return 0; +} + +int one_session_mod_tunend(struct pfcp_tool_session *session) +{ + int rc; + + rc = session_tunmap_tx_mod_req(session, true, one_session_mod_tunend_resp_cb); + if (rc == CMD_SUCCESS) + responses_pending++; + return rc; +} + +void est_resp_get_created_f_teid(struct pfcp_tool_gtp_tun_ep *dst, const struct osmo_pfcp_msg *rx_resp, uint16_t pdr_id) +{ + int i; + const struct osmo_pfcp_msg_session_est_resp *r; + if (rx_resp->h.message_type != OSMO_PFCP_MSGT_SESSION_EST_RESP) + return; + + r = &rx_resp->ies.session_est_resp; + + for (i = 0; i < r->created_pdr_count; i++) { + const struct osmo_pfcp_ie_created_pdr *p = &r->created_pdr[i]; + if (p->pdr_id != pdr_id) + continue; + if (!p->local_f_teid_present) + continue; + osmo_sockaddr_str_from_sockaddr(&dst->addr, + &p->local_f_teid.fixed.ip_addr.v4.u.sas); + dst->teid = p->local_f_teid.fixed.teid; + } +} + +int one_session_create_tunend_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg) +{ + struct pfcp_tool_session *session = req->ctx.priv; + enum osmo_pfcp_cause *cause; + const struct osmo_pfcp_msg_session_est_resp *r = NULL; + if (rx_resp) + r = &rx_resp->ies.session_est_resp; + + responses_pending--; + + if (errmsg) + LOGP(DLPFCP, LOGL_ERROR, "%s\n", errmsg); + + cause = rx_resp ? osmo_pfcp_msg_cause(rx_resp) : NULL; + if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) + return 0; + + /* store SEID */ + if (r && r->up_f_seid_present) + session->up_f_seid = r->up_f_seid; + /* store access local F-TEID */ + est_resp_get_created_f_teid(&session->tunend.access.local, rx_resp, PDR_ID_ACCESS); + + /* Success response, now continue with second step: Session Mod to set the CORE's remote side GTP */ + one_session_mod_tunend(session); + return 0; +} + +static int set_access_ran_tunend(struct pfcp_tool_gtp_tun_ep *dst) +{ + if (llist_empty(&g_pfcp_tool->gtp.gtp_local_addrs)) { + /* No local GTP ports configured, just set an arbitrary address. */ + if (osmo_sockaddr_str_from_str2(&dst->addr, fallback_gtp_ip_access)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Error setting Access side GTP IP address from %s\n", + osmo_quote_cstr_c(OTC_SELECT, fallback_gtp_ip_access, -1)); + return CMD_WARNING; + } + return CMD_SUCCESS; + } + + /* next local GTP address to emit from */ + struct udp_port *next; + next = g_pfcp_tool->gtp.gtp_local_addrs_next; + if (next) { + /* Move on by one gtp_local_addr. If past the end of the list, wrap back to the start below. */ + if (next->entry.next == &g_pfcp_tool->gtp.gtp_local_addrs) + next = NULL; + else + next = container_of(next->entry.next, struct udp_port, entry); + } + if (!next) + next = llist_first_entry_or_null(&g_pfcp_tool->gtp.gtp_local_addrs, struct udp_port, + entry); + OSMO_ASSERT(next); + g_pfcp_tool->gtp.gtp_local_addrs_next = next; + + if (osmo_sockaddr_str_from_osa(&dst->addr, &next->osa)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Error setting Access side GTP IP address from %s\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &next->osa)); + return CMD_WARNING; + } + dst->teid = pfcp_tool_new_teid(); + LOGP(DLGLOBAL, LOGL_DEBUG, "session picked gtp local " OSMO_SOCKADDR_STR_FMT " TEID 0x%x\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&dst->addr), dst->teid); + return CMD_SUCCESS; +} + +static int set_core_cn_tunend(struct pfcp_tool_gtp_tun_ep *dst) +{ + if (llist_empty(&g_pfcp_tool->gtp.gtp_core_addrs)) { + /* No core GTP ports configured, just set an arbitrary address. */ + if (osmo_sockaddr_str_from_str2(&dst->addr, fallback_gtp_ip_access)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Error setting Access side GTP IP address from %s\n", + osmo_quote_cstr_c(OTC_SELECT, fallback_gtp_ip_access, -1)); + return CMD_WARNING; + } + return CMD_SUCCESS; + } + + /* next core GTP address to emit from */ + struct udp_port *next; + next = llist_round_robin(&g_pfcp_tool->gtp.gtp_core_addrs, + &g_pfcp_tool->gtp.gtp_core_addrs_next, + struct udp_port, entry); + + if (osmo_sockaddr_str_from_osa(&dst->addr, &next->osa)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Error setting Access side GTP IP address from %s\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &next->osa)); + return CMD_WARNING; + } + dst->teid = pfcp_tool_new_teid(); + LOGP(DLGLOBAL, LOGL_DEBUG, "session picked gtp core " OSMO_SOCKADDR_STR_FMT " TEID 0x%x\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&dst->addr), dst->teid); + return CMD_SUCCESS; +} + +static int one_session_create_tunend(struct pfcp_tool_peer *peer) +{ + struct pfcp_tool_session *session; + struct pfcp_tool_tunend *te; + struct pfcp_tool_gtp_tun_ep *dst; + struct osmo_sockaddr osa; + int rc; + + session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), UP_GTP_U_TUNEND); + te = &session->tunend; + + /* Access: set remote GTP address from UPF's point of view. + * A local GTP port from osmo-pfcp-tool's point of view. */ + dst = &te->access.remote; + rc = set_access_ran_tunend(dst); + if (rc != CMD_SUCCESS) + return rc; + + /* Set UE address */ + te->access.local = (struct pfcp_tool_gtp_tun_ep){}; + pfcp_tool_next_ue_addr(&osa); + if (osmo_sockaddr_str_from_osa(&te->core.ue_local_addr, &osa)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Error setting UE address from %s\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &osa)); + return CMD_WARNING; + }; + + /* Send initial Session Establishment Request */ + rc = session_tunend_tx_est_req(session, false, one_session_create_tunend_resp_cb); + if (rc == CMD_SUCCESS) + responses_pending++; + return rc; +} + +/* N SESSIONS TUNMAP */ + +int one_session_mod_tunmap_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg) +{ + responses_pending--; + return 0; +} + +int one_session_mod_tunmap(struct pfcp_tool_session *session) +{ + struct pfcp_tool_gtp_tun_ep *dst; + int rc; + + dst = &session->tunmap.core.remote; + if (osmo_sockaddr_str_from_str2(&dst->addr, gtp_ip_core)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Error setting GTP IP address from %s\n", + osmo_quote_cstr_c(OTC_SELECT, gtp_ip_core, -1)); + return CMD_WARNING; + } + dst->teid = pfcp_tool_new_teid(); + + rc = session_tunmap_tx_mod_req(session, true, one_session_mod_tunmap_resp_cb); + if (rc == CMD_SUCCESS) + responses_pending++; + return rc; +} + +int one_session_create_tunmap_resp_cb(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg) +{ + struct pfcp_tool_session *session = req->ctx.priv; + enum osmo_pfcp_cause *cause; + + responses_pending--; + + if (errmsg) + LOGP(DLPFCP, LOGL_ERROR, "%s\n", errmsg); + + cause = rx_resp ? osmo_pfcp_msg_cause(rx_resp) : NULL; + if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) + return 0; + + /* store SEID */ + if (rx_resp->ies.session_est_resp.up_f_seid_present) + session->up_f_seid = rx_resp->ies.session_est_resp.up_f_seid; + + /* store local F-TEIDs */ + est_resp_get_created_f_teid(&session->tunmap.access.local, rx_resp, PDR_ID_ACCESS); + est_resp_get_created_f_teid(&session->tunmap.core.local, rx_resp, PDR_ID_CORE); + + /* Success response, now continue with second step: Session Mod to set the CORE's remote side GTP */ + one_session_mod_tunmap(session); + return 0; +} + +static int one_session_create_tunmap(struct pfcp_tool_peer *peer) +{ + struct pfcp_tool_session *session; + struct pfcp_tool_tunmap *tm; + struct pfcp_tool_gtp_tun_ep *dst; + int rc; + + session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), UP_GTP_U_TUNMAP); + tm = &session->tunmap; + + /* Access: set remote GTP address from UPF's point of view. + * A local GTP port from osmo-pfcp-tool's point of view. */ + dst = &tm->access.remote; + rc = set_access_ran_tunend(dst); + if (rc != CMD_SUCCESS) + return rc; + + /* Core: set remote GTP address */ + dst = &tm->core.remote; + rc = set_core_cn_tunend(dst); + if (rc != CMD_SUCCESS) + return rc; + + /* Set local F-TEIDs == CHOOSE */ + tm->access.local = (struct pfcp_tool_gtp_tun_ep){}; + tm->core.local = (struct pfcp_tool_gtp_tun_ep){}; + + /* Send initial Session Establishment Request */ + rc = session_tunmap_tx_est_req(session, false, one_session_create_tunmap_resp_cb); + if (rc == CMD_SUCCESS) + responses_pending++; + return rc; + return CMD_WARNING; +} + +#define MASK_WAIT_BATCH_RESPONSES 0x7f +static void n_sessions_create(struct vty *vty, struct pfcp_tool_peer *peer, int n, enum up_gtp_action_kind kind) +{ + int i; + for (i = 0; i < n; i++) { + int rc; + if (kind == UP_GTP_U_TUNMAP) + rc = one_session_create_tunmap(peer); + else + rc = one_session_create_tunend(peer); + if (rc != CMD_SUCCESS) + break; + + /* handle any pending select work */ + while (!osmo_select_shutdown_done()) { + rc = pfcp_tool_mainloop(1); + /* quit requested */ + if (rc < 0) + return; + /* no fd needed service */ + if (rc == 0) + break; + } + + /* Every N created sessions, wait for pending responses */ + if (!(i & MASK_WAIT_BATCH_RESPONSES) && responses_pending) { + vty_out(vty, "waiting for %d responses...%s", responses_pending, VTY_NEWLINE); + vty_flush(vty); + while (!osmo_select_shutdown_done()) { + if (pfcp_tool_mainloop(0) == -1) + break; + if (responses_pending == 0) + break; + } + } + } +} + +static void n_sessions_delete(struct pfcp_tool_peer *peer, int n, enum up_gtp_action_kind kind) +{ +} + +DEFUN(n_sessions, n_sessions_cmd, + "n (<0-2147483647>|all) session (create|delete) (tunend|tunmap)", + "Batch run\n" + "Perform the action N times, or on all available entries\n" + "In a batch run, create and later delete a number of sessions at once.\n" + "Create N new sessions\n" + "Delete N sessions created earlier\n" + TUNEND_STR TUNMAP_STR) +{ + struct pfcp_tool_peer *peer = vty->index; + int n = ((strcmp("all", argv[0]) == 0) ? -1 : atoi(argv[0])); + bool create = (strcmp("create", argv[1]) == 0); + enum up_gtp_action_kind kind; + if (!strcmp(argv[2], "tunmap")) + kind = UP_GTP_U_TUNMAP; + else + kind = UP_GTP_U_TUNEND; + + if (create) + n_sessions_create(vty, peer, n, kind); + else + n_sessions_delete(peer, n, kind); + return CMD_SUCCESS; +} + + +static void install_ve_and_config(struct cmd_element *cmd) +{ + install_element_ve(cmd); + install_element(CONFIG_NODE, cmd); +} + +void pfcp_tool_vty_init_cfg() +{ + OSMO_ASSERT(g_pfcp_tool != NULL); + + install_ve_and_config(&c_local_addr_cmd); + install_ve_and_config(&c_listen_cmd); +} + +void pfcp_tool_vty_init_cmds() +{ + OSMO_ASSERT(g_pfcp_tool != NULL); + + install_ve_and_config(&c_sleep_cmd); + install_ve_and_config(&c_date_cmd); + install_ve_and_config(&wait_responses_cmd); + + install_ve_and_config(&peer_cmd); + install_node(&peer_node, NULL); + + install_element(PEER_NODE, &c_sleep_cmd); + install_element(PEER_NODE, &c_date_cmd); + install_element(PEER_NODE, &peer_tx_heartbeat_cmd); + install_element(PEER_NODE, &peer_tx_assoc_setup_req_cmd); + install_element(PEER_NODE, &peer_retrans_req_cmd); + install_element(PEER_NODE, &n_sessions_cmd); + install_element(PEER_NODE, &wait_responses_cmd); + + install_element(PEER_NODE, &session_cmd); + install_element(PEER_NODE, &session_endecaps_cmd); + install_node(&session_node, NULL); + install_element(SESSION_NODE, &c_sleep_cmd); + install_element(SESSION_NODE, &c_date_cmd); + install_element(SESSION_NODE, &session_tx_est_req_cmd); + install_element(SESSION_NODE, &session_tx_mod_req_cmd); + install_element(SESSION_NODE, &session_tx_del_req_cmd); + install_element(SESSION_NODE, &s_ue_cmd); + install_element(SESSION_NODE, &s_f_teid_cmd); + install_element(SESSION_NODE, &s_f_teid_choose_cmd); + + install_ve_and_config(&c_gtp_local_cmd); + install_ve_and_config(&c_gtp_core_cmd); + install_ve_and_config(&c_ue_ip_range_cmd); + + install_ve_and_config(>p_flood_cmd); + install_node(>p_flood_node, NULL); + install_element(GTP_FLOOD_NODE, >pf_workers_cmd); + install_element(GTP_FLOOD_NODE, >pf_rx_workers_cmd); + install_element(GTP_FLOOD_NODE, >pf_tx_workers_cmd); + install_element(GTP_FLOOD_NODE, >pf_io_uring_queue_size_cmd); + install_element(GTP_FLOOD_NODE, >pf_flows_cmd); + install_element(GTP_FLOOD_NODE, >pf_packets_cmd); + install_element(GTP_FLOOD_NODE, >pf_payload_src_port_cmd); + install_element(GTP_FLOOD_NODE, >pf_payload_target_ip_cmd); + install_element(GTP_FLOOD_NODE, >pf_payload_target_port_cmd); + install_element(GTP_FLOOD_NODE, >pf_payload_append_payload_info_cmd); + install_element(GTP_FLOOD_NODE, >pf_slew_cmd); +} + +int pfcp_tool_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case GTP_FLOOD_NODE: + /* exiting a 'gtp flood' configuration, start the flooding */ + pfcp_tool_gtp_flood_start(); + break; + default: + break; + } + return 0; +} diff --git a/src/osmo-pfcp-tool/range.c b/src/osmo-pfcp-tool/range.c new file mode 100644 index 0000000..5d30bd6 --- /dev/null +++ b/src/osmo-pfcp-tool/range.c @@ -0,0 +1,111 @@ +/* + * (C) 2024 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <osmocom/core/socket.h> +#include <osmocom/core/utils.h> + +#include <osmocom/pfcptool/range.h> + +void range_val_set_int(struct range_val *rv, uint32_t val) +{ + *rv = (struct range_val){ + .buf = { (uint64_t)val, 0 }, + .size = sizeof(val), + }; +} + +uint32_t range_val_get_int(const struct range_val *rv) +{ + return rv->buf[0]; +} + +void range_val_set_addr(struct range_val *rv, const struct osmo_sockaddr *val) +{ + *rv = (struct range_val){}; + rv->size = osmo_sockaddr_to_octets((void *)rv->buf, sizeof(rv->buf), val); + +#if !OSMO_IS_BIG_ENDIAN + for (int i = 0; i < rv->size / 2; i++) { + uint8_t *rvbuf = (void *)rv->buf; + uint8_t tmp = rvbuf[i]; + rvbuf[i] = rvbuf[rv->size - 1 - i]; + rvbuf[rv->size - 1 - i] = tmp; + } +#endif +} + +void range_val_get_addr(struct osmo_sockaddr *dst, const struct range_val *rv) +{ + void *buf; +#if OSMO_IS_BIG_ENDIAN + buf = rv->buf; +#else + int i; + uint8_t rev[sizeof(rv->buf)]; + uint8_t *val = (void *)rv->buf; + for (i = 0; i < rv->size; i++) + rev[i] = val[rv->size - 1 - i]; + buf = rev; +#endif + osmo_sockaddr_from_octets(dst, buf, rv->size); +} + +void range_val_inc(struct range_val *rv) +{ + uint64_t was = rv->buf[0]; + rv->buf[0]++; + if (rv->buf[0] < was) + rv->buf[1]++; +} + +int range_val_cmp(const struct range_val *a, const struct range_val *b) +{ + int rc; + if (a == b) + return 0; + if (!a) + return -1; + if (!b) + return 1; + rc = OSMO_CMP(a->buf[0], b->buf[0]); + if (rc) + return rc; + rc = OSMO_CMP(a->buf[1], b->buf[1]); + if (rc) + return rc; + return OSMO_CMP(a->size, b->size); +} + +void range_next(struct range *r) +{ + if (range_val_cmp(&r->next, &r->first) < 0) { + r->next = r->first; + return; + } + if (range_val_cmp(&r->next, &r->last) >= 0) { + r->next = r->first; + r->wrapped = true; + return; + } + range_val_inc(&r->next); +}