Attention is currently required from: laforge, neels.
pespin has posted comments on this change by pespin. ( https://gerrit.osmocom.org/c/upf-benchmark/+/38327?usp=email )
Change subject: Initial version of improved osmo-pfcp-tool from osmo-upf.git.
......................................................................
Patch Set 1:
(1 comment)
Patchset:
PS1:
This is just an initial commit with the code at the current state from osmo-upf.git branch "pespin/gtpload", which includes work from Neels and I.
I plan to continue changes on top of this initial import. So let's not be picky about code review here other than stuff which is obviously wrong at project level.
--
To view, visit https://gerrit.osmocom.org/c/upf-benchmark/+/38327?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: upf-benchmark
Gerrit-Branch: master
Gerrit-Change-Id: I179d575f53ef5797f296e13cd4d52a043fc4c1c1
Gerrit-Change-Number: 38327
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Attention: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Comment-Date: Fri, 04 Oct 2024 16:02:45 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
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(a)sysmocom.de>
+Pau Espin Pedrol <pespin(a)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(a)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(a)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(a)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(a)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-practic…
+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(a)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(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)laser.satlink.net>
+ * Arnt Gulbrandsen, <agulbra(a)nvg.unit.no>
+ * Tom May, <ftom(a)netcom.com>
+ * Andreas Schwab, <schwab(a)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(a)osmocom.org>
+ */
+/*
+ * (C) 2024 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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);
+}
--
To view, visit https://gerrit.osmocom.org/c/upf-benchmark/+/38327?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: upf-benchmark
Gerrit-Branch: master
Gerrit-Change-Id: I179d575f53ef5797f296e13cd4d52a043fc4c1c1
Gerrit-Change-Number: 38327
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-ci/+/38317?usp=email )
Change subject: jobs/osmocom-obs-nightly-asan: exclude pyosmocom
......................................................................
jobs/osmocom-obs-nightly-asan: exclude pyosmocom
Order entries alphabetically while at it.
Change-Id: I5f3c53ca55661970e29b9d4c10b7c9dbff53f00f
---
M jobs/osmocom-obs-nightly-asan.yml
1 file changed, 2 insertions(+), 1 deletion(-)
Approvals:
laforge: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/jobs/osmocom-obs-nightly-asan.yml b/jobs/osmocom-obs-nightly-asan.yml
index e3bf098..bd5480e 100644
--- a/jobs/osmocom-obs-nightly-asan.yml
+++ b/jobs/osmocom-obs-nightly-asan.yml
@@ -23,13 +23,14 @@
# We only care about Osmocom packages that run in the TTCN-3 testsuites
# for the asan repository.
EXCLUDE_PACKAGES="
- erlang/osmo_dia2gsup
erlang/osmo-epdg
erlang/osmo-s1gw
+ erlang/osmo_dia2gsup
osmo-bsc-nat
osmo-fl2k
osmo-gsm-manuals
python/osmo-python-tests
+ python/pyosmocom
rtl-sdr
"
--
To view, visit https://gerrit.osmocom.org/c/osmo-ci/+/38317?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: osmo-ci
Gerrit-Branch: master
Gerrit-Change-Id: I5f3c53ca55661970e29b9d4c10b7c9dbff53f00f
Gerrit-Change-Number: 38317
Gerrit-PatchSet: 2
Gerrit-Owner: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Attention is currently required from: falconia.
laforge has posted comments on this change by falconia. ( https://gerrit.osmocom.org/c/libosmo-abis/+/38326?usp=email )
Change subject: rtp2trau: add support for EDATA
......................................................................
Patch Set 1: Code-Review+1
--
To view, visit https://gerrit.osmocom.org/c/libosmo-abis/+/38326?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: libosmo-abis
Gerrit-Branch: master
Gerrit-Change-Id: I538567ac208c89b3fc8f4dc408dc15a813a1272a
Gerrit-Change-Number: 38326
Gerrit-PatchSet: 1
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: falconia <falcon(a)freecalypso.org>
Gerrit-Comment-Date: Fri, 04 Oct 2024 11:48:37 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
Attention is currently required from: falconia.
laforge has posted comments on this change by falconia. ( https://gerrit.osmocom.org/c/libosmo-abis/+/38325?usp=email )
Change subject: trau2rtp: add support for EDATA
......................................................................
Patch Set 1: Code-Review+1
--
To view, visit https://gerrit.osmocom.org/c/libosmo-abis/+/38325?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: libosmo-abis
Gerrit-Branch: master
Gerrit-Change-Id: If5344eb4718ac95b996e20caf969de35a9c8a35d
Gerrit-Change-Number: 38325
Gerrit-PatchSet: 1
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: falconia <falcon(a)freecalypso.org>
Gerrit-Comment-Date: Fri, 04 Oct 2024 11:48:13 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
Attention is currently required from: falconia.
laforge has posted comments on this change by falconia. ( https://gerrit.osmocom.org/c/libosmo-abis/+/38324?usp=email )
Change subject: trau_frame: D145_SYNC does not follow EDATA format
......................................................................
Patch Set 1: Code-Review+1
--
To view, visit https://gerrit.osmocom.org/c/libosmo-abis/+/38324?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: libosmo-abis
Gerrit-Branch: master
Gerrit-Change-Id: I7c743996fc620227438225ffda419217881034d1
Gerrit-Change-Number: 38324
Gerrit-PatchSet: 1
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: falconia <falcon(a)freecalypso.org>
Gerrit-Comment-Date: Fri, 04 Oct 2024 11:47:55 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes