mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-upf.git
				synced 2025-11-03 21:43:34 +00:00 
			
		
		
		
	Compare commits
	
		
			17 Commits
		
	
	
		
			0.3.0
			...
			daniel/gtp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2bbba1e0e2 | ||
| 
						 | 
					1be178967c | ||
| 
						 | 
					3dec8475bc | ||
| 
						 | 
					f2b93a5df4 | ||
| 
						 | 
					972810e00d | ||
| 
						 | 
					5a93480b57 | ||
| 
						 | 
					9489fafd7f | ||
| 
						 | 
					bb4f2983d1 | ||
| 
						 | 
					fafc0f37aa | ||
| 
						 | 
					c306e6199d | ||
| 
						 | 
					baae5ed175 | ||
| 
						 | 
					5dbcf9b945 | ||
| 
						 | 
					6cccd2a554 | ||
| 
						 | 
					3deff6dd64 | ||
| 
						 | 
					4aae5fb3bd | ||
| 
						 | 
					fe78da28ce | ||
| 
						 | 
					0230a9b223 | 
							
								
								
									
										661
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						
									
										661
									
								
								COPYING
									
									
									
									
									
								
							@@ -1,661 +0,0 @@
 | 
			
		||||
                    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/>.
 | 
			
		||||
@@ -19,6 +19,7 @@ SUBDIRS = \
 | 
			
		||||
BUILT_SOURCES = $(top_srcdir)/.version
 | 
			
		||||
EXTRA_DIST = \
 | 
			
		||||
	     .version \
 | 
			
		||||
	     contrib/osmo-upf.spec.in \
 | 
			
		||||
	     debian \
 | 
			
		||||
	     git-version-gen \
 | 
			
		||||
	     osmoappdesc.py \
 | 
			
		||||
 
 | 
			
		||||
@@ -5,16 +5,16 @@ Homepage
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
The official homepage of the project is
 | 
			
		||||
https://osmocom.org/projects/osmo-upf/wiki
 | 
			
		||||
https://osmocom.org/projects/osmoupf/wiki
 | 
			
		||||
 | 
			
		||||
GIT Repository
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
You can clone from the official osmo-upf.git repository using
 | 
			
		||||
 | 
			
		||||
	git clone https://gitea.osmocom.org/cellular-infrastructure/osmo-upf
 | 
			
		||||
	git clone git://git.osmocom.org/osmo-upf.git
 | 
			
		||||
 | 
			
		||||
There is a web interface at https://gitea.osmocom.org/cellular-infrastructure/osmo-upf.
 | 
			
		||||
There is a cgit interface at https://git.osmocom.org/osmo-upf/
 | 
			
		||||
 | 
			
		||||
To submit patches, see "Contributing" below.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
# When cleaning up this file: bump API version in corresponding Makefile.am and rename corresponding debian/lib*.install
 | 
			
		||||
# according to https://osmocom.org/projects/cellular-infrastructure/wiki/Make_a_new_release
 | 
			
		||||
# In short: https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
 | 
			
		||||
# according to https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
 | 
			
		||||
# In short:
 | 
			
		||||
# LIBVERSION=c:r:a
 | 
			
		||||
# If the library source code has changed at all since the last update, then increment revision: c:r + 1:a.
 | 
			
		||||
# If any interfaces have been added, removed, or changed since the last update: c + 1:0: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
 | 
			
		||||
#library	what		description / commit summary line
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								configure.ac
									
									
									
									
									
								
							@@ -21,6 +21,7 @@ dnl checks for programs
 | 
			
		||||
AC_PROG_MAKE_SET
 | 
			
		||||
AC_PROG_CC
 | 
			
		||||
AC_PROG_INSTALL
 | 
			
		||||
AC_PROG_RANLIB
 | 
			
		||||
LT_INIT
 | 
			
		||||
 | 
			
		||||
dnl patching ${archive_cmds} to affect generation of file "libtool" to fix linking with clang
 | 
			
		||||
@@ -36,12 +37,15 @@ fi
 | 
			
		||||
PKG_PROG_PKG_CONFIG([0.20])
 | 
			
		||||
 | 
			
		||||
dnl checks for libraries
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.5.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBGTPNL, libgtpnl >= 1.3.2)
 | 
			
		||||
PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2)
 | 
			
		||||
AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""])
 | 
			
		||||
AC_SUBST(LIBRARY_DL)
 | 
			
		||||
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.5.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.5.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.5.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBGTPNL, libgtpnl >= 1.2.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBNFTNL, libnftnl >= 1.1.9)
 | 
			
		||||
PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 0.9.8)
 | 
			
		||||
 | 
			
		||||
dnl checks for header files
 | 
			
		||||
AC_HEADER_STDC
 | 
			
		||||
@@ -123,7 +127,7 @@ if test "x$enable_ext_tests" = "xyes" ; then
 | 
			
		||||
	fi
 | 
			
		||||
	AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes)
 | 
			
		||||
	 if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
 | 
			
		||||
		AC_MSG_ERROR([Please install https://gitea.osmocom.org/cellular-infrastructure/osmo-python-tests to run the VTY/CTRL tests.])
 | 
			
		||||
		AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.])
 | 
			
		||||
	fi
 | 
			
		||||
fi
 | 
			
		||||
AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
 | 
			
		||||
@@ -197,16 +201,24 @@ AM_CONFIG_HEADER(config.h)
 | 
			
		||||
AC_OUTPUT(
 | 
			
		||||
    include/Makefile
 | 
			
		||||
    include/osmocom/Makefile
 | 
			
		||||
    include/osmocom/gtlv/Makefile
 | 
			
		||||
    include/osmocom/pfcp/Makefile
 | 
			
		||||
    include/osmocom/upf/Makefile
 | 
			
		||||
    src/Makefile
 | 
			
		||||
    src/libosmo-gtlv/Makefile
 | 
			
		||||
    src/libosmo-pfcp/Makefile
 | 
			
		||||
    src/osmo-upf/Makefile
 | 
			
		||||
    src/osmo-pfcp-tool/Makefile
 | 
			
		||||
    tests/Makefile
 | 
			
		||||
    tests/atlocal
 | 
			
		||||
    tests/unique_ids/Makefile
 | 
			
		||||
    tests/libosmo-gtlv/Makefile
 | 
			
		||||
    tests/libosmo-gtlv/test_gtlv_gen/Makefile
 | 
			
		||||
    tests/libosmo-gtlv/test_tliv/Makefile
 | 
			
		||||
    tests/libosmo-pfcp/Makefile
 | 
			
		||||
    doc/Makefile
 | 
			
		||||
    doc/examples/Makefile
 | 
			
		||||
    doc/manuals/Makefile
 | 
			
		||||
    doc/charts/Makefile
 | 
			
		||||
    contrib/Makefile
 | 
			
		||||
    contrib/systemd/Makefile
 | 
			
		||||
    Makefile)
 | 
			
		||||
 
 | 
			
		||||
@@ -30,36 +30,6 @@ 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=""
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# ACCESS                UPF tunmap                    UPF tunend (CORE)
 | 
			
		||||
# ACCESS                HOP                           CORE
 | 
			
		||||
#                       session 23 = tunmap           session 42 = encaps/decaps
 | 
			
		||||
# GTP 127.0.0.13        127.0.0.12                    127.0.0.11
 | 
			
		||||
# TEID l:23 r:123 <---> r:23 l:123 | l:142 r:42 <---> r:142 l:42 | 192.168.100.42
 | 
			
		||||
@@ -11,10 +11,10 @@ timer pfcp x23 0
 | 
			
		||||
pfcp-peer 127.0.0.11
 | 
			
		||||
 tx assoc-setup-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
 session tunend 42
 | 
			
		||||
 session endecaps 42
 | 
			
		||||
  ue ip 192.168.100.42
 | 
			
		||||
  gtp access local f-teid 127.0.0.11 42
 | 
			
		||||
  gtp access remote f-teid 127.0.0.12 142
 | 
			
		||||
  gtp access ip 127.0.0.12
 | 
			
		||||
  gtp access teid local 42 remote 142
 | 
			
		||||
  tx session-est-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
 | 
			
		||||
@@ -22,9 +22,9 @@ pfcp-peer 127.0.0.12
 | 
			
		||||
 tx assoc-setup-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
 session tunmap 23
 | 
			
		||||
  gtp core remote f-teid 127.0.0.11 42
 | 
			
		||||
  gtp core local f-teid 127.0.0.12 142
 | 
			
		||||
  gtp access local f-teid 127.0.0.12 123
 | 
			
		||||
  gtp access remote f-teid 127.0.0.13 23
 | 
			
		||||
  gtp core ip 127.0.0.11
 | 
			
		||||
  gtp core teid local 142 remote 42
 | 
			
		||||
  gtp access ip 127.0.0.13
 | 
			
		||||
  gtp access teid local 123 remote 23
 | 
			
		||||
  tx session-est-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
							
								
								
									
										8
									
								
								contrib/osmo-pfcp-tool-scripts/endecaps_session_est.vty
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								contrib/osmo-pfcp-tool-scripts/endecaps_session_est.vty
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
timer pfcp x23 0
 | 
			
		||||
pfcp-peer 127.0.0.1
 | 
			
		||||
 tx assoc-setup-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
 session endecaps
 | 
			
		||||
  tx session-est-req forw
 | 
			
		||||
  sleep 5
 | 
			
		||||
  tx session-del-req
 | 
			
		||||
@@ -1,10 +1,4 @@
 | 
			
		||||
log stderr
 | 
			
		||||
 logging color 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging timestamp 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print level 1
 | 
			
		||||
 logging level set-all info
 | 
			
		||||
 | 
			
		||||
local-addr 127.0.0.2
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
log stderr
 | 
			
		||||
 logging filter all 1
 | 
			
		||||
 logging color 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging timestamp 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print level 1
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print extended-timestamp 1
 | 
			
		||||
 logging level set-all notice
 | 
			
		||||
 logging level set-all info
 | 
			
		||||
 logging level session debug
 | 
			
		||||
@@ -21,5 +21,7 @@ ctrl
 | 
			
		||||
timer pfcp x24 5000
 | 
			
		||||
pfcp
 | 
			
		||||
 local-addr 127.0.0.11
 | 
			
		||||
tunend
 | 
			
		||||
gtp
 | 
			
		||||
 dev create apn11 127.0.0.11
 | 
			
		||||
nft
 | 
			
		||||
 table-name osmo-upf-11
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
log stderr
 | 
			
		||||
 logging filter all 1
 | 
			
		||||
 logging color 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging timestamp 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print level 1
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print extended-timestamp 1
 | 
			
		||||
 logging level set-all notice
 | 
			
		||||
 logging level set-all info
 | 
			
		||||
 logging level session debug
 | 
			
		||||
@@ -21,9 +21,7 @@ ctrl
 | 
			
		||||
timer pfcp x24 5000
 | 
			
		||||
pfcp
 | 
			
		||||
 local-addr 127.0.0.12
 | 
			
		||||
tunmap
 | 
			
		||||
gtp
 | 
			
		||||
 dev create apn12 127.0.0.12
 | 
			
		||||
nft
 | 
			
		||||
 table-name osmo-upf-12
 | 
			
		||||
 | 
			
		||||
# gtp-dev only for GTP-U Echo service
 | 
			
		||||
tunend
 | 
			
		||||
 dev create gtp-echo-12 127.0.0.12
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
timer pfcp x23 0
 | 
			
		||||
pfcp-peer 127.0.0.1
 | 
			
		||||
 session tunend
 | 
			
		||||
  ue ip 127.127.127.127
 | 
			
		||||
  gtp access remote f-teid 127.0.0.127 127
 | 
			
		||||
 session endecaps
 | 
			
		||||
  tx session-est-req
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,12 @@ pfcp-peer 127.0.0.1
 | 
			
		||||
 tx assoc-setup-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
 session
 | 
			
		||||
  ue ip 127.127.127.127
 | 
			
		||||
  gtp access remote f-teid 127.0.0.127 127
 | 
			
		||||
  tx session-est-req drop
 | 
			
		||||
  sleep 3
 | 
			
		||||
  tx session-mod-req far forw
 | 
			
		||||
  tx session-mod-req forw
 | 
			
		||||
  sleep 5
 | 
			
		||||
  tx session-mod-req far drop
 | 
			
		||||
  tx session-mod-req drop
 | 
			
		||||
  sleep 3
 | 
			
		||||
  tx session-mod-req far forw
 | 
			
		||||
  tx session-mod-req forw
 | 
			
		||||
  sleep 3
 | 
			
		||||
  tx session-del-req
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
timer pfcp x23 0
 | 
			
		||||
pfcp-peer 127.0.0.11
 | 
			
		||||
 tx assoc-setup-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
 session tunend
 | 
			
		||||
  ue ip 127.127.127.127
 | 
			
		||||
  gtp access local f-teid choose
 | 
			
		||||
  gtp access remote f-teid 127.0.0.12 142
 | 
			
		||||
  tx session-est-req forw
 | 
			
		||||
  sleep 5
 | 
			
		||||
  tx session-del-req
 | 
			
		||||
@@ -3,10 +3,6 @@ pfcp-peer 127.0.0.1
 | 
			
		||||
 tx assoc-setup-req
 | 
			
		||||
 sleep 1
 | 
			
		||||
 session tunmap
 | 
			
		||||
  gtp core remote f-teid 127.0.0.11 42
 | 
			
		||||
  gtp core local f-teid choose
 | 
			
		||||
  gtp access local f-teid choose
 | 
			
		||||
  gtp access remote f-teid 127.0.0.13 23
 | 
			
		||||
  tx session-est-req
 | 
			
		||||
  sleep 5
 | 
			
		||||
  tx session-del-req
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										87
									
								
								contrib/osmo-upf.spec.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								contrib/osmo-upf.spec.in
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
#
 | 
			
		||||
# spec file for package osmo-upf
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2017, Martin Hauke <mardnh@gmx.de>
 | 
			
		||||
#
 | 
			
		||||
# All modifications and additions to the file contributed by third parties
 | 
			
		||||
# remain the property of their copyright owners, unless otherwise agreed
 | 
			
		||||
# upon. The license for this file, and modifications and additions to the
 | 
			
		||||
# file, is the same license as for the pristine package itself (unless the
 | 
			
		||||
# license for the pristine package is not an Open Source License, in which
 | 
			
		||||
# case the license is the MIT License). An "Open Source License" is a
 | 
			
		||||
# license that conforms to the Open Source Definition (Version 1.9)
 | 
			
		||||
# published by the Open Source Initiative.
 | 
			
		||||
 | 
			
		||||
## Disable LTO for now since it breaks compilation of the tests
 | 
			
		||||
## https://osmocom.org/issues/4113
 | 
			
		||||
%define _lto_cflags %{nil}
 | 
			
		||||
 | 
			
		||||
Name:           osmo-upf
 | 
			
		||||
Version:        @VERSION@
 | 
			
		||||
Release:        0
 | 
			
		||||
Summary:        OsmoUPF: Osmocom User Plane Function
 | 
			
		||||
License:        AGPL-3.0-or-later AND GPL-2.0-or-later
 | 
			
		||||
Group:          Hardware/Mobile
 | 
			
		||||
URL:            https://osmocom.org/projects/osmoupf
 | 
			
		||||
Source:         %{name}-%{version}.tar.xz
 | 
			
		||||
BuildRequires:  autoconf-archive
 | 
			
		||||
BuildRequires:  automake >= 1.9
 | 
			
		||||
BuildRequires:  libtool >= 2
 | 
			
		||||
BuildRequires:  lksctp-tools-devel
 | 
			
		||||
BuildRequires:  pkgconfig >= 0.20
 | 
			
		||||
%if 0%{?suse_version}
 | 
			
		||||
BuildRequires:  systemd-rpm-macros
 | 
			
		||||
%endif
 | 
			
		||||
BuildRequires:  pkgconfig(libgtpnl) >= 1.2.0
 | 
			
		||||
BuildRequires:  pkgconfig(libosmocore) >= 1.6.0
 | 
			
		||||
BuildRequires:  pkgconfig(libosmoctrl) >= 1.6.0
 | 
			
		||||
BuildRequires:  pkgconfig(libosmovty) >= 1.6.0
 | 
			
		||||
BuildRequires:  pkgconfig(talloc)
 | 
			
		||||
%{?systemd_requires}
 | 
			
		||||
 | 
			
		||||
%description
 | 
			
		||||
OsmoUPF: Osmocom User Plane Function
 | 
			
		||||
 | 
			
		||||
%prep
 | 
			
		||||
%setup -q
 | 
			
		||||
 | 
			
		||||
%build
 | 
			
		||||
echo "%{version}" >.tarball-version
 | 
			
		||||
autoreconf -fi
 | 
			
		||||
%configure \
 | 
			
		||||
  --docdir=%{_docdir}/%{name} \
 | 
			
		||||
  --with-systemdsystemunitdir=%{_unitdir}
 | 
			
		||||
make %{?_smp_mflags}
 | 
			
		||||
 | 
			
		||||
%install
 | 
			
		||||
%make_install
 | 
			
		||||
 | 
			
		||||
%if 0%{?suse_version}
 | 
			
		||||
%preun
 | 
			
		||||
%service_del_preun %{name}.service
 | 
			
		||||
 | 
			
		||||
%postun
 | 
			
		||||
%service_del_postun %{name}.service
 | 
			
		||||
 | 
			
		||||
%pre
 | 
			
		||||
%service_add_pre %{name}.service
 | 
			
		||||
 | 
			
		||||
%post
 | 
			
		||||
%service_add_post %{name}.service
 | 
			
		||||
%endif
 | 
			
		||||
 | 
			
		||||
%check
 | 
			
		||||
make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
 | 
			
		||||
 | 
			
		||||
%files
 | 
			
		||||
%license COPYING
 | 
			
		||||
%doc AUTHORS README.md
 | 
			
		||||
%{_bindir}/osmo-upf
 | 
			
		||||
%dir %{_docdir}/%{name}/examples
 | 
			
		||||
%dir %{_docdir}/%{name}/examples/osmo-upf
 | 
			
		||||
%{_docdir}/%{name}/examples/osmo-upf/osmo-upf.cfg
 | 
			
		||||
%dir %{_sysconfdir}/osmocom
 | 
			
		||||
%config(noreplace) %{_sysconfdir}/osmocom/osmo-upf.cfg
 | 
			
		||||
%{_unitdir}/%{name}.service
 | 
			
		||||
 | 
			
		||||
%changelog
 | 
			
		||||
@@ -1,18 +1,11 @@
 | 
			
		||||
[Unit]
 | 
			
		||||
Description=Osmocom User Plane Function (UPF)
 | 
			
		||||
After=network-online.target
 | 
			
		||||
Wants=network-online.target
 | 
			
		||||
 | 
			
		||||
[Service]
 | 
			
		||||
Type=simple
 | 
			
		||||
StateDirectory=osmocom
 | 
			
		||||
WorkingDirectory=%S/osmocom
 | 
			
		||||
Restart=always
 | 
			
		||||
User=osmocom
 | 
			
		||||
Group=osmocom
 | 
			
		||||
ExecStart=/usr/bin/osmo-upf -c /etc/osmocom/osmo-upf.cfg
 | 
			
		||||
RestartSec=2
 | 
			
		||||
AmbientCapabilities=CAP_NET_ADMIN
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										168
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										168
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							@@ -1,168 +0,0 @@
 | 
			
		||||
osmo-upf (0.3.0) unstable; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Oliver Smith ]
 | 
			
		||||
  * upf_gtp: automatically delete old gtp devices
 | 
			
		||||
 | 
			
		||||
  [ Pau Espin Pedrol ]
 | 
			
		||||
  * Improve logging reading packets from gtp device
 | 
			
		||||
  * Remove unused file up_session_to_gtp.c
 | 
			
		||||
  * Simplify up_session_choose_f_teid() with early returns
 | 
			
		||||
  * Introduce hashtable to lookup session by F-TEID
 | 
			
		||||
  * Introduce hashtable to lookup chain_id
 | 
			
		||||
  * Introduce hashtable to look up gtp_tundev by local TEID
 | 
			
		||||
 | 
			
		||||
 -- Oliver Smith <osmith@sysmocom.de>  Tue, 18 Feb 2025 12:20:35 +0100
 | 
			
		||||
 | 
			
		||||
osmo-upf (0.2.0) unstable; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Neels Hofmeyr ]
 | 
			
		||||
  * improve manual: PFCP and GTP interfaces
 | 
			
		||||
  * example cfg: tweak logging
 | 
			
		||||
 | 
			
		||||
  [ Oliver Smith ]
 | 
			
		||||
  * osmo_pfcp_tool: fix osmo_pfcp_tool_copyright error
 | 
			
		||||
  * debian: add osmo-pfcp-tool to osmo-upf package
 | 
			
		||||
  * Run struct_endianness.py
 | 
			
		||||
  * contrib/jenkins: build nftables without python
 | 
			
		||||
  * contrib/jenkins: clone netfilter repos with https
 | 
			
		||||
  * contrib/jenkins: netfilter: use PARALLEL_MAKE
 | 
			
		||||
  * debian: set compat level to 10
 | 
			
		||||
  * systemd: depend on networking-online.target
 | 
			
		||||
  * .deb/.rpm: various fixes related to non-root
 | 
			
		||||
  * contrib/systemd: AmbientCapabilities=CAP_NET_ADMIN
 | 
			
		||||
  * contrib: remove rpm spec file
 | 
			
		||||
  * debian/postinst: add checks, be verbose
 | 
			
		||||
 | 
			
		||||
  [ Neels Janosch Hofmeyr ]
 | 
			
		||||
  * Allow running without a GTP dev
 | 
			
		||||
  * drop charts/, duplicated from libosmo-pfcp.git
 | 
			
		||||
  * manual: add/fix running.adoc
 | 
			
		||||
  * manual: add 'Configure Primary Links'
 | 
			
		||||
  * vty doc: indicate default nft table name
 | 
			
		||||
  * silence misleading error: "HEARTBEAT_REQ: Unknown message type"
 | 
			
		||||
  * drop unused enum up_session_kind
 | 
			
		||||
  * tests/upf.vty: add some missing nodes to the test
 | 
			
		||||
  * VTY: rename 'gtp' to 'tunend'
 | 
			
		||||
  * VTY: rename 'nft' to 'tunmap'
 | 
			
		||||
  * osmo-pfcp-tool VTY: rename 'endecaps' to 'tunend'
 | 
			
		||||
  * cosmetic: in code, rename 'endecaps' to 'tunend'
 | 
			
		||||
  * cosmetic: rename upf_gtp_tun to upf_gtp_tunend
 | 
			
		||||
  * drop unused upf_gtp_dev_is_tunnel_active()
 | 
			
		||||
  * cosmetic: rename upf_gtp_dev_tunnel_* to upf_gtp_dev_tunend_*
 | 
			
		||||
  * vty: revert rename of 'show gtp'
 | 
			
		||||
  * VTY: show gtp: still list tunmap if no tunend device is open
 | 
			
		||||
  * up_gtp_action_to_str_buf(): always print PDR IDs
 | 
			
		||||
  * use osmo_pfcp_ie_outer_header_creation_to_str_buf()
 | 
			
		||||
  * gtpu_echo: do not osmo_fd_register twice
 | 
			
		||||
  * VTY 'show gtp': more accurately identify local/remote IP
 | 
			
		||||
  * fix access/core mixup of PDR IDs / tunmap FAR
 | 
			
		||||
  * clarify comments and naming around PDR+FAR classification
 | 
			
		||||
  * tunmap: choose local GTP addr by Network Instance IEs
 | 
			
		||||
  * log: add missing sep in far_to_str
 | 
			
		||||
  * fix PFCP Session Mod: Update FAR
 | 
			
		||||
  * in GTP actions, also store local GTP addrs
 | 
			
		||||
  * nft: rename addr to addr_remote, add addr_local
 | 
			
		||||
  * nft: incoming GTP-U: match on local IP, not remote IP
 | 
			
		||||
  * nft: rewrite source IP in outgoing GTP-U
 | 
			
		||||
  * nft: log nft rulesets on debug log
 | 
			
		||||
  * nft: end each rule in semicolon
 | 
			
		||||
  * nft: ensure to assign rule id only once
 | 
			
		||||
  * GTP,UE addrs in osmo_sockaddr: assert( port == 0 )
 | 
			
		||||
  * nft: allow to get the ruleset string without running
 | 
			
		||||
  * vty: add: show nft-rule tunmap example
 | 
			
		||||
  * add cfg: tunmap / nft-rule append
 | 
			
		||||
  * nft: append 'accept' to each rule
 | 
			
		||||
  * tunend: choose local GTP addr by Network Instance IEs
 | 
			
		||||
  * manual: use 'tunend' and 'tunmap'
 | 
			
		||||
  * manual: explain new netinst cfg
 | 
			
		||||
  * manual: tweak 'running' for new netinst feature
 | 
			
		||||
  * manual: add charts explaining tunend and tunmap
 | 
			
		||||
  * manual: some tweaks in overview
 | 
			
		||||
  * manual: fix broken reference to netinst section
 | 
			
		||||
  * fix copy-paste bug in up_endpoint.c
 | 
			
		||||
  * fix deprecation: use telnet_init_default()
 | 
			
		||||
  * check rc of osmo_use_count_get_put()
 | 
			
		||||
  * error log: fix msg for gtp_del_tunnel() failure
 | 
			
		||||
  * fix various crashes on osmo_pfcp_endpoint_tx() err handling
 | 
			
		||||
  * osmo-pfcp-tool: avoid stale pointers on msg copy
 | 
			
		||||
  * fix some PFCP peer,session error handling paths
 | 
			
		||||
  * drop unused function up_peer_tx
 | 
			
		||||
  * move GTP port definitions to upf.h
 | 
			
		||||
  * deprecate cfg 'nft rule tunmap append'
 | 
			
		||||
  * tunmap: prep new nft ruleset: log only mapping id
 | 
			
		||||
  * tunmap: refactor nft ruleset: fix "martians" and "1024"
 | 
			
		||||
  * tunmap: ensure nft table is removed on program exit
 | 
			
		||||
  * osmo_pfcp_tool: make usable again
 | 
			
		||||
  * minor api doc
 | 
			
		||||
  * cosmetic: simplify naming: struct upf_tunmap, struct upf_tunend
 | 
			
		||||
  * cosmetic: reduce dup in tunnel struct definitions
 | 
			
		||||
  * cosmetic: rename g_upf->gtp to tunend, ->nft to tunmap
 | 
			
		||||
  * move next_teid from up_endpoint to g_upf
 | 
			
		||||
  * cosmetic: rename next_seid to next_up_seid
 | 
			
		||||
  * build: drop LIBOSMO_GTLV
 | 
			
		||||
  * build: add libupf.la (noinst)
 | 
			
		||||
  * add unique_ids_test.c
 | 
			
		||||
  * tunmap: ensure assigned chain_id is unused
 | 
			
		||||
  * cosmetic: clarify session active / partially active semantics
 | 
			
		||||
  * unique_ids_test.c: fix coverity ASSERT_SIDE_EFFECT
 | 
			
		||||
  * manual: fix typo in running.adoc
 | 
			
		||||
  * manual: 'Running': flatten section depths a bit
 | 
			
		||||
  * manual: 'Running': tweak, mention 'tunmap' and 'tunend'
 | 
			
		||||
  * manual: 'Running': tweak word, fix ws at line end
 | 
			
		||||
  * manual: explain GTP Echo workaround for tunmap
 | 
			
		||||
  * manual: explain IP forwarding
 | 
			
		||||
  * tunmap: always set GTP-U source port to 2152 when forwarding
 | 
			
		||||
  * vty doc fix
 | 
			
		||||
  * pfcp-tool: fix extra newline in vty_out
 | 
			
		||||
  * fix msgb memleak on GTP echo response
 | 
			
		||||
  * fix EXTRA_DIST for vty test scripts
 | 
			
		||||
  * drop unreachable statement
 | 
			
		||||
  * contrib/pfcp-tool-scripts: adjust tunend_session_est.vty and upf cfg to match up
 | 
			
		||||
  * upf gtp-u echo: improve loging
 | 
			
		||||
  * upf gtp-u echo: rx Echo Response messages
 | 
			
		||||
  * pfcp-tool: always use specific PDR ids for access and core
 | 
			
		||||
  * nft: batch nftables commands
 | 
			
		||||
  * osmo-upf: add VTY 'gtp-echo' command
 | 
			
		||||
 | 
			
		||||
  [ Max ]
 | 
			
		||||
  * Set working directory in systemd service file
 | 
			
		||||
  * ctrl: take both address and port from vty config
 | 
			
		||||
  * .deb/.rpm: add osmocom user during package install
 | 
			
		||||
 | 
			
		||||
  [ Vadim Yanitskiy ]
 | 
			
		||||
  * update git URLs (git -> https; gitea)
 | 
			
		||||
  * contrib/jenkins.sh: clone libnftnl and libnftables via git://
 | 
			
		||||
  * copyright: fix typo: sysmocom s/s.m.f.c./s.f.m.c./ GmbH
 | 
			
		||||
 | 
			
		||||
  [ arehbein ]
 | 
			
		||||
  * osmo-pfcp-tool: Fix call to strerror
 | 
			
		||||
  * up_session: Silence coverity warning
 | 
			
		||||
 | 
			
		||||
  [ Andreas Eversberg ]
 | 
			
		||||
  * Use uniform log format for default config files
 | 
			
		||||
 | 
			
		||||
 -- Oliver Smith <osmith@sysmocom.de>  Wed, 11 Dec 2024 16:37:06 +0100
 | 
			
		||||
 | 
			
		||||
osmo-upf (0.1.1) unstable; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Vadim Yanitskiy ]
 | 
			
		||||
  * configure.ac: do not require unused dlopen
 | 
			
		||||
  * Fix missing dash in Redmine project URL
 | 
			
		||||
 | 
			
		||||
  [ Harald Welte ]
 | 
			
		||||
  * add missing dependencies to libosmo-{gtlv,pfcp} to rpm + dpkg
 | 
			
		||||
 | 
			
		||||
  [ Neels Hofmeyr ]
 | 
			
		||||
  * add missing COPYING file
 | 
			
		||||
  * debian,RPM,configure: fix packaging (deps etc)
 | 
			
		||||
 | 
			
		||||
  [ Neels Janosch Hofmeyr ]
 | 
			
		||||
  * configure: set libosmocore >= 1.6.0 like in packaging
 | 
			
		||||
 | 
			
		||||
 -- Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>  Wed, 24 Aug 2022 16:50:56 +0200
 | 
			
		||||
 | 
			
		||||
osmo-upf (0.1.0) testing; urgency=low
 | 
			
		||||
 | 
			
		||||
  Create new Debian package.
 | 
			
		||||
 | 
			
		||||
 -- Neels Hofmeyr <nhofmeyr@sysmocom.de>  Tue, 16 Aug 2022 23:17:05 +0200
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								debian/compat
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								debian/compat
									
									
									
									
										vendored
									
									
								
							@@ -1 +1 @@
 | 
			
		||||
10
 | 
			
		||||
9
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							@@ -2,7 +2,7 @@ Source: osmo-upf
 | 
			
		||||
Section: net
 | 
			
		||||
Priority: extra
 | 
			
		||||
Maintainer: Osmocom team <openbsc@lists.osmocom.org>
 | 
			
		||||
Build-Depends: debhelper (>= 10),
 | 
			
		||||
Build-Depends: debhelper (>=9),
 | 
			
		||||
               dh-autoreconf,
 | 
			
		||||
               autotools-dev,
 | 
			
		||||
               autoconf,
 | 
			
		||||
@@ -12,14 +12,12 @@ Build-Depends: debhelper (>= 10),
 | 
			
		||||
               pkg-config,
 | 
			
		||||
               python3-minimal,
 | 
			
		||||
               libtalloc-dev,
 | 
			
		||||
               libgtpnl-dev (>= 1.3.2),
 | 
			
		||||
               libnftables-dev (>= 1.0.2),
 | 
			
		||||
               libosmocore-dev (>= 1.11.0),
 | 
			
		||||
               libosmo-pfcp-dev (>= 0.5.0),
 | 
			
		||||
               osmo-gsm-manuals-dev (>= 1.6.0)
 | 
			
		||||
               libgtpnl-dev (>= 1.2.0),
 | 
			
		||||
               libosmocore-dev (>= 1.6.0),
 | 
			
		||||
               osmo-gsm-manuals-dev (>= 1.2.0)
 | 
			
		||||
Standards-Version: 3.9.8
 | 
			
		||||
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-upf
 | 
			
		||||
Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-upf
 | 
			
		||||
Vcs-Git: git://git.osmocom.org/osmo-upf.git
 | 
			
		||||
Vcs-Browser: https://git.osmocom.org/osmo-upf/
 | 
			
		||||
Homepage: https://projects.osmocom.org/projects/osmo-upf
 | 
			
		||||
 | 
			
		||||
Package: osmo-upf
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								debian/copyright
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								debian/copyright
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 | 
			
		||||
Upstream-Name: osmo-upf
 | 
			
		||||
Source: https://gitea.osmocom.org/cellular-infrastructure/osmo-upf
 | 
			
		||||
Source: git://git.osmocom.org/osmo-upf
 | 
			
		||||
 | 
			
		||||
Files:     *
 | 
			
		||||
Copyright: 2021-2022 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								debian/osmo-upf.install
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								debian/osmo-upf.install
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,4 @@
 | 
			
		||||
etc/osmocom/osmo-upf.cfg
 | 
			
		||||
lib/systemd/system/osmo-upf.service
 | 
			
		||||
usr/bin/osmo-pfcp-tool
 | 
			
		||||
usr/bin/osmo-upf
 | 
			
		||||
usr/share/doc/osmo-upf/examples/osmo-upf/osmo-upf.cfg usr/share/doc/osmo-upf/examples
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								debian/postinst
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								debian/postinst
									
									
									
									
										vendored
									
									
								
							@@ -1,38 +0,0 @@
 | 
			
		||||
#!/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
 | 
			
		||||
 | 
			
		||||
		# Fix permissions of previous (root-owned) install (OS#4107)
 | 
			
		||||
		if dpkg --compare-versions "$2" le "0.2.0"; then
 | 
			
		||||
			if [ -e /etc/osmocom/osmo-upf.cfg ]; then
 | 
			
		||||
				chown -v osmocom:osmocom /etc/osmocom/osmo-upf.cfg
 | 
			
		||||
				chmod -v 0660 /etc/osmocom/osmo-upf.cfg
 | 
			
		||||
			fi
 | 
			
		||||
 | 
			
		||||
			if [ -d /etc/osmocom ]; then
 | 
			
		||||
				chown -v root:osmocom /etc/osmocom
 | 
			
		||||
				chmod -v 2775 /etc/osmocom
 | 
			
		||||
			fi
 | 
			
		||||
 | 
			
		||||
			mkdir -p /var/lib/osmocom
 | 
			
		||||
			chown -R -v osmocom:osmocom /var/lib/osmocom
 | 
			
		||||
		fi
 | 
			
		||||
		;;
 | 
			
		||||
esac
 | 
			
		||||
 | 
			
		||||
# dh_installdeb(1) will replace this with shell code automatically
 | 
			
		||||
# generated by other debhelper scripts.
 | 
			
		||||
#DEBHELPER#
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	examples \
 | 
			
		||||
	manuals \
 | 
			
		||||
	charts \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								doc/charts/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								doc/charts/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
msc: \
 | 
			
		||||
	$(builddir)/pfcp_msgs.png \
 | 
			
		||||
	$(builddir)/pfcp_msgs_gtp.png \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
dot: \
 | 
			
		||||
	$(builddir)/pfcp_overview.png \
 | 
			
		||||
	$(builddir)/pfcp_cp_peer_fsm.png \
 | 
			
		||||
	$(builddir)/pfcp_up_peer_fsm.png \
 | 
			
		||||
	$(builddir)/pfcp_heartbeat_fsm.png \
 | 
			
		||||
	$(builddir)/pfcp_cp_session_fsm.png \
 | 
			
		||||
	$(builddir)/pfcp_up_session_fsm.png \
 | 
			
		||||
	$(builddir)/pfcp_and_gtp.png \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
$(builddir)/%.png: $(srcdir)/%.msc
 | 
			
		||||
	mscgen -T png -o $@ $<
 | 
			
		||||
 | 
			
		||||
$(builddir)/%.png: $(srcdir)/%.dot
 | 
			
		||||
	dot -Tpng $< > $@
 | 
			
		||||
 | 
			
		||||
$(srcdir)/%.msc: $(srcdir)/%.ladder
 | 
			
		||||
	@which ladder_to_msc.py || (echo 'PLEASE POINT YOUR $$PATH AT libosmocore/contrib/ladder_to_msc.py' && false)
 | 
			
		||||
	ladder_to_msc.py -i $< -o $@
 | 
			
		||||
 | 
			
		||||
.PHONY: poll
 | 
			
		||||
poll:
 | 
			
		||||
	while true; do $(MAKE) msc dot; sleep 1; done
 | 
			
		||||
							
								
								
									
										20
									
								
								doc/charts/pfcp_and_gtp.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								doc/charts/pfcp_and_gtp.dot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=LR
 | 
			
		||||
labelloc=t; label="PFCP and GTP"
 | 
			
		||||
 | 
			
		||||
SGSN [label="SGSN\n123.44.0.9"]
 | 
			
		||||
SGWC [label="SGW-C\n123.44.05"]
 | 
			
		||||
subgraph cluster_UPF {
 | 
			
		||||
 label="OsmoUPF";
 | 
			
		||||
 SGWU [label="SGW-U\n123.44.0.6"];
 | 
			
		||||
 GTPk [label="kernel GTP\n123.44.0.6"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SGSN -> SGWC [label="S4\nGTPv2-C"]
 | 
			
		||||
SGWC -> SGWU [label="Sxa\nPFCP\nSession Establishment:\n"]
 | 
			
		||||
SGSN -> GTPk [label="S4\nGTPv1-U",dir=both]
 | 
			
		||||
 | 
			
		||||
MS [label="MS\n192.168.104.176"]
 | 
			
		||||
MS -> SGSN [dir=both]
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								doc/charts/pfcp_cp_peer_fsm.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								doc/charts/pfcp_cp_peer_fsm.dot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=TB
 | 
			
		||||
labelloc=t; label="PFCP CP peer FSM\nControl Plane side, managing association with remote UP peer"
 | 
			
		||||
 | 
			
		||||
cp [label="CP function",shape="box"]
 | 
			
		||||
 | 
			
		||||
cp -> WAIT_ASSOC_SETUP_RESP [label="cp_peer_associate()"]
 | 
			
		||||
 | 
			
		||||
txrx [label="PFCP socket",shape="box"]
 | 
			
		||||
WAIT_ASSOC_SETUP_RESP -> txrx [label="tx_assoc_setup_req()",style=dotted]
 | 
			
		||||
txrx -> WAIT_ASSOC_SETUP_RESP [label="EV_RX_ASSOC_SETUP_RESP",style=dotted]
 | 
			
		||||
WAIT_ASSOC_SETUP_RESP -> ASSOCIATED [label="Assoc Setup Resp"]
 | 
			
		||||
 | 
			
		||||
WAIT_ASSOC_SETUP_RESP -> WAIT_ASSOC_SETUP_RESP [label="retry"]
 | 
			
		||||
 | 
			
		||||
heartbeat [label="PFCP heartbeat FSM",shape=box3d]
 | 
			
		||||
ASSOCIATED -> heartbeat [label="alloc()",style=dotted]
 | 
			
		||||
heartbeat -> ASSOCIATED [label="EV_HEARTBEAT_FAILURE",style=dotted]
 | 
			
		||||
 | 
			
		||||
txrx2 [label="PFCP socket",shape="box"]
 | 
			
		||||
txrx2 -> ASSOCIATED [label="EV_RX_ASSOC_UPDATE_REQ\n3GPP TS 29.244 6.2.7.3.1",style=dotted]
 | 
			
		||||
GRACEFUL_RELEASE -> txrx2 [label="tx_assoc_update_resp()",style=dotted]
 | 
			
		||||
 | 
			
		||||
cp_session [label="PFCP CP session FSM",shape=box3d]
 | 
			
		||||
cp -> ASSOCIATED [label="cp_peer_session_create()",style=dotted]
 | 
			
		||||
ASSOCIATED -> cp_session [label="cp_session_create()",style=dotted]
 | 
			
		||||
cp -> cp_session [style=invisible,arrowhead=none]
 | 
			
		||||
 | 
			
		||||
ASSOCIATED -> GRACEFUL_RELEASE [label="Association Update\nindicating graceful release"]
 | 
			
		||||
 | 
			
		||||
cp -> ASSOCIATED [label="cp_peer_release()",style=dotted]
 | 
			
		||||
ASSOCIATED -> term [label="cp_peer_release()\nHeartbeat failure"]
 | 
			
		||||
 | 
			
		||||
ASSOCIATED -> WAIT_ASSOC_SETUP_RESP [label="Heartbeat failure"]
 | 
			
		||||
 | 
			
		||||
GRACEFUL_RELEASE -> term
 | 
			
		||||
term [shape="octagon"]
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								doc/charts/pfcp_cp_session_fsm.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								doc/charts/pfcp_cp_session_fsm.dot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=TB
 | 
			
		||||
labelloc=t; label="PFCP CP session FSM"
 | 
			
		||||
 | 
			
		||||
cp [label="CP function",shape=box]
 | 
			
		||||
cp -> WAIT_ESTABLISHMENT_RESP [label="cp_session_create(cp_peer)\niff cp_peer in state ASSOCIATED"]
 | 
			
		||||
 | 
			
		||||
txrx [label="PFCP socket",shape=box]
 | 
			
		||||
 | 
			
		||||
WAIT_ESTABLISHMENT_RESP -> txrx [label="tx_session_est_req()",style=dotted]
 | 
			
		||||
txrx -> WAIT_ESTABLISHMENT_RESP [label="EV_RX_SESSION_EST_RESP",style=dotted]
 | 
			
		||||
 | 
			
		||||
WAIT_ESTABLISHMENT_RESP -> ESTABLISHED [label="Est Resp"]
 | 
			
		||||
 | 
			
		||||
cp -> ESTABLISHED [label="cp_session_modify()",style=dotted]
 | 
			
		||||
ESTABLISHED -> WAIT_MODIFICATION_RESP [label="cp_session_modify()"]
 | 
			
		||||
WAIT_MODIFICATION_RESP -> txrx [label="tx_session_mod_req()",style=dotted]
 | 
			
		||||
txrx -> WAIT_MODIFICATION_RESP [label="EV_RX_SESSION_MOD_RESP",style=dotted,constraint=false]
 | 
			
		||||
WAIT_MODIFICATION_RESP -> ESTABLISHED [label="Mod Resp"]
 | 
			
		||||
 | 
			
		||||
cp -> ESTABLISHED [label="cp_session_delete()",style=dotted]
 | 
			
		||||
ESTABLISHED -> WAIT_DELETION_RESP [label="cp_session_delete()"]
 | 
			
		||||
WAIT_DELETION_RESP -> txrx [label="tx_session_del_req()",style=dotted]
 | 
			
		||||
txrx -> WAIT_DELETION_RESP [label="EV_RX_SESSION_DEL_RESP",style=dotted,constraint=false]
 | 
			
		||||
WAIT_DELETION_RESP -> term
 | 
			
		||||
term [shape="octagon"]
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								doc/charts/pfcp_heartbeat_fsm.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								doc/charts/pfcp_heartbeat_fsm.dot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=TB
 | 
			
		||||
labelloc=t; label="PFCP heartbeat FSM"
 | 
			
		||||
 | 
			
		||||
peer [label="PFCP CP/UP peer FSM",shape=box3d]
 | 
			
		||||
txrx [label="PFCP socket",shape=box]
 | 
			
		||||
 | 
			
		||||
peer -> IDLE [label="alloc()"]
 | 
			
		||||
IDLE -> WAIT_HEARTBEAT_RESP -> IDLE
 | 
			
		||||
WAIT_HEARTBEAT_RESP -> term
 | 
			
		||||
term [shape="octagon"]
 | 
			
		||||
 | 
			
		||||
WAIT_HEARTBEAT_RESP -> txrx [label="tx_heartbeat_req()",style=dotted]
 | 
			
		||||
txrx -> WAIT_HEARTBEAT_RESP [label="HEARTBEAT_EV_RX_RESP",style=dotted]
 | 
			
		||||
 | 
			
		||||
term -> peer [label="PEER_EV_HEARTBEAT_FAILURE",style=dotted]
 | 
			
		||||
 | 
			
		||||
txrx2 [label="PFCP socket",shape=box]
 | 
			
		||||
txrx2 -> txrx2 [label="rx Heartbeat Req\ntx Heartbeat Resp",style=dotted]
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								doc/charts/pfcp_msgs.ladder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								doc/charts/pfcp_msgs.ladder
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
{hscale=1}
 | 
			
		||||
upf = User Plane function
 | 
			
		||||
cpf = Control Plane function
 | 
			
		||||
 | 
			
		||||
cpf () .	Look up UPF,
 | 
			
		||||
		pick any one of the available
 | 
			
		||||
		IP addrs for the UPF
 | 
			
		||||
 | 
			
		||||
...
 | 
			
		||||
upf <> cpf	not yet associated
 | 
			
		||||
upf () cpf	reject any session related msgs
 | 
			
		||||
...
 | 
			
		||||
 | 
			
		||||
upf < cpf	PFCP Association Setup Request
 | 
			
		||||
		CP function Node Id, features
 | 
			
		||||
upf > cpf	PFCP Association Setup Response
 | 
			
		||||
		UP function Node Id, features
 | 
			
		||||
upf <> cpf	associated
 | 
			
		||||
upf () cpf	start Heartbeat checking
 | 
			
		||||
...
 | 
			
		||||
upf < cpf	Heartbeat Request
 | 
			
		||||
upf > cpf	Heartbeat Response
 | 
			
		||||
...
 | 
			
		||||
upf > cpf	Heartbeat Request
 | 
			
		||||
upf < cpf	Heartbeat Response
 | 
			
		||||
...
 | 
			
		||||
 | 
			
		||||
upf < cpf	Session Establishment Request
 | 
			
		||||
		CP Node-Id
 | 
			
		||||
		CP F-SEID
 | 
			
		||||
		1+ Packet Detection Rule(s)
 | 
			
		||||
		1+ Forward Action Rule(s)
 | 
			
		||||
upf > cpf	Session Establishment Response
 | 
			
		||||
 | 
			
		||||
upf < cpf	Session Modification Request
 | 
			
		||||
upf > cpf	Session Modification Response
 | 
			
		||||
 | 
			
		||||
upf < cpf	Session Deletion Request
 | 
			
		||||
upf > cpf	Session Deletion Response
 | 
			
		||||
 | 
			
		||||
...
 | 
			
		||||
upf () cpf	F-SEID: accept any other IP addrs than peer's Node Id
 | 
			
		||||
...
 | 
			
		||||
 | 
			
		||||
---		Graceful release initiated by CP
 | 
			
		||||
 | 
			
		||||
upf < cpf	Association Update Request
 | 
			
		||||
		with PFCP Association Release Preparation Start = 1
 | 
			
		||||
upf > cpf	Association Update Response
 | 
			
		||||
upf > cpf	Session Report Request
 | 
			
		||||
		to report non-zero usage reports,
 | 
			
		||||
		at least one message per PFCP Session
 | 
			
		||||
upf < cpf	Association Release Request
 | 
			
		||||
upf > cpf	Association Release Response
 | 
			
		||||
 | 
			
		||||
---		Graceful release initiated by UP
 | 
			
		||||
 | 
			
		||||
upf > cpf	Association Update Request
 | 
			
		||||
		with PFCP Association Release Preparation = 1
 | 
			
		||||
cpf <> .	refrain from establishing sessions
 | 
			
		||||
upf < cpf	Association Update Response
 | 
			
		||||
upf < cpf	Session Deletion Request(s)
 | 
			
		||||
		to collect usage reports
 | 
			
		||||
		per session
 | 
			
		||||
upf > cpf	Session Deletion Response(s)
 | 
			
		||||
cpf () .	wait Graceful Release Period
 | 
			
		||||
upf < cpf	Association Release Request
 | 
			
		||||
upf > cpf	Association Release Response
 | 
			
		||||
 | 
			
		||||
---		Release (immediate)
 | 
			
		||||
 | 
			
		||||
upf < cpf	Association Release Request
 | 
			
		||||
upf > cpf	Association Release Response
 | 
			
		||||
							
								
								
									
										142
									
								
								doc/charts/pfcp_msgs_gtp.ladder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								doc/charts/pfcp_msgs_gtp.ladder
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
{hscale=1}
 | 
			
		||||
sgsn =	SGSN
 | 
			
		||||
	123.44.0.9
 | 
			
		||||
sgwc =	SGW-C
 | 
			
		||||
	123.44.0.5
 | 
			
		||||
sgwu =	SGW-U
 | 
			
		||||
	123.44.0.6
 | 
			
		||||
pgwc =	PGW-C
 | 
			
		||||
	123.44.0.7
 | 
			
		||||
pgwu =	PGW-U
 | 
			
		||||
	123.44.0.8
 | 
			
		||||
 | 
			
		||||
sgsn <-> sgwc	S4-C GTPv2-C
 | 
			
		||||
sgwc <-> sgwu	Sxa PFCP
 | 
			
		||||
sgsn <-> sgwu	S4-U GTPv1-U
 | 
			
		||||
sgwc <-> pgwc	S5-C GTPv2-C
 | 
			
		||||
pgwc <-> pgwu	Sxb PFCP
 | 
			
		||||
sgwu <-> pgwu	S5-U GTPv1-U
 | 
			
		||||
 | 
			
		||||
...
 | 
			
		||||
 | 
			
		||||
sgsn -> sgwc	GTP Create Session Request
 | 
			
		||||
sgsn [] sgwc	F-TEID S11 = 123.44.0.9,0x004
 | 
			
		||||
		F-TEID S5 = 123.44.0.7,0x000
 | 
			
		||||
		PDN addr alloc = IPv4 192.168.100.2
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgwc -> sgwu	PFCP Session Establishment Request
 | 
			
		||||
sgwc [] sgwu	2x Create PDR
 | 
			
		||||
		F-TEID = CHOOSE
 | 
			
		||||
		FAR = NOCP,BUFF
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgwc <- sgwu	PFCP Session Establishment Response
 | 
			
		||||
sgwc [] sgwu	Created PDR F-TEID 123.44.0.6,0x015
 | 
			
		||||
		Created PDR F-TEID 123.44.0.6,0x016
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgwc -> pgwc	GTP Create Session Request
 | 
			
		||||
sgwc [] pgwc	F-TEID S5 = 123.44.0.5,0x00b
 | 
			
		||||
		PDN addr alloc = IPv4 192.168.100.2
 | 
			
		||||
		Bearer Ctx: F-TEID S5 = 123.44.0.6,0x015
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
pgwc -> pgwu	PFCP Session Establishment Request
 | 
			
		||||
pgwc [] pgwu	Create PDR 1:
 | 
			
		||||
		PDI: src-iface Core, UE IPv4 192.168.100.2
 | 
			
		||||
		FAR-1: FORW, dst-iface Access,
 | 
			
		||||
		hdr creation: GTP-U 123.44.0.6,0x015
 | 
			
		||||
 | 
			
		||||
		Create PDR 2:
 | 
			
		||||
		PDI: src-iface Access, F-TEID = CHOOSE id:05
 | 
			
		||||
		hdr removal: GTP-U
 | 
			
		||||
		FAR-2: FORW, dst-iface Core
 | 
			
		||||
 | 
			
		||||
		Create PDR 3:
 | 
			
		||||
		PDI: src-iface CP-function, F-TEID = CHOOSE
 | 
			
		||||
		hdr removal: GTP-U
 | 
			
		||||
		FAR-1
 | 
			
		||||
 | 
			
		||||
		Create PDR 4:
 | 
			
		||||
		PDI: src-iface Access, F-TEID = CHOOSE id:05, SDF Filter
 | 
			
		||||
		hdr removal: GTP-U
 | 
			
		||||
		FAR-3: FORW, dst-iface CP-Function,
 | 
			
		||||
		hdr creation: GTP-U 123.44.0.7,0x00b
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
pgwc <- pgwu	PFCP Session Establishment Response
 | 
			
		||||
pgwc [] pgwu	Created PDR-1
 | 
			
		||||
		Created PDR-2: F-TEID = 123.44.0.8,0x01e
 | 
			
		||||
		Created PDR-3: F-TEID = 123.44.0.8,0x01f
 | 
			
		||||
		Created PDR-4: F-TEID = 123.44.0.8,0x01e
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgwc <- pgwc	GTP Create Session Response
 | 
			
		||||
sgwc [] pgwc	TEID: 0x00b
 | 
			
		||||
		F-TEID: 123.44.0.7,0x00b
 | 
			
		||||
		PDN Addr: 192.168.100.2
 | 
			
		||||
		Bearer Ctx: F-TEID S5 123.44.0.8,0x01e
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgwc -> sgwu	PFCP Session Modification Request
 | 
			
		||||
sgwc [] sgwu	Update FAR-2: FORW, dst-iface Core,
 | 
			
		||||
		hdr creation GTP-U 123.44.0.8,0x01e
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgwc <- sgwu	PFCP Session Modification Response
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgsn <- sgwc	GTP Create Session Response
 | 
			
		||||
sgsn [] sgwc	TEID: 0x004
 | 
			
		||||
		F-TEID S11/S4: 123.44.0.5,0x007
 | 
			
		||||
		F-TEID S5/S8: 123.44.0.7,0x00b
 | 
			
		||||
		PDN Addr: 192.168.100.2
 | 
			
		||||
		Bearer Ctx:
 | 
			
		||||
		F-TEID S1-U: 123.44.0.6,0x016
 | 
			
		||||
		F-TEID S5/S8: 123.44.0.8,0x01e
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgsn -> sgwc	GTP Modify Bearer Request
 | 
			
		||||
sgsn [] sgwc	TEID: 0x007
 | 
			
		||||
		Bearer Ctx:
 | 
			
		||||
		F-TEID S1-U: 192.168.104.167,0x32adb2ad
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgwc -> sgwu	PFCP Session Modification Request
 | 
			
		||||
sgwc [] sgwu	Update FAR-1: FORW, dst-iface Access,
 | 
			
		||||
		hdr creation: GTP-U 192.168.104.167,0x32adb2ad
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sgwc <- sgwu	PFCP Session Modification Response
 | 
			
		||||
 | 
			
		||||
|||
 | 
			
		||||
 | 
			
		||||
sgsn <- sgwc	GTP Modify Bearer Response
 | 
			
		||||
sgsn [] sgwc	TEID: 0x004
 | 
			
		||||
		Bearer Ctx:
 | 
			
		||||
		F-TEID S1-U: 192.168.104.167,0x32adb2ad
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								doc/charts/pfcp_overview.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								doc/charts/pfcp_overview.dot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=TB
 | 
			
		||||
labelloc=t; label="PFCP Overview\n3GPP TS 29.244 3.1, 5.8.1"
 | 
			
		||||
 | 
			
		||||
subgraph cluster_N1_CP {
 | 
			
		||||
	label="Node: Control Plane function";style=dotted
 | 
			
		||||
	N1_E_CP [label="CP Entity"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
subgraph cluster_N2_UP {
 | 
			
		||||
	label="Node: User Plane function\nNode ID: my-userplane.com\n(FQDN may provide multiple PFCP Entities)";style=dotted
 | 
			
		||||
	N2_E_UP [label="UP Entity\n8.7.6.1"]
 | 
			
		||||
	N2_E_UP2 [label="UP Entity\n8.7.6.2"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
subgraph cluster_N3_UP {
 | 
			
		||||
	label="Node: User Plane function\nNode ID: 1.2.3.4\n(IP address means only one PFCP Entity)";style=dotted
 | 
			
		||||
	N3_E_UP [label="UP Entity\n1.2.3.4\n(osmo-upf)"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
N1_E_CP -> N3_E_UP [label="PFCP Request"]
 | 
			
		||||
N1_E_CP -> N2_E_UP
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								doc/charts/pfcp_up_peer_fsm.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								doc/charts/pfcp_up_peer_fsm.dot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=TB
 | 
			
		||||
labelloc=t; label="PFCP UP peer FSM\nUser Plane side, managing association with remote CP peer"
 | 
			
		||||
 | 
			
		||||
txrx [label="PFCP socket",shape="box"]
 | 
			
		||||
 | 
			
		||||
txrx -> NOT_ASSOCIATED [label="rx PFCP msg from\nnew remote IP"]
 | 
			
		||||
txrx -> NOT_ASSOCIATED [label="EV_RX_ASSOC_SETUP_REQ",style=dotted]
 | 
			
		||||
 | 
			
		||||
NOT_ASSOCIATED -> ASSOCIATED [label="Assoc Setup Req",shape="box"]
 | 
			
		||||
 | 
			
		||||
heartbeat [label="PFCP heartbeat FSM",shape=box3d]
 | 
			
		||||
ASSOCIATED -> heartbeat [label="alloc()",style=dotted]
 | 
			
		||||
heartbeat -> ASSOCIATED [label="EV_HEARTBEAT_FAILURE",style=dotted]
 | 
			
		||||
 | 
			
		||||
txrx -> ASSOCIATED [label="EV_RX_SESSION_EST_REQ",style=dotted]
 | 
			
		||||
up_session [label="PFCP UP session FSM",shape=box3d]
 | 
			
		||||
ASSOCIATED -> up_session [label="up_session_create()",style=dotted]
 | 
			
		||||
 | 
			
		||||
txrx -> ASSOCIATED [label="EV_RX_ASSOC_UPD_REQ",style=dotted]
 | 
			
		||||
ASSOCIATED -> GRACEFUL_RELEASE [label="Association Update\nindicating graceful release"]
 | 
			
		||||
 | 
			
		||||
ASSOCIATED -> term [label="Heartbeat failure"]
 | 
			
		||||
GRACEFUL_RELEASE -> term
 | 
			
		||||
term [shape="octagon"]
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								doc/charts/pfcp_up_session_fsm.dot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								doc/charts/pfcp_up_session_fsm.dot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=TB
 | 
			
		||||
labelloc=t; label="PFCP UP session FSM"
 | 
			
		||||
 | 
			
		||||
peer [label="PFCP UP peer FSM",shape=box3d]
 | 
			
		||||
peer -> ESTABLISHED [label="rx_session_est_req()"]
 | 
			
		||||
 | 
			
		||||
txrx [label="PFCP socket",shape="box"]
 | 
			
		||||
txrx2 [label="PFCP socket",shape="box"]
 | 
			
		||||
 | 
			
		||||
txrx -> ESTABLISHED [label="EV_RX_SESSION_MOD_REQ",style=dotted]
 | 
			
		||||
ESTABLISHED -> txrx [label="tx_session_mod_resp()",style=dotted,constraint=false]
 | 
			
		||||
ESTABLISHED -> ESTABLISHED [label="Mod"]
 | 
			
		||||
 | 
			
		||||
txrx2 -> ESTABLISHED [label="EV_RX_SESSION_DEL_REQ",style=dotted]
 | 
			
		||||
ESTABLISHED -> txrx2 [label="tx_session_del_resp()",style=dotted,constraint=false]
 | 
			
		||||
 | 
			
		||||
ESTABLISHED -> term [label="Deletion"]
 | 
			
		||||
term [shape="octagon"]
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,16 @@
 | 
			
		||||
log stderr
 | 
			
		||||
 logging filter all 1
 | 
			
		||||
 logging color 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging timestamp 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print level 1
 | 
			
		||||
 logging level set-all notice
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print extended-timestamp 1
 | 
			
		||||
 logging level set-all info
 | 
			
		||||
#logging level set-all debug
 | 
			
		||||
 | 
			
		||||
timer pfcp x24 5000
 | 
			
		||||
pfcp
 | 
			
		||||
 local-addr 127.0.0.1
 | 
			
		||||
tunend
 | 
			
		||||
gtp
 | 
			
		||||
 dev create apn23
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
log stderr
 | 
			
		||||
 logging filter all 1
 | 
			
		||||
 logging color 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging timestamp 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print level 1
 | 
			
		||||
 logging level set-all notice
 | 
			
		||||
 | 
			
		||||
timer pfcp x24 5000
 | 
			
		||||
pfcp
 | 
			
		||||
 local-addr 127.0.0.1
 | 
			
		||||
tunend
 | 
			
		||||
 mockup
 | 
			
		||||
tunmap
 | 
			
		||||
 mockup
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
log stderr
 | 
			
		||||
 logging filter all 1
 | 
			
		||||
 logging color 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging timestamp 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print level 1
 | 
			
		||||
 logging print category 1
 | 
			
		||||
 logging print category-hex 0
 | 
			
		||||
 logging print file basename last
 | 
			
		||||
 logging print extended-timestamp 1
 | 
			
		||||
 logging level set-all debug
 | 
			
		||||
 logging level set-all notice
 | 
			
		||||
 logging level set-all info
 | 
			
		||||
 | 
			
		||||
timer pfcp x24 5000
 | 
			
		||||
pfcp
 | 
			
		||||
 
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
[[netinst]]
 | 
			
		||||
== Local GTP Addresses / Network Instance
 | 
			
		||||
 | 
			
		||||
PFCP features optional Network Instance IEs, in which the CPF may tell the UPF which local network interface to use for
 | 
			
		||||
a PDR and/or a FAR.
 | 
			
		||||
 | 
			
		||||
NOTE:: osmo-upf only evaluates the Network Instances configured in PDRs. Since osmo-upf always pairs a PDR+FAR with
 | 
			
		||||
another PDR+FAR in reverse direction, each side's PDR is sufficient.
 | 
			
		||||
 | 
			
		||||
Network Instance IEs affect both the tunend and the tunmap use cases, as well as which local IP address is returned
 | 
			
		||||
in the PFCP response
 | 
			
		||||
 | 
			
		||||
1. Look up Network Instance name in the osmo-upf.cfg `netinst` section, to obtain a local IP address.
 | 
			
		||||
2. Depending on use case:
 | 
			
		||||
  - tunend: create the tunnel on a GTP device matching the local IP address, see <<gtp_module>>.
 | 
			
		||||
  - tunmap: use the local IP address in the netfilter ruleset, see <<nftables>>.
 | 
			
		||||
3. Usually, return the chosen local IP address in the F-TEID IE of the Created PDR IE in the PFCP response.
 | 
			
		||||
 | 
			
		||||
Network Instance configuration consists of {name, IP address} pairs.
 | 
			
		||||
 | 
			
		||||
NOTE:: As soon as a `netinst` configuration is nonempty, receiving an undefined Network Instance name results in a PFCP
 | 
			
		||||
Reject response, and a log message on cateogry `session`, level `NOTICE`. To make the PFCP return success, add the
 | 
			
		||||
failing name to the `netinst` config.
 | 
			
		||||
 | 
			
		||||
=== netinst for tunend
 | 
			
		||||
 | 
			
		||||
The following configuration sets up two GTP devices for tunend, expecting Network Instance names `access1` or `access2`:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunend
 | 
			
		||||
 dev create apn1 10.0.0.1
 | 
			
		||||
 dev create apn2 10.0.0.2
 | 
			
		||||
netinst
 | 
			
		||||
 add access1 10.0.0.1
 | 
			
		||||
 add access2 10.0.0.2
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
For example, if a Create PDR IE indicates Network Instance = `access1`, a GTP tunnel is set up in GTP kernel device
 | 
			
		||||
`apn1`. For `access2`, use `apn2`.
 | 
			
		||||
 | 
			
		||||
=== netinst for tunmap
 | 
			
		||||
 | 
			
		||||
For the tunmap use case, it is sufficient to configure `netinst` entries, without any addition to the `tunmap` section.
 | 
			
		||||
The following example configures various interfaces for tunmap, to match Network Instance names received in PFCP:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunmap
 | 
			
		||||
 table-name osmo-upf
 | 
			
		||||
netinst
 | 
			
		||||
 add access1 10.0.0.1
 | 
			
		||||
 add access2 10.0.0.2
 | 
			
		||||
 add core1 9.0.0.1
 | 
			
		||||
 add core2 9.0.0.2
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
For example, a Create PDR indicating a Network Instance of `core1` will result in an nftables rule that receives packets
 | 
			
		||||
on local address `9.0.0.1`.
 | 
			
		||||
@@ -22,189 +22,3 @@ The aim is to provide:
 | 
			
		||||
- 1000 modifications of tunnel state per second (add/remove/modify),
 | 
			
		||||
- 4-8 Gbps throughput,
 | 
			
		||||
- 100-125k concurrent GTP tunnels.
 | 
			
		||||
 | 
			
		||||
A typical network scenario using OsmoUPF is illustrated in the following
 | 
			
		||||
diagram:
 | 
			
		||||
 | 
			
		||||
.Typical network architecture used with OsmoUPF
 | 
			
		||||
[graphviz]
 | 
			
		||||
----
 | 
			
		||||
digraph G {
 | 
			
		||||
  rankdir = LR;
 | 
			
		||||
 | 
			
		||||
  UE [label="UE\n(3G phone)"]
 | 
			
		||||
 | 
			
		||||
  subgraph cluster_hnbgw_mgw_upf {
 | 
			
		||||
    style=dotted
 | 
			
		||||
    HNBGW -> UPF [label="PFCP",constraint=false]
 | 
			
		||||
    UPF [label=OsmoUPF,style=bold]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  subgraph cluster_hnbgw_mgw_upf2 {
 | 
			
		||||
    style=dotted
 | 
			
		||||
    SGSN -> UPF2 [label="PFCP",constraint=false]
 | 
			
		||||
    UPF2 [label=OsmoUPF,style=bold]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  subgraph cluster_hnbgw_mgw_upf3 {
 | 
			
		||||
    style=dotted
 | 
			
		||||
    GGSN -> UPF3 [label="PFCP",constraint=false]
 | 
			
		||||
    UPF3 [label=OsmoUPF,style=bold]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hNodeB [shape="box",label="hNodeB\n(3G femto cell)"]
 | 
			
		||||
 | 
			
		||||
  UE -> hNodeB [label="Uu"]
 | 
			
		||||
  hNodeB -> HNBGW [label="Iuh",style=dashed]
 | 
			
		||||
  STP [label="STP\n(SCCP/M3UA)"]
 | 
			
		||||
  HNBGW -> STP -> SGSN [label="IuPS",style=dashed]
 | 
			
		||||
  SGSN -> GGSN [label="GTP-C",style="dashed"]
 | 
			
		||||
  hNodeB -> UPF -> UPF2 -> UPF3 [label="GTP-U"]
 | 
			
		||||
  UPF3 -> internet [label="apn"]
 | 
			
		||||
}
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
NOTE: at the time of writing this section, the only Osmocom component providing
 | 
			
		||||
a PFCP CPF interface is OsmoHNBGW. PFCP support has not yet made its way into
 | 
			
		||||
OsmoSGSN nor OsmoGGSN.
 | 
			
		||||
 | 
			
		||||
=== the PFCP interface
 | 
			
		||||
 | 
			
		||||
PFCP is specified by 3GPP TS 29.244.
 | 
			
		||||
 | 
			
		||||
OsmoUPF implements a PFCP User Plane Function interface, listening for PFCP
 | 
			
		||||
requests from PFCP Control Plane Function clients, to carry out proxy-relaying
 | 
			
		||||
and encapsulation/decapsulation of GTP tunnels.
 | 
			
		||||
 | 
			
		||||
OsmoUPF does not support the complete PFCP feature set. It detects exactly two
 | 
			
		||||
use cases that will provide service of actual GTP tunnels:
 | 
			
		||||
 | 
			
		||||
.tunend use case
 | 
			
		||||
----
 | 
			
		||||
Access                 osmo-upf              Core
 | 
			
		||||
 PGW                      |              PDN/internet
 | 
			
		||||
  |                PDR1:  > FAR1:             |
 | 
			
		||||
  |                IP/GTP | IP                |
 | 
			
		||||
  |        ------> F-TEID |            -----> |
 | 
			
		||||
  |                       |                   |
 | 
			
		||||
  |                FAR2:  < PDR2:             |
 | 
			
		||||
  |                IP/GTP | IP                |
 | 
			
		||||
  | F-TEID <------        | UE IP addr <----- |
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
* `tunend`: GTP tunnel encapsulation/decapsulation:
 | 
			
		||||
  - One Packet Detection Rule (PDR) accepts a GTP tunnel from the Access side
 | 
			
		||||
    with an Outer Header Removal.
 | 
			
		||||
  - This PDR uses a Forwarding Action Rule (FAR) for plain IP towards Core.
 | 
			
		||||
  - Another PDR accepts plain IP on a specific IP address from Core.
 | 
			
		||||
  - The second PDR uses a FAR towards Access with Outer Header Creation for GTP.
 | 
			
		||||
 | 
			
		||||
.tunmap use case
 | 
			
		||||
----
 | 
			
		||||
Access                 osmo-upf                 Core
 | 
			
		||||
 PGW                      |                     PGW
 | 
			
		||||
  |                PDR1:  > FAR1:                |
 | 
			
		||||
  |                IP/GTP | IP/GTP               |
 | 
			
		||||
  |        ------> F-TEID |        -----> F-TEID |
 | 
			
		||||
  |                       |                      |
 | 
			
		||||
  |                FAR2:  < PDR2:                |
 | 
			
		||||
  |                IP/GTP | IP/GTP               |
 | 
			
		||||
  | F-TEID <------        | F-TEID <-----        |
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
* `tunmap`: GTP tunnel forwarding:
 | 
			
		||||
  - One Packet Detection Rule (PDR) accepts a GTP tunnel from the Access side
 | 
			
		||||
    with an Outer Header Removal.
 | 
			
		||||
  - This PDR uses a Forwarding Action Rule (FAR) towards Core with an Outer
 | 
			
		||||
    Header Creation for GTP.
 | 
			
		||||
  - A second PDR+FAR pair like above, with Access and Core swapped.
 | 
			
		||||
 | 
			
		||||
Access and Core must be indicated by the Source Interface IE (PDR) and
 | 
			
		||||
Destination Interface IE (FAR) in PFCP.
 | 
			
		||||
 | 
			
		||||
Any set of rules only partially or not at all matching the above PDR and FAR
 | 
			
		||||
rules will not result in any actions on the GTP user plane, but will still
 | 
			
		||||
return a successful outcome in the PFCP messages.
 | 
			
		||||
 | 
			
		||||
For example, a rule set using a Source Interface other than "Access" or "Core" results
 | 
			
		||||
in a PFCP no-op, returning PFCP responses with successful outcome, but not
 | 
			
		||||
providing any GTP-U service.
 | 
			
		||||
 | 
			
		||||
This is a direct result of:
 | 
			
		||||
 | 
			
		||||
- allowing PFCP rule sets to be setup incrementally by several subsequent PFCP
 | 
			
		||||
  messages, and of
 | 
			
		||||
- OsmoUPF using Linux kernel features for the GTP user plane, where there is
 | 
			
		||||
  either a full bidirectional GTP tunnel in place or none at all.
 | 
			
		||||
 | 
			
		||||
For example, for `tunmap`, a typical CPF will establish a PFCP session in two
 | 
			
		||||
steps: first request a local F-TEID from the UPF before passing on a data
 | 
			
		||||
service request from Access to Core. When the Core side has responded with its
 | 
			
		||||
GTP details, the PFCP session at the UPF is updated (Session Modifification),
 | 
			
		||||
to form a complete PFCP rule set.
 | 
			
		||||
 | 
			
		||||
.Typical sequence of establishing a GTP-U tunnel relay
 | 
			
		||||
["mscgen"]
 | 
			
		||||
----
 | 
			
		||||
msc {
 | 
			
		||||
	hscale="1";
 | 
			
		||||
	sgsn[label="SGSN"],sgwc[label="SGW-C"],sgwu[label="SGW-U"],pgwc[label="PGW-C"];
 | 
			
		||||
 | 
			
		||||
	sgsn << pgwc [label="Access"];
 | 
			
		||||
	sgsn >> pgwc [label="Core"];
 | 
			
		||||
 | 
			
		||||
	sgsn => sgwc [label="GTP Create Session Request\n\n\n"];
 | 
			
		||||
 | 
			
		||||
	|||;
 | 
			
		||||
 | 
			
		||||
	sgwc => sgwu [label="PFCP Session Establishment Request\n\n2x Create PDR\nF-TEID = CHOOSE"];
 | 
			
		||||
 | 
			
		||||
	|||;
 | 
			
		||||
 | 
			
		||||
	sgwc <= sgwu [label="PFCP Session Establishment Response\n\n2x Created PDR\nwith chosen local F-TEID"];
 | 
			
		||||
 | 
			
		||||
	|||;
 | 
			
		||||
 | 
			
		||||
	sgwc => pgwc [label="GTP Create Session Request\nwith chosen local F-TEID towards Core"];
 | 
			
		||||
	sgwc <= pgwc [label="GTP Create Session Response\nwith remote F-TEID at Core"];
 | 
			
		||||
 | 
			
		||||
	|||;
 | 
			
		||||
 | 
			
		||||
	sgwc => sgwu [label="PFCP Session Modification Request\n\nUpdate FAR\nwith remote F-TEID at Core"];
 | 
			
		||||
 | 
			
		||||
	|||;
 | 
			
		||||
 | 
			
		||||
	sgwc <= sgwu [label="PFCP Session Modification Response\n\n\n"];
 | 
			
		||||
 | 
			
		||||
	|||;
 | 
			
		||||
 | 
			
		||||
	sgsn <= sgwc [label="GTP Create Session Response\n\n\n"];
 | 
			
		||||
}
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
The OsmoUPF logging as well as the VTY interface yield information on whether a
 | 
			
		||||
ruleset results in an actual bidirectional GTP tunnel being set up.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
=== the GTP interface
 | 
			
		||||
 | 
			
		||||
OsmoUPF requires the following Linux kernel features to provide the GTP user
 | 
			
		||||
plane functionality:
 | 
			
		||||
 | 
			
		||||
- the Linux kernel GTP module for encapsulation/decapsulation between GTP and
 | 
			
		||||
  plain IP.
 | 
			
		||||
- the Linux netfilter nftables feature for relaying GTP, i.e. forwarding between
 | 
			
		||||
  two GTP tunnels.
 | 
			
		||||
 | 
			
		||||
Tunnel relaying with netfilter requires at least Linux kernel 5.17.
 | 
			
		||||
 | 
			
		||||
To be able to interact with these Linux kernel features, the osmo-upf binary
 | 
			
		||||
needs cap_net_admin privileges, as in:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
sudo setcap cap_net_admin+pe /usr/bin/osmo-upf
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
Without above Linux kernel features, or when no cap_net_admin is available,
 | 
			
		||||
OsmoUPF is only useful for testing PFCP clients: the GTP features may be run in
 | 
			
		||||
mockup mode, so that OsmoUPF serves as a "dry run" PFCP server.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,269 +0,0 @@
 | 
			
		||||
== Running OsmoUPF
 | 
			
		||||
 | 
			
		||||
The OsmoUPF executable (`osmo-upf`) offers the following command-line
 | 
			
		||||
arguments:
 | 
			
		||||
 | 
			
		||||
=== SYNOPSIS
 | 
			
		||||
 | 
			
		||||
*osmo-upf* [-h|-V] [-D] [-c 'CONFIGFILE']
 | 
			
		||||
 | 
			
		||||
=== OPTIONS
 | 
			
		||||
 | 
			
		||||
*-h, --help*::
 | 
			
		||||
	Print a short help message about the supported options
 | 
			
		||||
*-V, --version*::
 | 
			
		||||
	Print the compile-time version number of the OsmoHNBGW program
 | 
			
		||||
*-D, --daemonize*::
 | 
			
		||||
	Fork the process as a daemon into background.
 | 
			
		||||
*-c, --config-file 'CONFIGFILE'*::
 | 
			
		||||
	Specify the file and path name of the configuration file to be
 | 
			
		||||
	used. If none is specified, use `osmo-upf.cfg` in the current
 | 
			
		||||
	working directory.
 | 
			
		||||
 | 
			
		||||
=== Multiple instances
 | 
			
		||||
 | 
			
		||||
Running multiple instances of `osmo-upf` on the same computer is possible if
 | 
			
		||||
all interfaces (VTY, CTRL, PFCP) are separated using the appropriate
 | 
			
		||||
configuration options. The IP based interfaces are binding to local host by
 | 
			
		||||
default. In order to separate the processes, the user has to bind those
 | 
			
		||||
services to different ports, or different specific IP addresses.
 | 
			
		||||
 | 
			
		||||
The VTY and the Control interface can be bound to IP addresses from the loopback
 | 
			
		||||
address range, for example:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
line vty
 | 
			
		||||
 bind 127.0.0.2
 | 
			
		||||
ctrl
 | 
			
		||||
 bind 127.0.0.2
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
The PFCP port is specified to be fixed as port 8805. Hence, each osmo-upf
 | 
			
		||||
process needs to run on a distinct local interface:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
pfcp
 | 
			
		||||
  local-addr 10.9.0.2
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
For GTP encapsulation/decapsulation and GTP tunnel relaying, osmo-upf depends on
 | 
			
		||||
the IP addresses configured at the Linux kernel GTP module, and the IP addresses
 | 
			
		||||
negotiated within PFCP by the control plane function.
 | 
			
		||||
 | 
			
		||||
If multiple `osmo-upf` processes are running on the same Linux kernel, each
 | 
			
		||||
`osmo-upf` needs to be configured with a distinct netfilter table name, so that
 | 
			
		||||
naming of individual tunnel rulesets does not collide:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunmap
 | 
			
		||||
 table-name osmo-upf-2
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
=== Configure PFCP Server
 | 
			
		||||
 | 
			
		||||
The following example configures OsmoUPF to listen for PFCP association requests
 | 
			
		||||
from Control Plane Function entities on local interface 10.9.8.7, port 8805:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
pfcp
 | 
			
		||||
 local-addr 10.9.8.7
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
3GPP TS 29.244 4.2.2 specifies that PFCP Request messages shall be sent to UDP
 | 
			
		||||
port 8805, i.e. the PFCP port is fixed as 8805 and currently not configurable in
 | 
			
		||||
osmo-upf.
 | 
			
		||||
 | 
			
		||||
Setting a 'local-addr' is required: the PFCP protocol features a Node ID, which
 | 
			
		||||
uniquely identifies PFCP peers across different interfaces. According to the
 | 
			
		||||
PFCP specification, the Node ID can be a fully-qualified domain name (FQDN) or
 | 
			
		||||
an IP address. Currently, osmo-upf has no support for using an FQDN as Node
 | 
			
		||||
ID, and so far uses the 'local-addr' as local Node ID -- hence the 'local-addr'
 | 
			
		||||
must not be "0.0.0.0", which is an unfortunate consequence. This is likely to
 | 
			
		||||
improve in the future, see https://osmocom.org/issues/5682 .
 | 
			
		||||
 | 
			
		||||
=== Linux Kernel Features
 | 
			
		||||
 | 
			
		||||
OsmoUPF uses two distinct Linux kernel features:
 | 
			
		||||
 | 
			
		||||
* The GTP module is used for `tunend`: GTP encapsulation/decapsulation from/to
 | 
			
		||||
  "the internet".
 | 
			
		||||
 | 
			
		||||
* The netfilter framework and nftables are used for `tunmap`: GTP tunnel proxying,
 | 
			
		||||
  also known as tunnel forwarding or tunnel mapping.
 | 
			
		||||
 | 
			
		||||
.Linux kernel feature usage
 | 
			
		||||
[graphviz]
 | 
			
		||||
----
 | 
			
		||||
include::upf_gtp_roles.dot[]
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
GTP kernel module configuration in the `tunend` section can be omitted for sites
 | 
			
		||||
that serve only as GTP forwarding proxy, without encapsulation/decapsulation of
 | 
			
		||||
GTP payloads -- except to provide GTP Echo service, see <<gtp_echo>>.
 | 
			
		||||
 | 
			
		||||
Netfilter configuration in the `tunmap` section can be omitted for sites only
 | 
			
		||||
serving as GTP tunnel endpoint.
 | 
			
		||||
 | 
			
		||||
[[gtp_module]]
 | 
			
		||||
=== Configure Linux Kernel GTP Module for `tunend`
 | 
			
		||||
 | 
			
		||||
The Linux kernel GTP module is used for the `tunend` use case, i.e. GTP
 | 
			
		||||
encapsulation/decapsulation from/to "the internet".
 | 
			
		||||
 | 
			
		||||
To use the GTP kernel module, OsmoUPF requires a GTP device, which is a
 | 
			
		||||
dedicated network device provided by the Linux kernel, serving as GTP tunnel
 | 
			
		||||
endpoint. It is typically named like "apn0".
 | 
			
		||||
 | 
			
		||||
`osmo-upf` can either create a GTP device on startup, or use a pre-existing GTP
 | 
			
		||||
device. To en/decapsulate GTP, the APN device needs to be assigned an IP address
 | 
			
		||||
range that matches the UE IP addresses that are configured in GTP-C / PFCP.
 | 
			
		||||
 | 
			
		||||
The following configuration placed in `osmo-upf.cfg` creates a GTP device called
 | 
			
		||||
`apn23` on startup of osmo-upf, which is destroyed on program exit. It listens
 | 
			
		||||
for GTP on local IP address `1.2.3.4`:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunend
 | 
			
		||||
 dev create apn23 1.2.3.4
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
TODO:: `osmo-upf` is not yet able to configure this network device's IP address
 | 
			
		||||
range, MTU etc.
 | 
			
		||||
 | 
			
		||||
The following configuration placed in `osmo-upf.cfg` uses a pre-existing device
 | 
			
		||||
called `apn42`:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunend
 | 
			
		||||
 dev use apn42 2.3.4.5
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
GTP kernel devices can be managed manually using the `gtp-link` program
 | 
			
		||||
available from the 'libgtpnl' project:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
# gtp-link add apn42
 | 
			
		||||
(keep this process running)
 | 
			
		||||
# ip addr add dev apn42 192.168.42.1/24
 | 
			
		||||
 | 
			
		||||
$ osmo-upf -c osmo-upf.cfg
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
It is possible to configure multiple GTP devices in `osmo-upf.cfg`. Depending on
 | 
			
		||||
the Network Instance name, osmo-upf creates tunnel endpoints on the GTP device
 | 
			
		||||
with a matching IP address:
 | 
			
		||||
 | 
			
		||||
- The Network Instance IE in the PDR on the Access side determines the local IP
 | 
			
		||||
  address to use, see <<netinst>>.
 | 
			
		||||
- This local IP address in turn determines the GTP device to use.
 | 
			
		||||
 | 
			
		||||
It is possible for a GTP device to listen on ANY -- just omit the IP address in
 | 
			
		||||
the `dev` config. In this case, all Network Instance names will be served by
 | 
			
		||||
this GTP device. When using ANY, there should be exactly one GTP dev configured.
 | 
			
		||||
 | 
			
		||||
[[nftables]]
 | 
			
		||||
=== Configure Linux netfilter for `tunmap`
 | 
			
		||||
 | 
			
		||||
The Linux kernel netfilter module is used for GTP tunnel proxying, also known as
 | 
			
		||||
tunnel forwarding or tunnel mapping.
 | 
			
		||||
 | 
			
		||||
When using the netfilter module, you may set up `osmo-upf.cfg` for:
 | 
			
		||||
- GTP Echo (required)
 | 
			
		||||
- nft table name (optional)
 | 
			
		||||
 | 
			
		||||
[[gtp_echo]]
 | 
			
		||||
==== GTP Echo
 | 
			
		||||
 | 
			
		||||
You need to ensure that OsmoUPF responds to GTP Echo requests.
 | 
			
		||||
- A GTP device configured for `tunend` implicitly includes a GTP Echo service.
 | 
			
		||||
- For `tunmap`, no GTP Echo mechanism is implemented.
 | 
			
		||||
 | 
			
		||||
So, when your use case is `tunmap` only, you should still add a GTP device as
 | 
			
		||||
for `tunend`, only to provide the GTP Echo service.
 | 
			
		||||
 | 
			
		||||
Here are some options to do so:
 | 
			
		||||
 | 
			
		||||
If you have no GTP devices configured in `osmo-upf.cfg` yet, you can add a
 | 
			
		||||
single GTP device without a specific IP address, in order to respond to GTP-U
 | 
			
		||||
Echo requests on all interfaces to anyone that is asking:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunend
 | 
			
		||||
 dev create gtp-echo
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
Note that `gtp-echo` is just an arbitrary GTP device name, choose any string
 | 
			
		||||
that makes a valid network device name and is still available, as in the `dev`
 | 
			
		||||
argument in the `ip addr show dev` command on Linux.
 | 
			
		||||
 | 
			
		||||
This will bind osmo-upf on 0.0.0.0:2152 to respond to GTP Echo requests.
 | 
			
		||||
 | 
			
		||||
If you would like to limit GTP Echo responses to specific network interfaces,
 | 
			
		||||
you need to add a separate GTP device per local IP address:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunend
 | 
			
		||||
 dev create gtp-echo1 192.168.0.23
 | 
			
		||||
 dev create gtp-echo2 10.9.8.17
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
This will bind osmo-upf only on 192.168.0.23:2152 and 10.9.8.17:2152 to respond
 | 
			
		||||
to GTP Echo requests.
 | 
			
		||||
 | 
			
		||||
For creating and manipulating a GTP device in more versatile ways, see
 | 
			
		||||
<<gtp_module>>.
 | 
			
		||||
 | 
			
		||||
==== nft Table Name
 | 
			
		||||
 | 
			
		||||
For `tunmap`, `osmo-upf` creates a new nft table, under which it submits
 | 
			
		||||
rule sets for GTP tunnel proxying. This table name defaults to `osmo-upf`. A
 | 
			
		||||
custom table name can be configured in `osmo-upf.cfg` like this:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
tunmap
 | 
			
		||||
 table-name my-table-name
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
When running more than one osmo-upf process on a system, pick distinct table
 | 
			
		||||
names to avoid name collisions in the nftables rulesets.
 | 
			
		||||
 | 
			
		||||
=== IP Forwarding
 | 
			
		||||
 | 
			
		||||
In order to allow forwarding GTP payloads, the Linux operating system must
 | 
			
		||||
be configured to allow IP forwarding.
 | 
			
		||||
 | 
			
		||||
Note that there are many distribution-specific ways to configure this, and there
 | 
			
		||||
might be higher-level firewall rule management software available like `ufw`.
 | 
			
		||||
You should configure firewall rules matching your distribution and setup.
 | 
			
		||||
 | 
			
		||||
To allow IP forwarding from and to all interfaces globally in a reboot-safe way,
 | 
			
		||||
you may put a line like this in /etc/sysctl.conf:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
net.ipv4.ip_forward=1
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
To do the same in an ad-hoc way that is not reboot safe but takes effect
 | 
			
		||||
immediately:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
It is also possible to instruct the firewall to allow IP forwarding for specific
 | 
			
		||||
network devices only. For example, on a Debian based system, place an nft
 | 
			
		||||
ruleset like this in `/etc/nftables.conf`:
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
define gtp_netdevs = { eth0, eth23 };
 | 
			
		||||
 | 
			
		||||
table inet filter {
 | 
			
		||||
      chain forward {
 | 
			
		||||
              type filter hook forward priority filter; policy drop;
 | 
			
		||||
              iifname $gtp_netdevs oifname $gtp_netdevs udp dport 2152 accept
 | 
			
		||||
      }
 | 
			
		||||
}
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
This ruleset allows IP forwarding, but limited to the GTP-U port 2152,
 | 
			
		||||
and to two specific network devices eth0 and eth23.
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
digraph G {
 | 
			
		||||
rankdir=LR
 | 
			
		||||
sgsn [label="SGSN"]
 | 
			
		||||
 | 
			
		||||
subgraph cluster_sgw {
 | 
			
		||||
	style=invisible
 | 
			
		||||
	sgwc [label="SGW-C"]
 | 
			
		||||
	sgwu [label="OsmoUPF as SGW-U\ntunnel proxy\n*netfilter* kernel module",style=bold,shape=box]
 | 
			
		||||
	sgwc -> sgwu [label="PFCP",constraint=false]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
subgraph cluster_pgw {
 | 
			
		||||
	style=invisible
 | 
			
		||||
	pgwc [label="PGW-C"]
 | 
			
		||||
	pgwu [label="OsmoUPF as PGW-U\ntunnel proxy\n*netfilter* kernel module",style=bold,shape=box]
 | 
			
		||||
	pgwc -> pgwu [label="PFCP",constraint=false]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
subgraph cluster_tdf {
 | 
			
		||||
	style=invisible
 | 
			
		||||
	tdfc [label="TDF-C"]
 | 
			
		||||
	tdfu [label="OsmoUPF as TDF-U\ntunnel en-/decaps\n*GTP* kernel module",style=bold,shape=box]
 | 
			
		||||
	tdfc -> tdfu [label="PFCP",constraint=false]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pdn [label="PDN\n'the internet'"]
 | 
			
		||||
 | 
			
		||||
sgsn -> sgwc -> pgwc -> tdfc [label="GTP-C"]
 | 
			
		||||
sgsn -> sgwu -> pgwu -> tdfu [label="GTP-U",dir=both]
 | 
			
		||||
tdfu -> pdn [label="IP",dir=both]
 | 
			
		||||
}
 | 
			
		||||
@@ -9,10 +9,6 @@ include::./common/chapters/preface.adoc[]
 | 
			
		||||
 | 
			
		||||
include::{srcdir}/chapters/overview.adoc[]
 | 
			
		||||
 | 
			
		||||
include::{srcdir}/chapters/running.adoc[]
 | 
			
		||||
 | 
			
		||||
include::{srcdir}/chapters/netinst.adoc[]
 | 
			
		||||
 | 
			
		||||
include::./common/chapters/vty.adoc[]
 | 
			
		||||
 | 
			
		||||
include::./common/chapters/logging.adoc[]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	gtlv \
 | 
			
		||||
	pfcp \
 | 
			
		||||
	upf \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								include/osmocom/gtlv/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								include/osmocom/gtlv/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
tlv_HEADERS = \
 | 
			
		||||
	gtlv.h \
 | 
			
		||||
	gtlv_dec_enc.h \
 | 
			
		||||
	gtlv_gen.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
tlvdir = $(includedir)/osmocom/gtlv
 | 
			
		||||
							
								
								
									
										157
									
								
								include/osmocom/gtlv/gtlv.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								include/osmocom/gtlv/gtlv.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
struct msgb;
 | 
			
		||||
struct osmo_gtlv_load;
 | 
			
		||||
struct osmo_gtlv_put;
 | 
			
		||||
struct value_string;
 | 
			
		||||
 | 
			
		||||
struct osmo_gtlv_tag_inst {
 | 
			
		||||
	unsigned int tag;
 | 
			
		||||
	bool instance_present;
 | 
			
		||||
	unsigned int instance;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_tag_inst_cmp(const struct osmo_gtlv_tag_inst *a, const struct osmo_gtlv_tag_inst *b);
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_tag_inst_to_str_buf(char *buf, size_t buflen, const struct osmo_gtlv_tag_inst *ti,
 | 
			
		||||
				 const struct value_string *tag_names);
 | 
			
		||||
char *osmo_gtlv_tag_inst_to_str_c(void *ctx, const struct osmo_gtlv_tag_inst *ti,
 | 
			
		||||
				 const struct value_string *tag_names);
 | 
			
		||||
 | 
			
		||||
/*! TL configuration for osmo_gtlv_load*() and osmo_gtlv_put*(). Depending on these implementations provided by the caller,
 | 
			
		||||
 * osmo_gtlv can load any sizes of tag and length fields (that don't surpass the value range of unsigned int and size_t,
 | 
			
		||||
 * respectively), as well as TV (fixed-length) or TvLV (variable-sized length).
 | 
			
		||||
 *
 | 
			
		||||
 * See osmo_t8l8v_cfg and osmo_t16l16v_cfg, ready implementations for plain 8bit and 16bit TLV protocols.
 | 
			
		||||
 *
 | 
			
		||||
 * libosmo-pfcp serves as example for using this entire TLV API, uncluding de/encoding to structs and generating parts
 | 
			
		||||
 * of the TLV parsing code based on message definitions. It uses osmo_t16l16v_cfg.
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_gtlv_cfg {
 | 
			
		||||
	/*! The length in bytes of the shortest possible TL header (e.g. 4 for T16L16V, or 1 for 8bit tags where TV IEs
 | 
			
		||||
	 * without a length exist). A src_data_len passed to store_tl() below is guaranteed to be >= this value. If at
 | 
			
		||||
	 * any point there is remaining message data smaller than this value, a parsing error is returned.
 | 
			
		||||
	 */
 | 
			
		||||
	size_t tl_min_size;
 | 
			
		||||
 | 
			
		||||
	/*! Read one TL from the start of src_data.
 | 
			
		||||
	 * \param gtlv  Return the T (tag) value read from src_data in gtlv->tag.
 | 
			
		||||
	 *             Return the L (length) value read from src_data in gtlv->len.
 | 
			
		||||
	 *             Return the I (instance) value read from src_data in gtlv->len; ignore if there is no I.
 | 
			
		||||
	 *             Return the position just after the TL in gtlv->*val. If there is V data, point at the start of the
 | 
			
		||||
	 *             V data in src_data. If there is no V data, point at the byte just after the TL part in src_data.
 | 
			
		||||
	 * \param src_data  Part of raw message being decoded.
 | 
			
		||||
	 * \param src_data_len  Remaining message data length at src_data.
 | 
			
		||||
	 * \return 0 on success, negative on error.
 | 
			
		||||
	 */
 | 
			
		||||
	int (*load_tl)(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t src_data_len);
 | 
			
		||||
 | 
			
		||||
	/*! Write a TL to dst_data, and return the size of the TL written.
 | 
			
		||||
	 * This is also invoked by osmo_gtlv_put_update_tl() to overwrite a previous TL header. If the TL part's size
 | 
			
		||||
	 * can be different than the first time (e.g. due to a large L value in a TvLV protocol), an implementation can
 | 
			
		||||
	 * use the 'gtlv' arg to figure out how to memmove the message data:
 | 
			
		||||
	 * When invoked by osmo_gtlv_put_tl(), dst_data == gtlv->dst->tail and dst_data_avail == msgb_tailroom().
 | 
			
		||||
	 * When invoked by osmo_gtlv_put_update_tl(), dst_data < gtlv->dst->tail, dst_data points at the start of the
 | 
			
		||||
	 * TL section written earlier by osmo_gtlv_put_tl() and dst_data_avail == the size of the TL written earlier.
 | 
			
		||||
	 *
 | 
			
		||||
	 * \param dst_data  Write TL data to the start of this buffer.
 | 
			
		||||
	 * \param dst_data_avail  Remaining available space in dst_data.
 | 
			
		||||
	 * \param tag  The T value to store in dst_data.
 | 
			
		||||
	 * \param instance  The I value to store in dst_data (if this tag is a TLIV); ignore when not a TLIV.
 | 
			
		||||
	 * \param len  The L value to store in dst_data.
 | 
			
		||||
	 * \param gtlv  Backpointer to the osmo_gtlv_put struct, including gtlv->dst, the underlying msgb.
 | 
			
		||||
	 * \return the size of the TL part in bytes on success, -EINVAL if tag is invalid, -EMSGSIZE if len is too large
 | 
			
		||||
	 * or dst_data_avail is too small for the TL.
 | 
			
		||||
	 */
 | 
			
		||||
	int (*store_tl)(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_gtlv_tag_inst *ti, size_t len,
 | 
			
		||||
			struct osmo_gtlv_put *gtlv);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Configuration that allows parsing an 8bit tag and 8bit length TLV. */
 | 
			
		||||
extern const struct osmo_gtlv_cfg osmo_t8l8v_cfg;
 | 
			
		||||
 | 
			
		||||
/*! Configuration that allows parsing a 16bit tag and 16bit length TLV (see for example PFCP). */
 | 
			
		||||
extern const struct osmo_gtlv_cfg osmo_t16l16v_cfg;
 | 
			
		||||
 | 
			
		||||
/*! State for loading a TLV structure from raw data. */
 | 
			
		||||
struct osmo_gtlv_load {
 | 
			
		||||
	/*! Caller-defined context pointer available for use by load_tl() and store_tl() implementations. */
 | 
			
		||||
	void *priv;
 | 
			
		||||
 | 
			
		||||
	/*! Definition of tag and length sizes (by function pointers). */
 | 
			
		||||
	const struct osmo_gtlv_cfg *cfg;
 | 
			
		||||
 | 
			
		||||
	/*! Overall message buffer being parsed. */
 | 
			
		||||
	struct {
 | 
			
		||||
		const uint8_t *data;
 | 
			
		||||
		size_t len;
 | 
			
		||||
	} src;
 | 
			
		||||
 | 
			
		||||
	/*! Return value from last invocation of osmo_gtlv_load_next*(): tag value of parsed IE. */
 | 
			
		||||
	struct osmo_gtlv_tag_inst ti;
 | 
			
		||||
	/*! Return value from last invocation of osmo_gtlv_load_next*(): Start of the IE's payload data (after tag and
 | 
			
		||||
	 * length). If the end of the src buffer is reached, val == NULL. If a TLV contained no value part, len == 0,
 | 
			
		||||
	 * but this still points just after the TL. */
 | 
			
		||||
	const uint8_t *val;
 | 
			
		||||
	/*! Return value from last invocation of osmo_gtlv_load_next*(): Length of the IE's payload data (without tag and
 | 
			
		||||
	 * length) */
 | 
			
		||||
	size_t len;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Start or restart the gtlv from the first IE in the overall TLV data. */
 | 
			
		||||
static inline void osmo_gtlv_load_start(struct osmo_gtlv_load *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	gtlv->val = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_load_next(struct osmo_gtlv_load *gtlv);
 | 
			
		||||
int osmo_gtlv_load_peek_tag(const struct osmo_gtlv_load *gtlv, struct osmo_gtlv_tag_inst *ti);
 | 
			
		||||
int osmo_gtlv_load_next_by_tag(struct osmo_gtlv_load *gtlv, unsigned int tag);
 | 
			
		||||
int osmo_gtlv_load_next_by_tag_inst(struct osmo_gtlv_load *gtlv, const struct osmo_gtlv_tag_inst *ti);
 | 
			
		||||
 | 
			
		||||
/* State for storing a TLV structure into a msgb. */
 | 
			
		||||
struct osmo_gtlv_put {
 | 
			
		||||
	/*! Caller-defined context pointer available for use by load_tl() and store_tl() implementations. */
 | 
			
		||||
	void *priv;
 | 
			
		||||
 | 
			
		||||
	/* Definition of tag and length sizes (by function pointers). */
 | 
			
		||||
	const struct osmo_gtlv_cfg *cfg;
 | 
			
		||||
 | 
			
		||||
	/* msgb to append new TL to */
 | 
			
		||||
	struct msgb *dst;
 | 
			
		||||
	/* What was the last TL written and where are its TL and V */
 | 
			
		||||
	struct osmo_gtlv_tag_inst last_ti;
 | 
			
		||||
	uint8_t *last_tl;
 | 
			
		||||
	uint8_t *last_val;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_put_tl(struct osmo_gtlv_put *gtlv, unsigned int tag, size_t len);
 | 
			
		||||
int osmo_gtlv_put_tli(struct osmo_gtlv_put *gtlv, const struct osmo_gtlv_tag_inst *ti, size_t len);
 | 
			
		||||
int osmo_gtlv_put_update_tl(struct osmo_gtlv_put *gtlv);
 | 
			
		||||
							
								
								
									
										201
									
								
								include/osmocom/gtlv/gtlv_dec_enc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								include/osmocom/gtlv/gtlv_dec_enc.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
/* Decode and encode the value parts of a TLV structure */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gtlv/gtlv.h>
 | 
			
		||||
 | 
			
		||||
struct value_string;
 | 
			
		||||
 | 
			
		||||
/* User defined function to decode a single TLV value part. See struct osmo_gtlv_coding.
 | 
			
		||||
 * \param decoded_struct  Pointer to the root struct, as context information, e.g. for logging.
 | 
			
		||||
 * \param decode_to  Pointer to the struct member, write the decoded value here.
 | 
			
		||||
 * \param gtlv  TLV loader, pointing at a gtlv->val of gtlv->len bytes.
 | 
			
		||||
 * \return 0 on success, nonzero on error, e.g. -EINVAL if the gtlv->val is invalid.
 | 
			
		||||
 */
 | 
			
		||||
typedef int (*osmo_gtlv_dec_func)(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv);
 | 
			
		||||
 | 
			
		||||
/* User defined function to encode a single TLV value part. See struct osmo_gtlv_coding.
 | 
			
		||||
 * \param gtlv  TLV writer, pointing at a gtlv->dst to msgb_put() data in.
 | 
			
		||||
 * \param decoded_struct  Pointer to the root struct, as context information, e.g. for logging.
 | 
			
		||||
 * \param encode_from  Pointer to the struct member, obtain the value to encode from here.
 | 
			
		||||
 * \return 0 on success, nonzero on error, e.g. -EINVAL if encode_from has an un-encodable value.
 | 
			
		||||
 */
 | 
			
		||||
typedef int (*osmo_gtlv_enc_func)(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from);
 | 
			
		||||
 | 
			
		||||
/* Optional user defined function to convert a decoded IE struct (the Value part stored as C struct) to string. See
 | 
			
		||||
 * struct osmo_gtlv_coding.
 | 
			
		||||
 * \param buf  Return string in this buffer.
 | 
			
		||||
 * \param buflen  Size of buf.
 | 
			
		||||
 * \param str_of  Pointer to the struct member described by an osmo_gtlv_coding, obtain the value to encode from here.
 | 
			
		||||
 * \return number of characters that would be written if the buffer is large enough, like snprintf().
 | 
			
		||||
 */
 | 
			
		||||
typedef int (*osmo_gtlv_enc_to_str_func)(char *buf, size_t buflen, const void *str_of);
 | 
			
		||||
 | 
			
		||||
/* Whether TLV structures nested inside the value data of an outer IE should be parsed in the same order. */
 | 
			
		||||
enum osmo_gtlv_coding_nested_ies_ordered {
 | 
			
		||||
	/*! When stepping into nested IEs, keep the same ordering requirement as the outer IE. */
 | 
			
		||||
	OSMO_GTLV_NESTED_IES_ORDERING_SAME = 0,
 | 
			
		||||
	/*! Require IEs in a PDU to appear exactly in the order defined by osmo_gtlv_coding arrays. Causes a parsing
 | 
			
		||||
	 * failure if the TLVs appear in a different order. Does much less iterating looking for matching tags when
 | 
			
		||||
	 * decoding (faster). */
 | 
			
		||||
	OSMO_GTLV_NESTED_IES_ORDERED,
 | 
			
		||||
	/*! Do not require IEs to be in the defined order in decoded PDUs. When encoding a TLV, IEs will always be
 | 
			
		||||
	 * encoded in the order they are defined. This has an effect on decoding only. */
 | 
			
		||||
	OSMO_GTLV_NESTED_IES_UNORDERED,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define OSMO_ARRAY_PITCH(arr) ((char *)(&(arr)[1]) - (char *)(arr))
 | 
			
		||||
#define OSMO_MEMB_ARRAY_PITCH(obj_type, arr_memb) OSMO_ARRAY_PITCH(((obj_type *)0)->arr_memb)
 | 
			
		||||
 | 
			
		||||
/*! Definition of how to decode/encode a IE to/from a struct.
 | 
			
		||||
 * Kept in lists describing TLV structures, and nestable.
 | 
			
		||||
 *
 | 
			
		||||
 * Instance lists of this can be composed manually, or auto-generated using gtlv_gen.c. Auto-generating has the benefit
 | 
			
		||||
 * that the decoded structs to match the IEs are also generated at the same time and thus always match the message
 | 
			
		||||
 * definitions. For an example, see tests/libosmo-gtlv/test_gtlv_gen/. */
 | 
			
		||||
struct osmo_gtlv_coding {
 | 
			
		||||
	/*! the IEI discriminator, and optional instance number */
 | 
			
		||||
	struct osmo_gtlv_tag_inst ti;
 | 
			
		||||
 | 
			
		||||
	/*! Decoding function callback. Invoked for each defined and present IE encountered in the message.
 | 
			
		||||
	 * Return 0 on success, negative on failure. */
 | 
			
		||||
	osmo_gtlv_dec_func dec_func;
 | 
			
		||||
	/*! Encoding function callback. Invoked for each defined and present IE encountered in the message.
 | 
			
		||||
	 * Return 0 on success, negative on failure. */
 | 
			
		||||
	osmo_gtlv_enc_func enc_func;
 | 
			
		||||
 | 
			
		||||
	/*! Means to output the decoded value to a human readable string, optional. */
 | 
			
		||||
	osmo_gtlv_enc_to_str_func enc_to_str_func;
 | 
			
		||||
 | 
			
		||||
	/*! offsetof(decoded_struct_type, member_var): how far into the base struct you find a specific field for decoded
 | 
			
		||||
	 * value. For example, memb_ofs = offsetof(struct foo_msg, ies.bar_response.cause).
 | 
			
		||||
	 * When decoding, the decoded value is written here, when encoding it is read from here. */
 | 
			
		||||
	unsigned int memb_ofs;
 | 
			
		||||
	/*! For repeated IEs (.has_count = true), the array pitch / the offset to add to get to the next array index. */
 | 
			
		||||
	unsigned int memb_array_pitch;
 | 
			
		||||
 | 
			
		||||
	/*! True for optional/conditional IEs. */
 | 
			
		||||
	bool has_presence_flag;
 | 
			
		||||
	/* For optional/conditional IEs (has_presence_flag = true), the offset of the bool foo_present flag,
 | 
			
		||||
	 * For example, if there are
 | 
			
		||||
	 *
 | 
			
		||||
	 * struct foo_msg {
 | 
			
		||||
	 *         struct baz baz;
 | 
			
		||||
	 *         bool baz_present;
 | 
			
		||||
	 * };
 | 
			
		||||
	 *
 | 
			
		||||
	 * then set
 | 
			
		||||
	 * memb_ofs = offsetof(struct foo_msg, baz);
 | 
			
		||||
	 * has_presence_flag = true;
 | 
			
		||||
	 * presence_flag_ofs = offsetof(struct foo_msg, baz_present);
 | 
			
		||||
	 */
 | 
			
		||||
	unsigned int presence_flag_ofs;
 | 
			
		||||
 | 
			
		||||
	/*! True for repeated IEs, for array members:
 | 
			
		||||
	 *
 | 
			
		||||
	 * struct foo_msg {
 | 
			
		||||
	 *         struct moo moo[10];
 | 
			
		||||
	 *         unsigned int moo_count;
 | 
			
		||||
	 * };
 | 
			
		||||
	 *
 | 
			
		||||
	 * memb_ofs = offsetof(struct foo_msg, moo);
 | 
			
		||||
	 * has_count = true;
 | 
			
		||||
	 * count_ofs = offsetof(struct foo_msg, moo_count);
 | 
			
		||||
	 * count_max = 10;
 | 
			
		||||
	 */
 | 
			
		||||
	bool has_count;
 | 
			
		||||
	/*! For repeated IEs, the offset of the unsigned int foo_count indicator of how many array indexes are
 | 
			
		||||
	 * in use. See has_count. */
 | 
			
		||||
	unsigned int count_ofs;
 | 
			
		||||
	/*! Maximum array size for member_var[]. See has_count. */
 | 
			
		||||
	unsigned int count_max;
 | 
			
		||||
	/*! If nonzero, it is an error when less than this amount of the repeated IE have been decoded. */
 | 
			
		||||
	unsigned int count_mandatory;
 | 
			
		||||
 | 
			
		||||
	/*! For nested TLVs: if this IE's value part is itself a separate TLV structure, point this at the list of IE
 | 
			
		||||
	 * coding definitions for the inner IEs.
 | 
			
		||||
	 * In this example, the nested IEs decode/encode to different sub structs depending on the tag value.
 | 
			
		||||
	 *
 | 
			
		||||
	 *     struct bar {
 | 
			
		||||
	 *             int aaa;
 | 
			
		||||
	 *             int bbb;
 | 
			
		||||
	 *     };
 | 
			
		||||
	 *
 | 
			
		||||
	 *     struct foo_msg {
 | 
			
		||||
	 *             struct bar bar;
 | 
			
		||||
	 *             struct bar other_bar;
 | 
			
		||||
	 *     };
 | 
			
		||||
	 *
 | 
			
		||||
	 *     struct osmo_gtlv_coding bar_nested_ies[] = {
 | 
			
		||||
	 *             { FOO_IEI_AAA, .memb_ofs = offsetof(struct bar, aaa), },
 | 
			
		||||
	 *             { FOO_IEI_BBB, .memb_ofs = offsetof(struct bar, bbb), },
 | 
			
		||||
	 *             {}
 | 
			
		||||
	 *     };
 | 
			
		||||
	 *
 | 
			
		||||
	 *     struct osmo_gtlv_coding foo_msg_ies[] = {
 | 
			
		||||
	 *             { FOO_IEI_GOO, .memb_ofs = offsetof(struct foo_msg, bar), .nested_ies = bar_nested_ies, },
 | 
			
		||||
	 *             { FOO_IEI_OTHER_GOO, .memb_ofs = offsetof(struct foo_msg, other_bar), .nested_ies = bar_nested_ies, },
 | 
			
		||||
	 *             {}
 | 
			
		||||
	 *     };
 | 
			
		||||
	 */
 | 
			
		||||
	const struct osmo_gtlv_coding *nested_ies;
 | 
			
		||||
 | 
			
		||||
	/*! If the nested TLV has a different tag/length size than the outer TLV structure, provide a different config
 | 
			
		||||
	 * here. If they are the same, just keep this NULL. */
 | 
			
		||||
	const struct osmo_gtlv_cfg *nested_ies_cfg;
 | 
			
		||||
 | 
			
		||||
	/*! When stepping into nested IEs, what is the ordering requirement for the nested TLV structure? */
 | 
			
		||||
	enum osmo_gtlv_coding_nested_ies_ordered nested_ies_ordered;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*! User defined hook for error logging during TLV and value decoding.
 | 
			
		||||
 * \param decoded_struct  Pointer to the base struct describing this message, for context.
 | 
			
		||||
 * \param file  Source file of where the error occurred.
 | 
			
		||||
 * \param line  Source file line of where the error occurred.
 | 
			
		||||
 * \param fmt  Error message string format.
 | 
			
		||||
 * \param ...  Error message string args.
 | 
			
		||||
 */
 | 
			
		||||
typedef void (*osmo_gtlv_err_cb)(void *data, void *decoded_struct, const char *file, int line, const char *fmt, ...);
 | 
			
		||||
 | 
			
		||||
int osmo_gtlvs_decode(void *decoded_struct, unsigned int obj_ofs, struct osmo_gtlv_load *gtlv, bool tlv_ordered,
 | 
			
		||||
		     const struct osmo_gtlv_coding *ie_coding,
 | 
			
		||||
		     osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs);
 | 
			
		||||
 | 
			
		||||
int osmo_gtlvs_encode(struct osmo_gtlv_put *gtlv, const void *decoded_struct, unsigned int obj_ofs,
 | 
			
		||||
		     const struct osmo_gtlv_coding *ie_coding,
 | 
			
		||||
		     osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs);
 | 
			
		||||
 | 
			
		||||
int osmo_gtlvs_encode_to_str_buf(char *buf, size_t buflen, const void *decoded_struct, unsigned int obj_ofs,
 | 
			
		||||
				const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs);
 | 
			
		||||
char *osmo_gtlvs_encode_to_str_c(void *ctx, const void *decoded_struct, unsigned int obj_ofs,
 | 
			
		||||
				const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs);
 | 
			
		||||
 | 
			
		||||
static inline bool osmo_gtlv_coding_end(const struct osmo_gtlv_coding *iec)
 | 
			
		||||
{
 | 
			
		||||
	return iec->dec_func == NULL && iec->enc_func == NULL && iec->nested_ies == NULL;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								include/osmocom/gtlv/gtlv_gen.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								include/osmocom/gtlv/gtlv_gen.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
/* Write h and c source files for TLV protocol definitions, based on very sparse TLV definitions.
 | 
			
		||||
 * For a usage example see tests/libosmo-gtlv/test_gtlv_gen/. */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_gtlv_gen_ie;
 | 
			
		||||
 | 
			
		||||
/* O means optional, M means mandatory.
 | 
			
		||||
 * If all of the IE struct, tag name and functions can be derived from the name, just pass osmo_gtlv_gen_ie_auto as
 | 
			
		||||
 * TLV_GEN_IE. */
 | 
			
		||||
#define OSMO_GTLV_GEN_O(TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .optional = true, .ie = &(TLV_GEN_IE) }
 | 
			
		||||
#define OSMO_GTLV_GEN_M(TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .ie = &(TLV_GEN_IE) }
 | 
			
		||||
#define OSMO_GTLV_GEN_O_MULTI(MAX, TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .multi = MAX, .ie = &(TLV_GEN_IE) }
 | 
			
		||||
#define OSMO_GTLV_GEN_M_MULTI(MAX, MAND_COUNT, TLV_GEN_IE, MEMB_NAME) \
 | 
			
		||||
	{ MEMB_NAME, .multi = MAX, .multi_mandatory = MAND_COUNT, .ie = &(TLV_GEN_IE) }
 | 
			
		||||
#define OSMO_GTLV_GEN_O_INST(INSTANCE, TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .optional = true, .instance = INSTANCE, .ie = &TLV_GEN_IE }
 | 
			
		||||
#define OSMO_GTLV_GEN_M_INST(INSTANCE, TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .instance = INSTANCE, .ie = &(TLV_GEN_IE) }
 | 
			
		||||
 | 
			
		||||
#define OSMO_GTLV_GEN_NO_INSTANCE INT_MAX
 | 
			
		||||
 | 
			
		||||
/*! osmo_gtlv_gen_ie with all members == NULL, so that all are derived from the member name. */
 | 
			
		||||
extern const struct osmo_gtlv_gen_ie osmo_gtlv_gen_ie_auto;
 | 
			
		||||
 | 
			
		||||
/*! Modifier for Mandatory/Optional/Multiple around an osmo_gtlv_gen_ie. */
 | 
			
		||||
struct osmo_gtlv_gen_ie_o {
 | 
			
		||||
	/*! The C name of the member in a decoded struct, to be of the type defined by .ie.
 | 
			
		||||
	 * All parts of .ie, if NULL, are derived from this name.
 | 
			
		||||
	 *
 | 
			
		||||
	 * For example, simply this
 | 
			
		||||
	 *
 | 
			
		||||
	 *   struct osmo_gtlv_gen_ie_o foo[] = {
 | 
			
		||||
	 *       OSMO_GTLV_GEN_O("bar", NULL),
 | 
			
		||||
	 *   };
 | 
			
		||||
	 *
 | 
			
		||||
	 * Generates
 | 
			
		||||
	 *
 | 
			
		||||
	 *   struct myproto_msg_foo {
 | 
			
		||||
	 *       struct myproto_ie_bar bar;
 | 
			
		||||
	 *   }
 | 
			
		||||
	 *
 | 
			
		||||
	 * and an osmo_gtlv_coding entry of
 | 
			
		||||
	 *
 | 
			
		||||
	 * { MYPROTO_IEI_BAR,
 | 
			
		||||
	 *   .memb_ofs = offsetof(struct myproto_msg_foo, bar),
 | 
			
		||||
	 *   .dec_func = myproto_dec_bar,
 | 
			
		||||
	 *   .enc_func = myproto_enc_bar,
 | 
			
		||||
	 *   .enc_to_str_func = myproto_enc_to_str_bar,
 | 
			
		||||
	 * }
 | 
			
		||||
	 *
 | 
			
		||||
	 * See also osmo_gtlv_gen_cfg.add_enc_to_str.
 | 
			
		||||
	 */
 | 
			
		||||
	const char *name;
 | 
			
		||||
 | 
			
		||||
	/*! Whether to add a bool foo_present, and to skip encoding/decoding if false.
 | 
			
		||||
	 * Only useful for non-multi IEs (compare OSMO_GTLV_GEN_O_MULTI() vs OSMO_GTLV_GEN_M_MULTI()). */
 | 
			
		||||
	bool optional;
 | 
			
		||||
 | 
			
		||||
	/*! If non-NULL, the member is an array: foo[123] with an unsigned int foo_count.
 | 
			
		||||
	 * Set to the maximum number of array elements; for foo[123] set .multi = 123. */
 | 
			
		||||
	unsigned int multi;
 | 
			
		||||
	/*! Number of mandatory occurences of the IE, only has an effect if .multi > 0. */
 | 
			
		||||
	unsigned int multi_mandatory;
 | 
			
		||||
 | 
			
		||||
	/* If any, the instance nr to match, in C that yields an unsigned int.
 | 
			
		||||
	 * e.g. "1" or "MYPROTO_FOO_INST_ONE". */
 | 
			
		||||
	const char *instance;
 | 
			
		||||
 | 
			
		||||
	/*! IE decoding / encoding instructions. If NULL, the entire IE definition is derived from .name.
 | 
			
		||||
	 * 'MYPROTO_IEI_NAME', 'myproto_dec_name()', 'myproto_enc_name()', 'myproto_enc_to_str_name()'.
 | 
			
		||||
	 * Your myproto_ies_custom.h needs to define an enum value MYPROTO_IEI_NAME and*/
 | 
			
		||||
	const struct osmo_gtlv_gen_ie *ie;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Define decoding and encoding of a single IE, i.e. one full TLV. */
 | 
			
		||||
struct osmo_gtlv_gen_ie {
 | 
			
		||||
	/*! like "uint32_t" or "struct foo".
 | 
			
		||||
	 * If NULL, use "struct myproto_ie_<name>" instead, where <name> comes from the osmo_gtlv_gen_ie_o.
 | 
			
		||||
	 * When there are nested IEs, the struct definition is auto-generated, deriving the struct members from the
 | 
			
		||||
	 * nested_ies list.
 | 
			
		||||
	 * When there are no nested IEs, the type needs to be defined manually by a myproto_ies_custom.h. */
 | 
			
		||||
	const char *decoded_type;
 | 
			
		||||
 | 
			
		||||
	/*! C name of this tag value, e.g. "foo" to use tag "MYPROTO_IEI_FOO".
 | 
			
		||||
	 * If NULL, take "MYPROTO_IEI_"+upper(memb_name) instead, where memb_name comes from the osmo_gtlv_gen_ie_o.
 | 
			
		||||
	 * decoded_type and/or dec_enc may be derived from this, if they are NULL. */
 | 
			
		||||
	const char *tag_name;
 | 
			
		||||
 | 
			
		||||
	/*! Name suffix of the dec/enc functions. "foo" -> myproto_dec_foo(), myproto_enc_foo(),
 | 
			
		||||
	 * myproto_enc_to_str_foo().
 | 
			
		||||
	 * These functions need to be implemented manually in a myproto_ies_custom.c.
 | 
			
		||||
	 * When osmo_gtlv_gen_cfg.add_enc_to_str is false, the myproto_enc_to_str_foo() is not required. */
 | 
			
		||||
	const char *dec_enc;
 | 
			
		||||
 | 
			
		||||
	/*! List of inner IEs terminated by {}. If non-NULL, this is a "Grouped IE" with an inner TLV structure inside
 | 
			
		||||
	 * this IE's V part. */
 | 
			
		||||
	const struct osmo_gtlv_gen_ie_o *nested_ies;
 | 
			
		||||
 | 
			
		||||
	/*! To place a spec comment in the generated code. */
 | 
			
		||||
	const char *spec_ref;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! General TLV decoding and encoding definitions applying to all IEs (and nested IEs). */
 | 
			
		||||
struct osmo_gtlv_gen_cfg {
 | 
			
		||||
	/*! Name of the protocol for use in C type or function names, like "myproto". */
 | 
			
		||||
	const char *proto_name;
 | 
			
		||||
 | 
			
		||||
	/*! When placing comments to spec references, prefix with this. For example, "3GPP TS 12.345 ". */
 | 
			
		||||
	const char *spec_ref_prefix;
 | 
			
		||||
 | 
			
		||||
	/*! The type to pass a message discriminator as, like 'enum myproto_message_types' */
 | 
			
		||||
	const char *message_type_enum;
 | 
			
		||||
	/*! To reference a message type discriminator like MYPROTO_MSGT_FOO, this would be "MYPROTO_MSGT_". */
 | 
			
		||||
	const char *message_type_prefix;
 | 
			
		||||
 | 
			
		||||
	/*! Type to use to represent tag IEI in decoded form.
 | 
			
		||||
	 * For example "enum foo_msg_iei". */
 | 
			
		||||
	const char *tag_enum;
 | 
			
		||||
	/*! The tag IEI enum value is uppercase(tag_prefix + (iedef->tag_name or iedef->name)).
 | 
			
		||||
	 * For example, with tag_prefix = "OSMO_FOO_IEI_", we would generate code like
 | 
			
		||||
	 * enum osmo_foo_iei tag = OSMO_FOO_IEI_BAR; */
 | 
			
		||||
	const char *tag_prefix;
 | 
			
		||||
 | 
			
		||||
	/*! When an osmo_gtlv_gen_ie provides no decoded_type string, it is derived from .name and this prefix is
 | 
			
		||||
	 * added. For example, with decoded_type_prefix = "struct foo_ie_", the decoded_type defaults to
 | 
			
		||||
	 * struct foo_ie_bar for an IE definition with name = "bar". */
 | 
			
		||||
	const char *decoded_type_prefix;
 | 
			
		||||
 | 
			
		||||
	/*! To include user defined headers, set to something like "#include <osmocom/foo/foo_tlv_devs.h". This is put at
 | 
			
		||||
	 * the head of the generated .h file. */
 | 
			
		||||
	const char *h_header;
 | 
			
		||||
 | 
			
		||||
	/*! To include user defined headers, set to something like "#include <osmocom/foo/foo_msg.h". This is put at
 | 
			
		||||
	 * the head of the generated .c file. */
 | 
			
		||||
	const char *c_header;
 | 
			
		||||
 | 
			
		||||
	/*! Array of message IE definitions, indexed by message type. */
 | 
			
		||||
	const struct osmo_gtlv_gen_msg *msg_defs;
 | 
			
		||||
 | 
			
		||||
	/*! Whether to add to_str functions. When true, every automatically derived IE (that has no nested IEs) needs to
 | 
			
		||||
	 * have a myproto_enc_to_str_foo() defined by a myproto_ies_custom.c. When false, osmo_gtlvs_encode_to_str_buf()
 | 
			
		||||
	 * will print '?' instead of the IE contents. */
 | 
			
		||||
	bool add_enc_to_str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! For generating the outer union that composes a protocol's PDU variants, an entry of the list of message names and
 | 
			
		||||
 * IEs in each message. */
 | 
			
		||||
struct osmo_gtlv_gen_msg {
 | 
			
		||||
	const char *name;
 | 
			
		||||
	const struct osmo_gtlv_gen_ie_o *ies;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_gen_main(const struct osmo_gtlv_gen_cfg *cfg, int argc, const char **argv);
 | 
			
		||||
							
								
								
									
										25
									
								
								include/osmocom/pfcp/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								include/osmocom/pfcp/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
pfcp_HEADERS = \
 | 
			
		||||
	pfcp_endpoint.h \
 | 
			
		||||
	pfcp_heartbeat_fsm.h \
 | 
			
		||||
	pfcp_ies_custom.h \
 | 
			
		||||
	pfcp_msg.h \
 | 
			
		||||
	pfcp_proto.h \
 | 
			
		||||
	pfcp_strs.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
pfcpdir = $(includedir)/osmocom/pfcp
 | 
			
		||||
 | 
			
		||||
BUILT_SOURCES = \
 | 
			
		||||
	pfcp_ies_auto.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
CLEANFILES = \
 | 
			
		||||
	pfcp_ies_auto.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
pfcp_ies_auto.h: $(top_srcdir)/src/libosmo-pfcp/gen__pfcp_ies_auto.c \
 | 
			
		||||
		 $(top_srcdir)/src/libosmo-gtlv/gtlv_gen.c \
 | 
			
		||||
		 $(top_srcdir)/include/osmocom/gtlv/gtlv_gen.h
 | 
			
		||||
	$(MAKE) -C $(top_builddir)/src/libosmo-gtlv
 | 
			
		||||
	$(MAKE) -C $(top_builddir)/src/libosmo-pfcp gen__pfcp_ies_auto
 | 
			
		||||
	$(top_builddir)/src/libosmo-pfcp/gen__pfcp_ies_auto h > $(builddir)/pfcp_ies_auto.h
 | 
			
		||||
							
								
								
									
										100
									
								
								include/osmocom/pfcp/pfcp_endpoint.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								include/osmocom/pfcp/pfcp_endpoint.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/tdef.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_msg.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_endpoint;
 | 
			
		||||
struct osmo_fsm_inst;
 | 
			
		||||
 | 
			
		||||
#define OSMO_PFCP_TIMER_T1 -22
 | 
			
		||||
#define OSMO_PFCP_TIMER_N1 -23
 | 
			
		||||
#define OSMO_PFCP_TIMER_KEEP_RESP -24
 | 
			
		||||
 | 
			
		||||
extern struct osmo_tdef osmo_pfcp_tdefs[];
 | 
			
		||||
 | 
			
		||||
typedef void (*osmo_pfcp_endpoint_cb)(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m);
 | 
			
		||||
 | 
			
		||||
/* Send/receive PFCP messages to/from remote PFCP endpoints. */
 | 
			
		||||
struct osmo_pfcp_endpoint {
 | 
			
		||||
	struct {
 | 
			
		||||
		/* Local address */
 | 
			
		||||
		struct osmo_sockaddr local_addr;
 | 
			
		||||
		/* Local PFCP Node ID, as sent in outgoing messages' Node ID IE */
 | 
			
		||||
		struct osmo_pfcp_ie_node_id local_node_id;
 | 
			
		||||
 | 
			
		||||
		/* Timer definitions to use, if any. See t1_ms, keep_resp_ms. Use osmo_pfcp_tdefs by default. It is
 | 
			
		||||
		 * convenient to add osmo_pfcp_tdefs as one of your program's osmo_tdef_group entries and call
 | 
			
		||||
		 * osmo_tdef_vty_init() to expose PFCP timers on the VTY. */
 | 
			
		||||
		const struct osmo_tdef *tdefs;
 | 
			
		||||
	} cfg;
 | 
			
		||||
 | 
			
		||||
	/* PFCP socket */
 | 
			
		||||
	struct osmo_fd pfcp_fd;
 | 
			
		||||
 | 
			
		||||
	/* The time at which this endpoint last restarted, as seconds since unix epoch. */
 | 
			
		||||
	uint32_t recovery_time_stamp;
 | 
			
		||||
 | 
			
		||||
	/* State for determining the next sequence number for transmitting a request message */
 | 
			
		||||
	uint32_t seq_nr_state;
 | 
			
		||||
 | 
			
		||||
	/* Callback to enrich the osmo_pfcp_msg->ctx information before decoding, for logging context.
 | 
			
		||||
	 * The osmo_pfcp_msg argument will have a ctx.remote set. The header and IEs may or may not be parsed yet:
 | 
			
		||||
	 * This function is called twice:
 | 
			
		||||
	 * - just after receiving, before decoding; ctx.remote is set.
 | 
			
		||||
	 * - just after decoding the message header; ctx.remote is set and the header is decoded.
 | 
			
		||||
	 * This function may set ctx.peer_fi and ctx.session_fi, used for logging context during message decoding.
 | 
			
		||||
	 * The caller may also use these fi pointers to reduce lookup iterations.
 | 
			
		||||
	 */
 | 
			
		||||
	osmo_pfcp_endpoint_cb set_msg_ctx;
 | 
			
		||||
 | 
			
		||||
	/* Callback to receive single incoming PFCP messages from a remote peer, already decoded. */
 | 
			
		||||
	osmo_pfcp_endpoint_cb rx_msg;
 | 
			
		||||
 | 
			
		||||
	/* application-private data */
 | 
			
		||||
	void *priv;
 | 
			
		||||
 | 
			
		||||
	/* All transmitted messages that are still pending, list of osmo_pfcp_queue_entry.
 | 
			
		||||
	 * For a transmitted Request message, wait for a matching Response from a remote peer; if none arrives,
 | 
			
		||||
	 * retransmit (see n1 and t1_ms).
 | 
			
		||||
	 * For a transmitted Response message, keep it in the queue for a fixed amount of time. If the peer retransmits
 | 
			
		||||
	 * the original Request, do not dispatch the Request, but respond with the queued message directly.
 | 
			
		||||
	 */
 | 
			
		||||
	struct llist_head retrans_queue;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_endpoint *osmo_pfcp_endpoint_create(void *ctx, void *priv);
 | 
			
		||||
int osmo_pfcp_endpoint_bind(struct osmo_pfcp_endpoint *ep);
 | 
			
		||||
void osmo_pfcp_endpoint_close(struct osmo_pfcp_endpoint *ep);
 | 
			
		||||
void osmo_pfcp_endpoint_free(struct osmo_pfcp_endpoint **ep);
 | 
			
		||||
 | 
			
		||||
uint32_t osmo_pfcp_endpoint_next_seq_nr(struct osmo_pfcp_endpoint *ep);
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_endpoint_tx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m);
 | 
			
		||||
int osmo_pfcp_endpoint_tx_data(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m);
 | 
			
		||||
int osmo_pfcp_endpoint_tx_heartbeat_req(struct osmo_pfcp_endpoint *ep, const struct osmo_sockaddr *remote_addr);
 | 
			
		||||
							
								
								
									
										40
									
								
								include/osmocom/pfcp/pfcp_heartbeat_fsm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								include/osmocom/pfcp/pfcp_heartbeat_fsm.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
struct osmo_fsm_inst;
 | 
			
		||||
struct osmo_tdef;
 | 
			
		||||
 | 
			
		||||
enum osmo_pfcp_heartbeat_fsm_event {
 | 
			
		||||
	/* Dispatch this with a struct osmo_pfcp_msg* as data argument whenever a Heartbeat Response matching this
 | 
			
		||||
	 * instance is received. Typically a PFCP Peer responds to a request sent from here. */
 | 
			
		||||
	OSMO_PFCP_HEARTBEAT_EV_RX_RESP,
 | 
			
		||||
	/* Dispatch this with a struct osmo_pfcp_msg* as data argument whenever a Heartbeat Request matching this
 | 
			
		||||
	 * instance is received. Typically a PFCP Peer on its own accord sent a Heartbeat Request. */
 | 
			
		||||
	OSMO_PFCP_HEARTBEAT_EV_RX_REQ,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_fsm_inst *osmo_pfcp_heartbeat_alloc(struct osmo_fsm_inst *parent_fi,
 | 
			
		||||
						uint32_t parent_event_tx_heartbeat, uint32_t parent_event_term,
 | 
			
		||||
						struct osmo_tdef *tdefs);
 | 
			
		||||
							
								
								
									
										163
									
								
								include/osmocom/pfcp/pfcp_ies_custom.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								include/osmocom/pfcp/pfcp_ies_custom.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
			
		||||
/* Definitions for decoded PFCP IEs, to be used by the auto-generated pfcp_ies_auto.c. */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_proto.h>
 | 
			
		||||
 | 
			
		||||
/* Common pattern used in various PFCP IEs. */
 | 
			
		||||
struct osmo_pfcp_ip_addrs {
 | 
			
		||||
	bool v4_present;
 | 
			
		||||
	struct osmo_sockaddr v4;
 | 
			
		||||
	bool v6_present;
 | 
			
		||||
	struct osmo_sockaddr v6;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ip_addrs_set(struct osmo_pfcp_ip_addrs *dst, const struct osmo_sockaddr *addr);
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.38, IETF RFC 1035 3.1 */
 | 
			
		||||
struct osmo_pfcp_ie_node_id {
 | 
			
		||||
	enum osmo_pfcp_node_id_type type;
 | 
			
		||||
	union {
 | 
			
		||||
		struct osmo_sockaddr ip;
 | 
			
		||||
		/* Fully qualified domain name in "dot" notation ("host.example.com") */
 | 
			
		||||
		char fqdn[254];
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool osmo_pfcp_bits_get(const uint8_t *bits, unsigned int bitpos);
 | 
			
		||||
void osmo_pfcp_bits_set(uint8_t *bits, unsigned int bitpos, bool val);
 | 
			
		||||
int osmo_pfcp_bits_to_str_buf(char *buf, size_t buflen, const uint8_t *bits, const struct value_string *bit_strs);
 | 
			
		||||
char *osmo_pfcp_bits_to_str_c(void *ctx, const uint8_t *bits, const struct value_string *bit_str);
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.25
 | 
			
		||||
 * Usage:
 | 
			
		||||
 *     struct osmo_pfcp_ie_up_function_features x;
 | 
			
		||||
 *     osmo_pfcp_bits_set(x.bits, OSMO_PFCP_UP_FEAT_BUNDL, true);
 | 
			
		||||
 *     if (osmo_pfcp_bits_get(x.bits, OSMO_PFCP_UP_FEAT_BUNDL))
 | 
			
		||||
 *             foo();
 | 
			
		||||
 *     printf("%s\n", osmo_pfcp_bits_to_str_c(x.bits, osmo_pfcp_up_feature_strs));
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_pfcp_ie_up_function_features {
 | 
			
		||||
	uint8_t bits[6];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.58
 | 
			
		||||
 *     struct osmo_pfcp_ie_cp_function_features x;
 | 
			
		||||
 *     osmo_pfcp_bits_set(x.bits, OSMO_PFCP_CP_FEAT_BUNDL, true);
 | 
			
		||||
 *     if (osmo_pfcp_bits_get(x.bits, OSMO_PFCP_CP_FEAT_BUNDL))
 | 
			
		||||
 *             foo();
 | 
			
		||||
 *     printf("%s\n", osmo_pfcp_bits_to_str_c(x.bits, osmo_pfcp_cp_feature_strs));
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_pfcp_ie_cp_function_features {
 | 
			
		||||
	uint8_t bits[3];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.37 */
 | 
			
		||||
struct osmo_pfcp_ie_f_seid {
 | 
			
		||||
	uint64_t seid;
 | 
			
		||||
	struct osmo_pfcp_ip_addrs ip_addr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_ie_f_seid_set(struct osmo_pfcp_ie_f_seid *f_seid, uint64_t seid,
 | 
			
		||||
			     const struct osmo_sockaddr *remote_addr);
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.3.2 */
 | 
			
		||||
struct osmo_pfcp_ie_f_teid {
 | 
			
		||||
	bool choose_flag;
 | 
			
		||||
	union {
 | 
			
		||||
		struct {
 | 
			
		||||
			uint32_t teid;
 | 
			
		||||
			struct osmo_pfcp_ip_addrs ip_addr;
 | 
			
		||||
		} fixed;
 | 
			
		||||
		struct {
 | 
			
		||||
			bool ipv4_addr;
 | 
			
		||||
			bool ipv6_addr;
 | 
			
		||||
			bool choose_id_present;
 | 
			
		||||
			uint8_t choose_id;
 | 
			
		||||
		} choose;
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.62 */
 | 
			
		||||
struct osmo_pfcp_ie_ue_ip_address {
 | 
			
		||||
	bool chv6;
 | 
			
		||||
	bool chv4;
 | 
			
		||||
	bool ip_is_destination;
 | 
			
		||||
	struct osmo_pfcp_ip_addrs ip_addr;
 | 
			
		||||
	bool ipv6_prefix_delegation_bits_present;
 | 
			
		||||
	uint8_t ipv6_prefix_delegation_bits;
 | 
			
		||||
	bool ipv6_prefix_length_present;
 | 
			
		||||
	uint8_t ipv6_prefix_length;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.26.
 | 
			
		||||
 * Usage:
 | 
			
		||||
 *     struct osmo_pfcp_ie_apply_action x;
 | 
			
		||||
 *     osmo_pfcp_bits_set(x.bits, OSMO_PFCP_APPLY_ACTION_FORW, true);
 | 
			
		||||
 *     if (osmo_pfcp_bits_get(x.bits, OSMO_PFCP_APPLY_ACTION_FORW))
 | 
			
		||||
 *             foo();
 | 
			
		||||
 *     printf("%s\n", osmo_pfcp_bits_to_str_c(x.bits, osmo_pfcp_apply_action_strs));
 | 
			
		||||
 */
 | 
			
		||||
struct osmo_pfcp_ie_apply_action {
 | 
			
		||||
	uint8_t bits[2];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_ie_network_inst {
 | 
			
		||||
	/* A domain name may have up to 253 characters; plus nul. */
 | 
			
		||||
	char str[253+1];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_ie_activate_predefined_rules {
 | 
			
		||||
	char str[256];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.56 */
 | 
			
		||||
struct osmo_pfcp_ie_outer_header_creation {
 | 
			
		||||
	/* desc_bits Usage:
 | 
			
		||||
	 *     osmo_pfcp_bits_set(x.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
 | 
			
		||||
	 *     if (osmo_pfcp_bits_get(x.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4))
 | 
			
		||||
	 *             foo();
 | 
			
		||||
	 *     printf("%s\n", osmo_pfcp_bits_to_str_c(x.desc_bits, osmo_pfcp_outer_header_creation_strs));
 | 
			
		||||
	 */
 | 
			
		||||
	uint8_t desc_bits[2];
 | 
			
		||||
	bool teid_present;
 | 
			
		||||
	uint32_t teid;
 | 
			
		||||
	struct osmo_pfcp_ip_addrs ip_addr;
 | 
			
		||||
	bool port_number_present;
 | 
			
		||||
	uint16_t port_number;
 | 
			
		||||
	bool c_tag_present;
 | 
			
		||||
	uint32_t c_tag;
 | 
			
		||||
	bool s_tag_present;
 | 
			
		||||
	uint32_t s_tag;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.64. */
 | 
			
		||||
struct osmo_pfcp_ie_outer_header_removal {
 | 
			
		||||
	enum osmo_pfcp_outer_header_removal_desc desc;
 | 
			
		||||
	bool gtp_u_extension_header_del_present;
 | 
			
		||||
	uint8_t gtp_u_extension_header_del_bits[1];
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										165
									
								
								include/osmocom/pfcp/pfcp_msg.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								include/osmocom/pfcp/pfcp_msg.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
			
		||||
/* PFCP message encoding and decoding */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <inttypes.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_proto.h>
 | 
			
		||||
#include <osmocom/pfcp/pfcp_ies_auto.h>
 | 
			
		||||
#include <osmocom/pfcp/pfcp_strs.h>
 | 
			
		||||
 | 
			
		||||
struct msgb;
 | 
			
		||||
struct osmo_t16l16v_ie;
 | 
			
		||||
 | 
			
		||||
#define OSMO_PFCP_MSGB_ALLOC_SIZE 2048
 | 
			
		||||
 | 
			
		||||
#define OSMO_LOG_PFCP_MSG_SRC(M, LEVEL, file, line, FMT, ARGS...) do { \
 | 
			
		||||
		struct osmo_fsm_inst *_fi = (M) ? ( (M)->ctx.session_fi ?: (M)->ctx.peer_fi ) : NULL; \
 | 
			
		||||
		enum osmo_pfcp_cause *cause = osmo_pfcp_msg_cause(M); \
 | 
			
		||||
		if ((M)->h.seid_present) { \
 | 
			
		||||
			LOGPFSMSLSRC(_fi, DLPFCP, LEVEL, file, line, \
 | 
			
		||||
				     "%s%s PFCP seq-%u SEID-0x%"PRIx64" %s%s%s: " FMT, \
 | 
			
		||||
				     _fi ? "" : osmo_sockaddr_to_str_c(OTC_SELECT, &(M)->remote_addr), \
 | 
			
		||||
				     (M)->rx ? "-rx->" : "<-tx-", (M)->h.sequence_nr, \
 | 
			
		||||
				     (M)->h.seid, \
 | 
			
		||||
				     osmo_pfcp_message_type_str((M)->h.message_type), cause ? ": " : "", \
 | 
			
		||||
				     cause ? osmo_pfcp_cause_str(*cause) : "", ##ARGS); \
 | 
			
		||||
		} else { \
 | 
			
		||||
			LOGPFSMSLSRC(_fi, DLPFCP, LEVEL, file, line, \
 | 
			
		||||
				     "%s%s PFCP seq-%u %s%s%s: " FMT, \
 | 
			
		||||
				     _fi ? "" : osmo_sockaddr_to_str_c(OTC_SELECT, &(M)->remote_addr), \
 | 
			
		||||
				     (M)->rx ? "-rx->" : "<-tx-", (M)->h.sequence_nr, \
 | 
			
		||||
				     osmo_pfcp_message_type_str((M)->h.message_type), cause ? ": " : "", \
 | 
			
		||||
				     cause ? osmo_pfcp_cause_str(*cause) : "", ##ARGS); \
 | 
			
		||||
		} \
 | 
			
		||||
	} while(0)
 | 
			
		||||
 | 
			
		||||
#define OSMO_LOG_PFCP_MSG(M, LEVEL, FMT, ARGS...) \
 | 
			
		||||
	OSMO_LOG_PFCP_MSG_SRC(M, LEVEL, __FILE__, __LINE__, FMT, ##ARGS)
 | 
			
		||||
 | 
			
		||||
/* Return the next PFCP transmit sequence number based on the given sequence state var. */
 | 
			
		||||
static inline uint32_t osmo_pfcp_next_seq(uint32_t *seq_state)
 | 
			
		||||
{
 | 
			
		||||
	(*seq_state)++;
 | 
			
		||||
	(*seq_state) &= 0xffffff;
 | 
			
		||||
	return *seq_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_header_parsed {
 | 
			
		||||
	uint8_t version;
 | 
			
		||||
	enum osmo_pfcp_message_type message_type;
 | 
			
		||||
	uint32_t sequence_nr;
 | 
			
		||||
	bool priority_present;
 | 
			
		||||
	uint8_t priority;
 | 
			
		||||
	bool seid_present;
 | 
			
		||||
	uint64_t seid;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_msg {
 | 
			
		||||
	/* Peer's remote address. Received from this peer, or should be sent to this peer. */
 | 
			
		||||
	struct osmo_sockaddr remote_addr;
 | 
			
		||||
	/* True when this message was received from a remote; false when this message is going to be sent. */
 | 
			
		||||
	bool rx;
 | 
			
		||||
	/* True when this message is a Response message type; false if Request. This is set by
 | 
			
		||||
	 * osmo_pfcp_msg_decode() for received messages, and by osmo_pfcp_msg_alloc_tx */
 | 
			
		||||
	bool is_response;
 | 
			
		||||
 | 
			
		||||
	struct osmo_pfcp_header_parsed h;
 | 
			
		||||
 | 
			
		||||
	int ofs_cause;
 | 
			
		||||
	int ofs_node_id;
 | 
			
		||||
 | 
			
		||||
	/* The union of decoded IEs from all supported PFCP message types.  The union and its structure is defined in
 | 
			
		||||
	 * pfcp_ies_auto.h, which is generated by gen__pfcp_ies_auto.c.
 | 
			
		||||
	 */
 | 
			
		||||
	union osmo_pfcp_ies ies;
 | 
			
		||||
 | 
			
		||||
	/* Context information about this message, used for logging */
 | 
			
		||||
	struct {
 | 
			
		||||
		/* Peer FSM instance that this message is received from / sent to. This can be set in the
 | 
			
		||||
		 * osmo_pfcp_endpoint->set_msg_ctx() implementation, up to the caller. If present, this is used for
 | 
			
		||||
		 * logging context, and can also be used by the caller to reduce lookup iterations. */
 | 
			
		||||
		struct osmo_fsm_inst *peer_fi;
 | 
			
		||||
		struct osmo_use_count *peer_use_count;
 | 
			
		||||
		const char *peer_use_token;
 | 
			
		||||
 | 
			
		||||
		/* Session FSM instance that this message is received from / sent to. This can be set in the
 | 
			
		||||
		 * osmo_pfcp_endpoint->set_msg_ctx() implementation, up to the caller. If present, this is used for
 | 
			
		||||
		 * logging context, and can also be used by the caller to reduce lookup iterations. */
 | 
			
		||||
		struct osmo_fsm_inst *session_fi;
 | 
			
		||||
		struct osmo_use_count *session_use_count;
 | 
			
		||||
		const char *session_use_token;
 | 
			
		||||
	} ctx;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define OSMO_PFCP_MSG_FOR_IES(IES_P) ((struct osmo_pfcp_msg*)((char*)IES_P - offsetof(struct osmo_pfcp_msg, ies)))
 | 
			
		||||
 | 
			
		||||
bool osmo_pfcp_msgtype_is_response(enum osmo_pfcp_message_type message_type);
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ie_f_teid_to_str_buf(char *buf, size_t len, const struct osmo_pfcp_ie_f_teid *ft);
 | 
			
		||||
char *osmo_pfcp_ie_f_teid_to_str_c(void *ctx, const struct osmo_pfcp_ie_f_teid *ft);
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_msg_encode(struct msgb *msg, const struct osmo_pfcp_msg *pfcp_msg);
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_msg_decode_header(struct osmo_gtlv_load *tlv, struct osmo_pfcp_msg *m,
 | 
			
		||||
				const struct msgb *msg);
 | 
			
		||||
int osmo_pfcp_msg_decode_tlv(struct osmo_pfcp_msg *m, struct osmo_gtlv_load *tlv);
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_rx(void *ctx, const struct osmo_sockaddr *remote_addr);
 | 
			
		||||
struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_tx(void *ctx, const struct osmo_sockaddr *remote_addr,
 | 
			
		||||
					     const struct osmo_pfcp_ie_node_id *local_node_id,
 | 
			
		||||
					     const struct osmo_pfcp_msg *in_reply_to,
 | 
			
		||||
					     enum osmo_pfcp_message_type msg_type);
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_msg_invalidate_ctx(struct osmo_pfcp_msg *m, struct osmo_fsm_inst *deleted_fi);
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_msg_free(struct osmo_pfcp_msg *m);
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ie_node_id_from_osmo_sockaddr(struct osmo_pfcp_ie_node_id *node_id, const struct osmo_sockaddr *os);
 | 
			
		||||
int osmo_pfcp_ie_node_id_to_osmo_sockaddr(const struct osmo_pfcp_ie_node_id *node_id, struct osmo_sockaddr *os);
 | 
			
		||||
 | 
			
		||||
#define OSMO_PFCP_MSG_MEMB(M, OFS) ((OFS) <= 0 ? NULL : (void*)((uint8_t*)(M) + OFS))
 | 
			
		||||
 | 
			
		||||
static inline enum osmo_pfcp_cause *osmo_pfcp_msg_cause(const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	return OSMO_PFCP_MSG_MEMB(m, m->ofs_cause);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline struct osmo_pfcp_ie_node_id *osmo_pfcp_msg_node_id(const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	return OSMO_PFCP_MSG_MEMB(m, m->ofs_node_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ie_f_seid_cmp(const struct osmo_pfcp_ie_f_seid *a, const struct osmo_pfcp_ie_f_seid *b);
 | 
			
		||||
void osmo_pfcp_ie_f_seid_set_addr(struct osmo_pfcp_ie_f_seid *f_seid, const struct osmo_sockaddr *addr);
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_msg_to_str_buf(char *buf, size_t buflen, const struct osmo_pfcp_msg *m);
 | 
			
		||||
char *osmo_pfcp_msg_to_str_c(void *ctx, const struct osmo_pfcp_msg *m);
 | 
			
		||||
							
								
								
									
										528
									
								
								include/osmocom/pfcp/pfcp_proto.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										528
									
								
								include/osmocom/pfcp/pfcp_proto.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,528 @@
 | 
			
		||||
/* 3GPP TS 29.244: Packet Forwarding Control Protocol */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#define OSMO_PFCP_PORT 8805
 | 
			
		||||
 | 
			
		||||
/* Section 7.3 / Table 7.3-1 */
 | 
			
		||||
enum osmo_pfcp_message_type {
 | 
			
		||||
	OSMO_PFCP_MSGT_NONE = 0,
 | 
			
		||||
 | 
			
		||||
	/* Node related messages */
 | 
			
		||||
	OSMO_PFCP_MSGT_HEARTBEAT_REQ = 1,
 | 
			
		||||
	OSMO_PFCP_MSGT_HEARTBEAT_RESP = 2,
 | 
			
		||||
	OSMO_PFCP_MSGT_PFD_MGMT_REQ = 3,
 | 
			
		||||
	OSMO_PFCP_MSGT_PFD_MGMT_RESP = 4,
 | 
			
		||||
	OSMO_PFCP_MSGT_ASSOC_SETUP_REQ = 5,
 | 
			
		||||
	OSMO_PFCP_MSGT_ASSOC_SETUP_RESP = 6,
 | 
			
		||||
	OSMO_PFCP_MSGT_ASSOC_UPDATE_REQ = 7,
 | 
			
		||||
	OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP = 8,
 | 
			
		||||
	OSMO_PFCP_MSGT_ASSOC_RELEASE_REQ = 9,
 | 
			
		||||
	OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP = 10,
 | 
			
		||||
	OSMO_PFCP_MSGT_VERSION_NOT_SUPP_RESP = 11,
 | 
			
		||||
	OSMO_PFCP_MSGT_NODE_REPORT_REQ = 12,
 | 
			
		||||
	OSMO_PFCP_MSGT_NODE_REPORT_RESP = 13,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_SET_DEL_REQ = 14,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_SET_DEL_RESP = 15,
 | 
			
		||||
 | 
			
		||||
	/* Session related messages */
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_EST_REQ = 50,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_EST_RESP = 51,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_MOD_REQ = 52,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_MOD_RESP = 53,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_DEL_REQ = 54,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_DEL_RESP = 55,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_REP_REQ = 56,
 | 
			
		||||
	OSMO_PFCP_MSGT_SESSION_REP_RESP = 57,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Section 8.1.2 / Table 8.1.2-1 */
 | 
			
		||||
enum osmo_pfcp_iei {
 | 
			
		||||
	OSMO_PFCP_INVALID_IEI = 0,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_PDR = 1,
 | 
			
		||||
	OSMO_PFCP_IEI_PDI = 2,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_FAR = 3,
 | 
			
		||||
	OSMO_PFCP_IEI_FORW_PARAMS = 4,
 | 
			
		||||
	OSMO_PFCP_IEI_DUPL_PARAMS = 5,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_URR = 6,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_QER = 7,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATED_PDR = 8,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_PDR = 9,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_FAR = 10,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_FORW_PARAMS = 11,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_BAR_SESS_REP_RESP = 12,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_URR = 13,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_QER = 14,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_PDR = 15,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_FAR = 16,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_URR = 17,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_QER = 18,
 | 
			
		||||
	OSMO_PFCP_IEI_CAUSE = 19,
 | 
			
		||||
	OSMO_PFCP_IEI_SOURCE_IFACE = 20,
 | 
			
		||||
	OSMO_PFCP_IEI_F_TEID = 21,
 | 
			
		||||
	OSMO_PFCP_IEI_NETWORK_INST = 22,
 | 
			
		||||
	OSMO_PFCP_IEI_SDF_FILTER = 23,
 | 
			
		||||
	OSMO_PFCP_IEI_APPLICATION_ID = 24,
 | 
			
		||||
	OSMO_PFCP_IEI_GATE_STATUS = 25,
 | 
			
		||||
	OSMO_PFCP_IEI_MBR = 26,
 | 
			
		||||
	OSMO_PFCP_IEI_GBR = 27,
 | 
			
		||||
	OSMO_PFCP_IEI_QER_CORRELATION_ID = 28,
 | 
			
		||||
	OSMO_PFCP_IEI_PRECEDENCE = 29,
 | 
			
		||||
	OSMO_PFCP_IEI_TRANSPORT_LEVEL_MARKING = 30,
 | 
			
		||||
	OSMO_PFCP_IEI_VOLUME_THRESH = 31,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_THRESH = 32,
 | 
			
		||||
	OSMO_PFCP_IEI_MONITORING_TIME = 33,
 | 
			
		||||
	OSMO_PFCP_IEI_SUBSEQUENT_VOLUME_THRESH = 34,
 | 
			
		||||
	OSMO_PFCP_IEI_SUBSEQUENT_TIME_THRESH = 35,
 | 
			
		||||
	OSMO_PFCP_IEI_INACT_DETECTION_TIME = 36,
 | 
			
		||||
	OSMO_PFCP_IEI_REPORTING_TRIGGERS = 37,
 | 
			
		||||
	OSMO_PFCP_IEI_REDIRECT_INFO = 38,
 | 
			
		||||
	OSMO_PFCP_IEI_REP_TYPE = 39,
 | 
			
		||||
	OSMO_PFCP_IEI_OFFENDING_IE = 40,
 | 
			
		||||
	OSMO_PFCP_IEI_FORW_POLICY = 41,
 | 
			
		||||
	OSMO_PFCP_IEI_DESTINATION_IFACE = 42,
 | 
			
		||||
	OSMO_PFCP_IEI_UP_FUNCTION_FEATURES = 43,
 | 
			
		||||
	OSMO_PFCP_IEI_APPLY_ACTION = 44,
 | 
			
		||||
	OSMO_PFCP_IEI_DL_DATA_SERVICE_INFO = 45,
 | 
			
		||||
	OSMO_PFCP_IEI_DL_DATA_NOTIFICATION_DELAY = 46,
 | 
			
		||||
	OSMO_PFCP_IEI_DL_BUFF_DURATION = 47,
 | 
			
		||||
	OSMO_PFCP_IEI_DL_BUFF_SUGGESTED_PACKET_COUNT = 48,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCPSMREQ_FLAGS = 49,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCPSRRSP_FLAGS = 50,
 | 
			
		||||
	OSMO_PFCP_IEI_LOAD_CTRL_INFO = 51,
 | 
			
		||||
	OSMO_PFCP_IEI_SEQUENCE_NUMBER = 52,
 | 
			
		||||
	OSMO_PFCP_IEI_METRIC = 53,
 | 
			
		||||
	OSMO_PFCP_IEI_OVERLOAD_CTRL_INFO = 54,
 | 
			
		||||
	OSMO_PFCP_IEI_TIMER = 55,
 | 
			
		||||
	OSMO_PFCP_IEI_PDR_ID = 56,
 | 
			
		||||
	OSMO_PFCP_IEI_F_SEID = 57,
 | 
			
		||||
	OSMO_PFCP_IEI_APPLICATION_IDS_PFDS = 58,
 | 
			
		||||
	OSMO_PFCP_IEI_PFD_CONTEXT = 59,
 | 
			
		||||
	OSMO_PFCP_IEI_NODE_ID = 60,
 | 
			
		||||
	OSMO_PFCP_IEI_PFD_CONTENTS = 61,
 | 
			
		||||
	OSMO_PFCP_IEI_MEAS_METHOD = 62,
 | 
			
		||||
	OSMO_PFCP_IEI_USAGE_REP_TRIGGER = 63,
 | 
			
		||||
	OSMO_PFCP_IEI_MEAS_PERIOD = 64,
 | 
			
		||||
	OSMO_PFCP_IEI_FQ_CSID = 65,
 | 
			
		||||
	OSMO_PFCP_IEI_VOLUME_MEAS = 66,
 | 
			
		||||
	OSMO_PFCP_IEI_DURATION_MEAS = 67,
 | 
			
		||||
	OSMO_PFCP_IEI_APPLICATION_DETECTION_INFO = 68,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_OF_FIRST_PACKET = 69,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_OF_LAST_PACKET = 70,
 | 
			
		||||
	OSMO_PFCP_IEI_QUOTA_HOLDING_TIME = 71,
 | 
			
		||||
	OSMO_PFCP_IEI_DROPPED_DL_TRAFFIC_THRESH = 72,
 | 
			
		||||
	OSMO_PFCP_IEI_VOLUME_QUOTA = 73,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_QUOTA = 74,
 | 
			
		||||
	OSMO_PFCP_IEI_START_TIME = 75,
 | 
			
		||||
	OSMO_PFCP_IEI_END_TIME = 76,
 | 
			
		||||
	OSMO_PFCP_IEI_QUERY_URR = 77,
 | 
			
		||||
	OSMO_PFCP_IEI_USAGE_REP_SESS_MOD_RESP = 78,
 | 
			
		||||
	OSMO_PFCP_IEI_USAGE_REP_SESS_DEL_RESP = 79,
 | 
			
		||||
	OSMO_PFCP_IEI_USAGE_REP_SESS_REP_REQ = 80,
 | 
			
		||||
	OSMO_PFCP_IEI_URR_ID = 81,
 | 
			
		||||
	OSMO_PFCP_IEI_LINKED_URR_ID = 82,
 | 
			
		||||
	OSMO_PFCP_IEI_DL_DATA_REP = 83,
 | 
			
		||||
	OSMO_PFCP_IEI_OUTER_HEADER_CREATION = 84,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_BAR = 85,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_BAR_SESS_MOD_REQ = 86,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_BAR = 87,
 | 
			
		||||
	OSMO_PFCP_IEI_BAR_ID = 88,
 | 
			
		||||
	OSMO_PFCP_IEI_CP_FUNCTION_FEATURES = 89,
 | 
			
		||||
	OSMO_PFCP_IEI_USAGE_INFO = 90,
 | 
			
		||||
	OSMO_PFCP_IEI_APPLICATION_INST_ID = 91,
 | 
			
		||||
	OSMO_PFCP_IEI_FLOW_INFO = 92,
 | 
			
		||||
	OSMO_PFCP_IEI_UE_IP_ADDRESS = 93,
 | 
			
		||||
	OSMO_PFCP_IEI_PACKET_RATE = 94,
 | 
			
		||||
	OSMO_PFCP_IEI_OUTER_HEADER_REMOVAL = 95,
 | 
			
		||||
	OSMO_PFCP_IEI_RECOVERY_TIME_STAMP = 96,
 | 
			
		||||
	OSMO_PFCP_IEI_DL_FLOW_LEVEL_MARKING = 97,
 | 
			
		||||
	OSMO_PFCP_IEI_HEADER_ENRICHMENT = 98,
 | 
			
		||||
	OSMO_PFCP_IEI_ERROR_IND_REP = 99,
 | 
			
		||||
	OSMO_PFCP_IEI_MEAS_INFO = 100,
 | 
			
		||||
	OSMO_PFCP_IEI_NODE_REP_TYPE = 101,
 | 
			
		||||
	OSMO_PFCP_IEI_USER_PLANE_PATH_FAILURE_REP = 102,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOTE_GTP_U_PEER = 103,
 | 
			
		||||
	OSMO_PFCP_IEI_UR_SEQN = 104,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_DUPL_PARAMS = 105,
 | 
			
		||||
	OSMO_PFCP_IEI_ACTIVATE_PREDEFINED_RULES = 106,
 | 
			
		||||
	OSMO_PFCP_IEI_DEACTIVATE_PREDEFINED_RULES = 107,
 | 
			
		||||
	OSMO_PFCP_IEI_FAR_ID = 108,
 | 
			
		||||
	OSMO_PFCP_IEI_QER_ID = 109,
 | 
			
		||||
	OSMO_PFCP_IEI_OCI_FLAGS = 110,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCP_ASSOC_RELEASE_REQ = 111,
 | 
			
		||||
	OSMO_PFCP_IEI_GRACEFUL_RELEASE_PERIOD = 112,
 | 
			
		||||
	OSMO_PFCP_IEI_PDN_TYPE = 113,
 | 
			
		||||
	OSMO_PFCP_IEI_FAILED_RULE_ID = 114,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_QUOTA_MECHANISM = 115,
 | 
			
		||||
	OSMO_PFCP_IEI_RESERVED = 116,
 | 
			
		||||
	OSMO_PFCP_IEI_USER_PLANE_INACT_TIMER = 117,
 | 
			
		||||
	OSMO_PFCP_IEI_AGGREGATED_URRS = 118,
 | 
			
		||||
	OSMO_PFCP_IEI_MULTIPLIER = 119,
 | 
			
		||||
	OSMO_PFCP_IEI_AGGREGATED_URR_ID = 120,
 | 
			
		||||
	OSMO_PFCP_IEI_SUBSEQUENT_VOLUME_QUOTA = 121,
 | 
			
		||||
	OSMO_PFCP_IEI_SUBSEQUENT_TIME_QUOTA = 122,
 | 
			
		||||
	OSMO_PFCP_IEI_RQI = 123,
 | 
			
		||||
	OSMO_PFCP_IEI_QFI = 124,
 | 
			
		||||
	OSMO_PFCP_IEI_QUERY_URR_REFERENCE = 125,
 | 
			
		||||
	OSMO_PFCP_IEI_ADDITIONAL_USAGE_REPS_INFO = 126,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_TRAFFIC_ENDPOINT = 127,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATED_TRAFFIC_ENDPOINT = 128,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_TRAFFIC_ENDPOINT = 129,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_TRAFFIC_ENDPOINT = 130,
 | 
			
		||||
	OSMO_PFCP_IEI_TRAFFIC_ENDPOINT_ID = 131,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERNET_PACKET_FILTER = 132,
 | 
			
		||||
	OSMO_PFCP_IEI_MAC_ADDRESS = 133,
 | 
			
		||||
	OSMO_PFCP_IEI_C_TAG = 134,
 | 
			
		||||
	OSMO_PFCP_IEI_S_TAG = 135,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERTYPE = 136,
 | 
			
		||||
	OSMO_PFCP_IEI_PROXYING = 137,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERNET_FILTER_ID = 138,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERNET_FILTER_PROPERTIES = 139,
 | 
			
		||||
	OSMO_PFCP_IEI_SUGGESTED_BUFF_PACKETS_COUNT = 140,
 | 
			
		||||
	OSMO_PFCP_IEI_USER_ID = 141,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERNET_PDU_SESS_INFO = 142,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERNET_TRAFFIC_INFO = 143,
 | 
			
		||||
	OSMO_PFCP_IEI_MAC_ADDRS_DETECTED = 144,
 | 
			
		||||
	OSMO_PFCP_IEI_MAC_ADDRS_REMOVED = 145,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERNET_INACT_TIMER = 146,
 | 
			
		||||
	OSMO_PFCP_IEI_ADDITIONAL_MONITORING_TIME = 147,
 | 
			
		||||
	OSMO_PFCP_IEI_EVENT_QUOTA = 148,
 | 
			
		||||
	OSMO_PFCP_IEI_EVENT_THRESH = 149,
 | 
			
		||||
	OSMO_PFCP_IEI_SUBSEQUENT_EVENT_QUOTA = 150,
 | 
			
		||||
	OSMO_PFCP_IEI_SUBSEQUENT_EVENT_THRESH = 151,
 | 
			
		||||
	OSMO_PFCP_IEI_TRACE_INFO = 152,
 | 
			
		||||
	OSMO_PFCP_IEI_FRAMED_ROUTE = 153,
 | 
			
		||||
	OSMO_PFCP_IEI_FRAMED_ROUTING = 154,
 | 
			
		||||
	OSMO_PFCP_IEI_FRAMED_IPV6_ROUTE = 155,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_STAMP = 156,
 | 
			
		||||
	OSMO_PFCP_IEI_AVERAGING_WINDOW = 157,
 | 
			
		||||
	OSMO_PFCP_IEI_PAGING_POLICY_INDICATOR = 158,
 | 
			
		||||
	OSMO_PFCP_IEI_APN_DNN = 159,
 | 
			
		||||
	OSMO_PFCP_IEI_3GPP_IFACE_TYPE = 160,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCPSRREQ_FLAGS = 161,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCPAUREQ_FLAGS = 162,
 | 
			
		||||
	OSMO_PFCP_IEI_ACTIVATION_TIME = 163,
 | 
			
		||||
	OSMO_PFCP_IEI_DEACTIVATION_TIME = 164,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_MAR = 165,
 | 
			
		||||
	OSMO_PFCP_IEI_3GPP_ACCESS_FORW_ACTION_INFO = 166,
 | 
			
		||||
	OSMO_PFCP_IEI_NON_3GPP_ACCESS_FORW_ACTION_INFO = 167,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_MAR = 168,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_MAR = 169,
 | 
			
		||||
	OSMO_PFCP_IEI_MAR_ID = 170,
 | 
			
		||||
	OSMO_PFCP_IEI_STEERING_FUNCTIONALITY = 171,
 | 
			
		||||
	OSMO_PFCP_IEI_STEERING_MODE = 172,
 | 
			
		||||
	OSMO_PFCP_IEI_WEIGHT = 173,
 | 
			
		||||
	OSMO_PFCP_IEI_PRIORITY = 174,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_3GPP_ACCESS_FORW_ACTION_INFO = 175,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_NON_3GPP_ACCESS_FORW_ACTION_INFO = 176,
 | 
			
		||||
	OSMO_PFCP_IEI_UE_IP_ADDRESS_POOL_IDENTITY = 177,
 | 
			
		||||
	OSMO_PFCP_IEI_ALTERNATIVE_SMF_IP_ADDRESS = 178,
 | 
			
		||||
	OSMO_PFCP_IEI_PACKET_REPLICATION_AND_DETECTION_CARRY_ON_INFO = 179,
 | 
			
		||||
	OSMO_PFCP_IEI_SMF_SET_ID = 180,
 | 
			
		||||
	OSMO_PFCP_IEI_QUOTA_VALIDITY_TIME = 181,
 | 
			
		||||
	OSMO_PFCP_IEI_NUMBER_OF_REPS = 182,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCP_SESS_RETENTION_INFO_IN_ASSOC_SETUP_REQ = 183,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCPASRSP_FLAGS = 184,
 | 
			
		||||
	OSMO_PFCP_IEI_CP_ENTITY_IP_ADDRESS = 185,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCPSEREQ_FLAGS = 186,
 | 
			
		||||
	OSMO_PFCP_IEI_USER_PLANE_PATH_RECOVERY_REP = 187,
 | 
			
		||||
	OSMO_PFCP_IEI_IP_MULTICAST_ADDR_INFO_IN_SESS_EST_REQ = 188,
 | 
			
		||||
	OSMO_PFCP_IEI_JOIN_IP_MULTICAST_INFO_IE_IN_USAGE_REP = 189,
 | 
			
		||||
	OSMO_PFCP_IEI_LEAVE_IP_MULTICAST_INFO_IE_IN_USAGE_REP = 190,
 | 
			
		||||
	OSMO_PFCP_IEI_IP_MULTICAST_ADDRESS = 191,
 | 
			
		||||
	OSMO_PFCP_IEI_SOURCE_IP_ADDRESS = 192,
 | 
			
		||||
	OSMO_PFCP_IEI_PACKET_RATE_STATUS = 193,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_BRIDGE_INFO_FOR_TSC = 194,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATED_BRIDGE_INFO_FOR_TSC = 195,
 | 
			
		||||
	OSMO_PFCP_IEI_DS_TT_PORT_NUMBER = 196,
 | 
			
		||||
	OSMO_PFCP_IEI_NW_TT_PORT_NUMBER = 197,
 | 
			
		||||
	OSMO_PFCP_IEI_TSN_BRIDGE_ID = 198,
 | 
			
		||||
	OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_MOD_REQ = 199,
 | 
			
		||||
	OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_MOD_RESP = 200,
 | 
			
		||||
	OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_REP_REQ = 201,
 | 
			
		||||
	OSMO_PFCP_IEI_PORT_MGMT_INFO_CONTAINER = 202,
 | 
			
		||||
	OSMO_PFCP_IEI_CLOCK_DRIFT_CTRL_INFO = 203,
 | 
			
		||||
	OSMO_PFCP_IEI_REQUESTED_CLOCK_DRIFT_INFO = 204,
 | 
			
		||||
	OSMO_PFCP_IEI_CLOCK_DRIFT_REP = 205,
 | 
			
		||||
	OSMO_PFCP_IEI_TSN_TIME_DOMAIN_NUMBER = 206,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_OFFSET_THRESH = 207,
 | 
			
		||||
	OSMO_PFCP_IEI_CUMULATIVE_RATERATIO_THRESH = 208,
 | 
			
		||||
	OSMO_PFCP_IEI_TIME_OFFSET_MEAS = 209,
 | 
			
		||||
	OSMO_PFCP_IEI_CUMULATIVE_RATERATIO_MEAS = 210,
 | 
			
		||||
	OSMO_PFCP_IEI_REMOVE_SRR = 211,
 | 
			
		||||
	OSMO_PFCP_IEI_CREATE_SRR = 212,
 | 
			
		||||
	OSMO_PFCP_IEI_UPD_SRR = 213,
 | 
			
		||||
	OSMO_PFCP_IEI_SESS_REP = 214,
 | 
			
		||||
	OSMO_PFCP_IEI_SRR_ID = 215,
 | 
			
		||||
	OSMO_PFCP_IEI_ACCESS_AVAIL_CTRL_INFO = 216,
 | 
			
		||||
	OSMO_PFCP_IEI_REQUESTED_ACCESS_AVAIL_INFO = 217,
 | 
			
		||||
	OSMO_PFCP_IEI_ACCESS_AVAIL_REP = 218,
 | 
			
		||||
	OSMO_PFCP_IEI_ACCESS_AVAIL_INFO = 219,
 | 
			
		||||
	OSMO_PFCP_IEI_PROVIDE_ATSSS_CTRL_INFO = 220,
 | 
			
		||||
	OSMO_PFCP_IEI_ATSSS_CTRL_PARAMS = 221,
 | 
			
		||||
	OSMO_PFCP_IEI_MPTCP_CTRL_INFO = 222,
 | 
			
		||||
	OSMO_PFCP_IEI_ATSSS_LL_CTRL_INFO = 223,
 | 
			
		||||
	OSMO_PFCP_IEI_PMF_CTRL_INFO = 224,
 | 
			
		||||
	OSMO_PFCP_IEI_MPTCP_PARAMS = 225,
 | 
			
		||||
	OSMO_PFCP_IEI_ATSSS_LL_PARAMS = 226,
 | 
			
		||||
	OSMO_PFCP_IEI_PMF_PARAMS = 227,
 | 
			
		||||
	OSMO_PFCP_IEI_MPTCP_ADDRESS_INFO = 228,
 | 
			
		||||
	OSMO_PFCP_IEI_UE_LINK_SPECIFIC_IP_ADDRESS = 229,
 | 
			
		||||
	OSMO_PFCP_IEI_PMF_ADDRESS_INFO = 230,
 | 
			
		||||
	OSMO_PFCP_IEI_ATSSS_LL_INFO = 231,
 | 
			
		||||
	OSMO_PFCP_IEI_DATA_NETWORK_ACCESS_IDENTIFIER = 232,
 | 
			
		||||
	OSMO_PFCP_IEI_UE_IP_ADDRESS_POOL_INFO = 233,
 | 
			
		||||
	OSMO_PFCP_IEI_AVERAGE_PACKET_DELAY = 234,
 | 
			
		||||
	OSMO_PFCP_IEI_MIN_PACKET_DELAY = 235,
 | 
			
		||||
	OSMO_PFCP_IEI_MAX_PACKET_DELAY = 236,
 | 
			
		||||
	OSMO_PFCP_IEI_QOS_REP_TRIGGER = 237,
 | 
			
		||||
	OSMO_PFCP_IEI_GTP_U_PATH_QOS_CTRL_INFO = 238,
 | 
			
		||||
	OSMO_PFCP_IEI_GTP_U_PATH_QOS_REP_NODE_REP_REQ = 239,
 | 
			
		||||
	OSMO_PFCP_IEI_QOS_INFO_IN_GTP_U_PATH_QOS_REP = 240,
 | 
			
		||||
	OSMO_PFCP_IEI_GTP_U_PATH_IFACE_TYPE = 241,
 | 
			
		||||
	OSMO_PFCP_IEI_QOS_MONITORING_PER_QOS_FLOW_CTRL_INFO = 242,
 | 
			
		||||
	OSMO_PFCP_IEI_REQUESTED_QOS_MONITORING = 243,
 | 
			
		||||
	OSMO_PFCP_IEI_REPORTING_FREQUENCY = 244,
 | 
			
		||||
	OSMO_PFCP_IEI_PACKET_DELAY_THRESHOLDS = 245,
 | 
			
		||||
	OSMO_PFCP_IEI_MIN_WAIT_TIME = 246,
 | 
			
		||||
	OSMO_PFCP_IEI_QOS_MONITORING_REP = 247,
 | 
			
		||||
	OSMO_PFCP_IEI_QOS_MONITORING_MEAS = 248,
 | 
			
		||||
	OSMO_PFCP_IEI_MT_EDT_CTRL_INFO = 249,
 | 
			
		||||
	OSMO_PFCP_IEI_DL_DATA_PACKETS_SIZE = 250,
 | 
			
		||||
	OSMO_PFCP_IEI_QER_CTRL_INDICATIONS = 251,
 | 
			
		||||
	OSMO_PFCP_IEI_PACKET_RATE_STATUS_REP = 252,
 | 
			
		||||
	OSMO_PFCP_IEI_NF_INST_ID = 253,
 | 
			
		||||
	OSMO_PFCP_IEI_ETHERNET_CONTEXT_INFO = 254,
 | 
			
		||||
	OSMO_PFCP_IEI_REDUNDANT_TRANSMISSION_PARAMS = 255,
 | 
			
		||||
	OSMO_PFCP_IEI_UPDATED_PDR = 256,
 | 
			
		||||
	OSMO_PFCP_IEI_S_NSSAI = 257,
 | 
			
		||||
	OSMO_PFCP_IEI_IP_VERSION = 258,
 | 
			
		||||
	OSMO_PFCP_IEI_PFCPASREQ_FLAGS = 259,
 | 
			
		||||
	OSMO_PFCP_IEI_DATA_STATUS = 260,
 | 
			
		||||
	OSMO_PFCP_IEI_PROVIDE_RDS_CONF_INFO = 261,
 | 
			
		||||
	OSMO_PFCP_IEI_RDS_CONF_INFO = 262,
 | 
			
		||||
	OSMO_PFCP_IEI_QUERY_PACKET_RATE_STATUS_IE_IN_SESS_MOD_REQ = 263,
 | 
			
		||||
	OSMO_PFCP_IEI_PACKET_RATE_STATUS_REP_IE_IN_SESS_MOD_RESP = 264,
 | 
			
		||||
	OSMO_PFCP_IEI_MPTCP_APPLICABLE_IND = 265,
 | 
			
		||||
	OSMO_PFCP_IEI_BRIDGE_MGMT_INFO_CONTAINER = 266,
 | 
			
		||||
	OSMO_PFCP_IEI_UE_IP_ADDRESS_USAGE_INFO = 267,
 | 
			
		||||
	OSMO_PFCP_IEI_NUMBER_OF_UE_IP_ADDRS = 268,
 | 
			
		||||
	OSMO_PFCP_IEI_VALIDITY_TIMER = 269,
 | 
			
		||||
	OSMO_PFCP_IEI_REDUNDANT_TRANSMISSION_FORW_PARAMS = 270,
 | 
			
		||||
	OSMO_PFCP_IEI_TRANSPORT_DELAY_REPORTING = 271,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Section 8.2.1 / Table 8.2.1-1 */
 | 
			
		||||
enum osmo_pfcp_cause {
 | 
			
		||||
	OSMO_PFCP_CAUSE_RESERVED = 0,
 | 
			
		||||
	OSMO_PFCP_CAUSE_REQUEST_ACCEPTED = 1,
 | 
			
		||||
	OSMO_PFCP_CAUSE_MORE_USAGE_REPORT_TO_SEND = 2,
 | 
			
		||||
	OSMO_PFCP_CAUSE_REQUEST_REJECTED = 64,
 | 
			
		||||
	OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND = 65,
 | 
			
		||||
	OSMO_PFCP_CAUSE_MANDATORY_IE_MISSING = 66,
 | 
			
		||||
	OSMO_PFCP_CAUSE_CONDITIONAL_IE_MISSING = 67,
 | 
			
		||||
	OSMO_PFCP_CAUSE_INVALID_LENGTH = 68,
 | 
			
		||||
	OSMO_PFCP_CAUSE_MANDATORY_IE_INCORRECT = 69,
 | 
			
		||||
	OSMO_PFCP_CAUSE_INVALID_FORW_POLICY = 70,
 | 
			
		||||
	OSMO_PFCP_CAUSE_INVALID_F_TEID_ALLOC_OPTION = 71,
 | 
			
		||||
	OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC = 72,
 | 
			
		||||
	OSMO_PFCP_CAUSE_RULE_CREATION_MOD_FAILURE = 73,
 | 
			
		||||
	OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION = 74,
 | 
			
		||||
	OSMO_PFCP_CAUSE_NO_RESOURCES_AVAILABLE = 75,
 | 
			
		||||
	OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED = 76,
 | 
			
		||||
	OSMO_PFCP_CAUSE_SYSTEM_FAILURE = 77,
 | 
			
		||||
	OSMO_PFCP_CAUSE_REDIRECTION_REQUESTED = 78,
 | 
			
		||||
	OSMO_PFCP_CAUSE_ALL_DYNAMIC_ADDRESSES_ARE_OCCUPIED = 79,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Section 8.2.38 */
 | 
			
		||||
enum osmo_pfcp_node_id_type {
 | 
			
		||||
	OSMO_PFCP_NODE_ID_T_IPV4 = 0,
 | 
			
		||||
	OSMO_PFCP_NODE_ID_T_IPV6 = 1,
 | 
			
		||||
	OSMO_PFCP_NODE_ID_T_FQDN = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum osmo_pfcp_3gpp_iface_type {
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S1_U = 0,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S5_S8_U = 1,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S4_U = 2,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S11_U = 3,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S12_U = 4,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_GN_GP_U = 5,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S2A_U = 6,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S2B_U = 7,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_ENODEB_GTP_U_INTERFACE_FOR_DL_DATA_FORWARDING = 8,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_ENODEB_GTP_U_INTERFACE_FOR_UL_DATA_FORWARDING = 9,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_SGW_UPF_GTP_U_INTERFACE_FOR_DL_DATA_FORWARDING = 10,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_N3_3GPP_ACCESS = 11,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_N3_TRUSTED_NON_3GPP_ACCESS = 12,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_N3_UNTRUSTED_NON_3GPP_ACCESS = 13,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_N3_FOR_DATA_FORWARDING = 14,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_N9 = 15,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_SGI = 16,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_N6 = 17,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_N19 = 18,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_S8_U = 19,
 | 
			
		||||
	OSMO_PFCP_3GPP_IFACE_TYPE_GP_U = 20,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum osmo_pfcp_source_iface {
 | 
			
		||||
	OSMO_PFCP_SOURCE_IFACE_ACCESS = 0,
 | 
			
		||||
	OSMO_PFCP_SOURCE_IFACE_CORE = 1,
 | 
			
		||||
	OSMO_PFCP_SOURCE_IFACE_SGI_LAN_N6_LAN = 2,
 | 
			
		||||
	OSMO_PFCP_SOURCE_IFACE_CP_FUNCTION = 3,
 | 
			
		||||
	OSMO_PFCP_SOURCE_IFACE_5G_VN_INTERNAL = 4,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum osmo_pfcp_dest_iface {
 | 
			
		||||
	OSMO_PFCP_DEST_IFACE_ACCESS = 0,
 | 
			
		||||
	OSMO_PFCP_DEST_IFACE_CORE = 1,
 | 
			
		||||
	OSMO_PFCP_DEST_IFACE_SGI_LAN_N6_LAN = 2,
 | 
			
		||||
	OSMO_PFCP_DEST_IFACE_CP_FUNCTION = 3,
 | 
			
		||||
	OSMO_PFCP_DEST_IFACE_LI_FUNCTION = 4,
 | 
			
		||||
	OSMO_PFCP_DEST_IFACE_5G_VN_INTERNAL = 5,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* The enum values correspond to the bit index in the supported features bitmask in the PFCP UP Function Features IE.
 | 
			
		||||
 * 0 means first octet and first bit, "Octet 5 Bit 1" as in spec;
 | 
			
		||||
 * 7 means first octet last bit, "Octet 5 Bit 8";
 | 
			
		||||
 * 8 means second octet first bit, "Octet 6 Bit 1";
 | 
			
		||||
 * and so on.
 | 
			
		||||
 * Intended for use with osmo_pfcp_bits_get(), osmo_pfcp_bits_set(), osmo_pfcp_bits_to_str_c().
 | 
			
		||||
 */
 | 
			
		||||
enum osmo_pfcp_up_feature {
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_BUCP = 0,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_DDND,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_DLBD,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_TRST,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_FTUP,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_PFDM,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_HEEU,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_TREU,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_EMPU,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_PDIU,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_UDBC,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_QUOAC,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_TRACE,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_FRRT,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_PFDE,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_EPFAR,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_DPDRA,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_ADPDP,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_UEIP,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_SSET,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_MNOP,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_MTE,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_BUNDL,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_GCOM,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_MPAS,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_RTTL,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_VTIME,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_NORP,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_IP6PL,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_TSCU,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_MPTCP,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_ATSSSLL,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_QFQM,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_GPQM,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_MTEDT,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_CIOT,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_ETHAR,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_DDDS,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_RDS,
 | 
			
		||||
	OSMO_PFCP_UP_FEAT_RTTWP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* The enum values correspond to the bit index in the supported features bitmask in the PFCP CP Function Features IE.
 | 
			
		||||
 * 0 means first octet and first bit, "Octet 5 Bit 1" as in spec;
 | 
			
		||||
 * 7 means first octet last bit, "Octet 5 Bit 8";
 | 
			
		||||
 * 8 means second octet first bit, "Octet 6 Bit 1";
 | 
			
		||||
 * and so on.
 | 
			
		||||
 * Intended for use with osmo_pfcp_bits_get(), osmo_pfcp_bits_set(), osmo_pfcp_bits_to_str_c().
 | 
			
		||||
 */
 | 
			
		||||
enum osmo_pfcp_cp_feature {
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_LOAD = 0,
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_OVRL,
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_EPFAR,
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_SSET,
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_BUNDL,
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_MPAS,
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_ARDR,
 | 
			
		||||
	OSMO_PFCP_CP_FEAT_UIAUR,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* The enum values correspond to the bit index in the PFCP Apply Action IE.
 | 
			
		||||
 * 0 means first octet and first bit, "Octet 5 Bit 1" as in spec;
 | 
			
		||||
 * 7 means first octet last bit, "Octet 5 Bit 8";
 | 
			
		||||
 * 8 means second octet first bit, "Octet 6 Bit 1";
 | 
			
		||||
 * and so on.
 | 
			
		||||
 * Intended for use with osmo_pfcp_bits_get(), osmo_pfcp_bits_set(), osmo_pfcp_bits_to_str_c().
 | 
			
		||||
 */
 | 
			
		||||
enum osmo_pfcp_apply_action {
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_DROP = 0,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_FORW,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_BUFF,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_NOCP,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_DUPL,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_IPMA,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_IPMD,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_DFRT,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_EDRT,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_BDPN,
 | 
			
		||||
	OSMO_PFCP_APPLY_ACTION_DDPN,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* The enum values correspond to the bit index in the description bitmask in the PFCP Outer Header Creation IE.
 | 
			
		||||
 * 0 means first octet and first bit, "Octet 5 Bit 1" as in spec;
 | 
			
		||||
 * 7 means first octet last bit, "Octet 5 Bit 8";
 | 
			
		||||
 * 8 means second octet first bit, "Octet 6 Bit 1";
 | 
			
		||||
 * and so on.
 | 
			
		||||
 * Intended for use with osmo_pfcp_bits_get(), osmo_pfcp_bits_set(), osmo_pfcp_bits_to_str_c().
 | 
			
		||||
 */
 | 
			
		||||
enum osmo_pfcp_outer_header_creation {
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4 = 0,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV6,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV4,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV6,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_IPV4,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_IPV6,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_C_TAG,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_S_TAG,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_N19_INDICATION,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_CREATION_N6_INDICATION,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 29.244 8.2.64 */
 | 
			
		||||
enum osmo_pfcp_outer_header_removal_desc {
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4 = 0,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV6 = 1,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_UDP_IPV4 = 2,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_UDP_IPV6 = 3,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_IPV4 = 4,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_IPV6 = 5,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IP = 6,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_VLAN_S_TAG = 7,
 | 
			
		||||
	OSMO_PFCP_OUTER_HEADER_REMOVAL_S_TAG_AND_C_TAG = 8,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										94
									
								
								include/osmocom/pfcp/pfcp_strs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								include/osmocom/pfcp/pfcp_strs.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_proto.h>
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_message_type_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_message_type_str(enum osmo_pfcp_message_type val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_message_type_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_iei_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_iei_str(enum osmo_pfcp_iei val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_iei_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_cause_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_cause_str(enum osmo_pfcp_cause val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_cause_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_up_feature_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_up_feature_str(enum osmo_pfcp_up_feature val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_up_feature_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_cp_feature_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_cp_feature_str(enum osmo_pfcp_cp_feature val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_cp_feature_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_apply_action_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_apply_action_str(enum osmo_pfcp_apply_action val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_apply_action_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_outer_header_creation_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_outer_header_creation_str(enum osmo_pfcp_outer_header_creation val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_outer_header_creation_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_outer_header_removal_desc_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_outer_header_removal_desc_str(enum osmo_pfcp_outer_header_removal_desc val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_outer_header_removal_desc_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_source_iface_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_source_iface_str(enum osmo_pfcp_source_iface val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_source_iface_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_dest_iface_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_dest_iface_str(enum osmo_pfcp_dest_iface val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_dest_iface_strs, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern const struct value_string osmo_pfcp_3gpp_iface_type_strs[];
 | 
			
		||||
static inline const char *osmo_pfcp_3gpp_iface_type_str(enum osmo_pfcp_3gpp_iface_type val)
 | 
			
		||||
{
 | 
			
		||||
	return get_value_string(osmo_pfcp_3gpp_iface_type_strs, val);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
noinst_HEADERS = \
 | 
			
		||||
	netinst.h \
 | 
			
		||||
	up_endpoint.h \
 | 
			
		||||
	up_peer.h \
 | 
			
		||||
	up_session.h \
 | 
			
		||||
@@ -7,6 +6,5 @@ noinst_HEADERS = \
 | 
			
		||||
	upf_gtp.h \
 | 
			
		||||
	upf_gtpu_echo.h \
 | 
			
		||||
	upf_nft.h \
 | 
			
		||||
	upf_tun.h \
 | 
			
		||||
	up_gtp_action.h \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 
 | 
			
		||||
@@ -35,14 +35,14 @@ struct osmo_sockaddr;
 | 
			
		||||
struct up_endpoint {
 | 
			
		||||
	struct osmo_pfcp_endpoint *pfcp_ep;
 | 
			
		||||
 | 
			
		||||
	/* list of struct up_peer. */
 | 
			
		||||
	struct llist_head peers;
 | 
			
		||||
 | 
			
		||||
	uint64_t next_up_seid_state;
 | 
			
		||||
	uint64_t next_seid_state;
 | 
			
		||||
	uint32_t next_teid_state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct up_endpoint *up_endpoint_alloc(void *ctx, const struct osmo_sockaddr *local_addr);
 | 
			
		||||
int up_endpoint_bind(struct up_endpoint *up_ep);
 | 
			
		||||
struct up_endpoint *up_endpoint_init(void *ctx, const struct osmo_sockaddr *local_addr);
 | 
			
		||||
void up_endpoint_free(struct up_endpoint **ep);
 | 
			
		||||
 | 
			
		||||
uint64_t up_endpoint_next_up_seid(struct up_endpoint *ep);
 | 
			
		||||
uint64_t up_endpoint_next_seid(struct up_endpoint *ep);
 | 
			
		||||
uint32_t up_endpoint_next_teid(struct up_endpoint *ep);
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ struct up_session;
 | 
			
		||||
 | 
			
		||||
enum up_gtp_action_kind {
 | 
			
		||||
	UP_GTP_DROP,
 | 
			
		||||
	UP_GTP_U_TUNEND,
 | 
			
		||||
	UP_GTP_U_ENDECAPS,
 | 
			
		||||
	UP_GTP_U_TUNMAP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -48,25 +48,22 @@ struct up_gtp_action {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	struct up_session *session;
 | 
			
		||||
 | 
			
		||||
	uint16_t pdr_access;
 | 
			
		||||
	uint16_t pdr_core;
 | 
			
		||||
	uint16_t pdr_access;
 | 
			
		||||
 | 
			
		||||
	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 upf_tunend tunend;
 | 
			
		||||
		struct upf_gtp_tun_desc endecaps;
 | 
			
		||||
 | 
			
		||||
		/* Tunnel-map GTP: translate from one TEID to another and forward */
 | 
			
		||||
		struct upf_tunmap tunmap;
 | 
			
		||||
		struct upf_nft_tunmap_desc tunmap;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* volatile loop variable to match up wanted and actually present GTP actions */
 | 
			
		||||
	void *handle;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct up_gtp_action *up_gtp_action_alloc(void *ctx, struct up_session *session, enum up_gtp_action_kind kind, struct llist_head *dst);
 | 
			
		||||
void up_gtp_action_free(struct up_gtp_action *a);
 | 
			
		||||
 | 
			
		||||
int up_gtp_action_cmp(const struct up_gtp_action *a, const struct up_gtp_action *b);
 | 
			
		||||
 | 
			
		||||
int up_gtp_action_enable(struct up_gtp_action *a);
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,6 @@ struct up_peer {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
 | 
			
		||||
	struct osmo_fsm_inst *fi;
 | 
			
		||||
	/* backpointer */
 | 
			
		||||
	struct up_endpoint *up_endpoint;
 | 
			
		||||
 | 
			
		||||
	/* peer's remote address */
 | 
			
		||||
 
 | 
			
		||||
@@ -41,12 +41,17 @@ enum up_session_fsm_event {
 | 
			
		||||
	UP_SESSION_EV_USE_COUNT_ZERO,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum up_session_kind {
 | 
			
		||||
	UP_SESSION_DROP,
 | 
			
		||||
	UP_SESSION_GTP_U_ENDECAPS,
 | 
			
		||||
	UP_SESSION_GTP_U_FORW,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct up_session {
 | 
			
		||||
	struct hlist_node node_by_up_seid;
 | 
			
		||||
	struct hlist_node node_by_cp_seid;
 | 
			
		||||
 | 
			
		||||
	struct osmo_fsm_inst *fi;
 | 
			
		||||
	/* backpointer */
 | 
			
		||||
	struct up_peer *up_peer;
 | 
			
		||||
 | 
			
		||||
	struct osmo_pfcp_ie_f_seid cp_f_seid;
 | 
			
		||||
@@ -55,20 +60,18 @@ struct up_session {
 | 
			
		||||
	struct osmo_use_count use_count;
 | 
			
		||||
	struct osmo_use_count_entry use_count_buf[8];
 | 
			
		||||
 | 
			
		||||
	/* llist of struct pdr */
 | 
			
		||||
	struct llist_head pdrs;
 | 
			
		||||
	/* llist of struct far */
 | 
			
		||||
	struct llist_head fars;
 | 
			
		||||
	/* llist of struct chosen_f_teid */
 | 
			
		||||
	struct llist_head chosen_f_teids;
 | 
			
		||||
 | 
			
		||||
	/* llist of struct up_gtp_action */
 | 
			
		||||
	struct llist_head active_gtp_actions;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct up_session *up_session_find_or_add(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid);
 | 
			
		||||
struct up_session *up_session_find_or_add(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid,
 | 
			
		||||
					  const struct osmo_pfcp_ie_f_seid *up_f_seid);
 | 
			
		||||
struct up_session *up_session_find_by_up_seid(struct up_peer *peer, uint64_t up_seid);
 | 
			
		||||
struct up_session *up_session_find_by_cp_f_seid(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid);
 | 
			
		||||
struct up_session *up_session_find_by_local_teid(struct up_peer *peer, uint32_t teid);
 | 
			
		||||
 | 
			
		||||
void up_session_set_msg_ctx(struct up_session *session, struct osmo_pfcp_msg *m);
 | 
			
		||||
 | 
			
		||||
@@ -82,8 +85,7 @@ int up_session_to_str_buf(char *buf, size_t buflen, struct up_session *session);
 | 
			
		||||
char *up_session_to_str_c(void *ctx, struct up_session *session);
 | 
			
		||||
 | 
			
		||||
struct pdr {
 | 
			
		||||
	struct llist_head entry; /* item in session->pdrs */
 | 
			
		||||
	struct hlist_node node_by_local_f_teid; /* item in g_upf->gtp.pdrs_by_local_f_teid */
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	struct up_session *session;
 | 
			
		||||
 | 
			
		||||
	struct osmo_pfcp_ie_create_pdr desc;
 | 
			
		||||
@@ -94,8 +96,8 @@ struct pdr {
 | 
			
		||||
 | 
			
		||||
	bool rx_decaps;
 | 
			
		||||
	bool forw_encaps;
 | 
			
		||||
	bool access_to_core;
 | 
			
		||||
	bool core_to_access;
 | 
			
		||||
	bool forw_to_core;
 | 
			
		||||
	bool forw_from_core;
 | 
			
		||||
 | 
			
		||||
	struct pdr *reverse_pdr;
 | 
			
		||||
	bool active;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
@@ -21,18 +21,3 @@
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
 | 
			
		||||
struct upf_tun_ep {
 | 
			
		||||
	struct osmo_sockaddr addr;
 | 
			
		||||
	uint32_t teid;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct upf_tun {
 | 
			
		||||
	struct upf_tun_ep local;
 | 
			
		||||
	struct upf_tun_ep remote;
 | 
			
		||||
};
 | 
			
		||||
@@ -28,7 +28,6 @@
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/core/hashtable.h>
 | 
			
		||||
 | 
			
		||||
struct osmo_tdef;
 | 
			
		||||
struct ctrl_handle;
 | 
			
		||||
@@ -38,21 +37,14 @@ struct nft_ctx;
 | 
			
		||||
 | 
			
		||||
#define UPF_PFCP_LISTEN_DEFAULT "0.0.0.0"
 | 
			
		||||
 | 
			
		||||
#define PORT_GTP0_C 3386
 | 
			
		||||
#define PORT_GTP0_U 3386
 | 
			
		||||
 | 
			
		||||
#define PORT_GTP1_C 2123
 | 
			
		||||
#define PORT_GTP1_U 2152
 | 
			
		||||
 | 
			
		||||
extern struct osmo_tdef_group g_upf_tdef_groups[];
 | 
			
		||||
extern struct osmo_tdef g_upf_nft_tdefs[];
 | 
			
		||||
 | 
			
		||||
struct pfcp_vty_cfg {
 | 
			
		||||
	char *local_addr;
 | 
			
		||||
	uint16_t local_port;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct tunend_vty_cfg_dev {
 | 
			
		||||
struct gtp_vty_cfg_dev {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
 | 
			
		||||
	/* If true, osmo-upf creates the GTP device on startup. If false, the GTP device was created by the user, and we
 | 
			
		||||
@@ -68,18 +60,12 @@ struct tunend_vty_cfg_dev {
 | 
			
		||||
	char *local_addr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct tunend_vty_cfg {
 | 
			
		||||
	/* list of struct tunend_vty_cfg_dev, GTP devices as in the config file. The actual GTP devices in use are in
 | 
			
		||||
	 * g_upf->tunend.devs. */
 | 
			
		||||
struct gtp_vty_cfg {
 | 
			
		||||
	/* list of struct gtp_vty_cfg_dev, GTP devices as in the config file. The actual GTP devices in use are in
 | 
			
		||||
	 * g_upf->gtp.devs. */
 | 
			
		||||
	struct llist_head devs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Item in an llist of string pointers */
 | 
			
		||||
struct string_listitem {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	char *str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct g_upf {
 | 
			
		||||
	struct ctrl_handle *ctrl;
 | 
			
		||||
 | 
			
		||||
@@ -90,11 +76,8 @@ struct g_upf {
 | 
			
		||||
 | 
			
		||||
	/* Tunnel encaps decaps via GTP kernel module */
 | 
			
		||||
	struct {
 | 
			
		||||
		/* if true, don't actually send commands to the GTP kernel module, just return success. */
 | 
			
		||||
		bool mockup;
 | 
			
		||||
 | 
			
		||||
		/* GTP devices as in osmo-upf.cfg */
 | 
			
		||||
		struct tunend_vty_cfg vty_cfg;
 | 
			
		||||
		struct gtp_vty_cfg vty_cfg;
 | 
			
		||||
 | 
			
		||||
		/* GTP devices actually in use, list of struct upf_gtp_dev. */
 | 
			
		||||
		struct llist_head devs;
 | 
			
		||||
@@ -103,30 +86,15 @@ struct g_upf {
 | 
			
		||||
		int32_t genl_id;
 | 
			
		||||
 | 
			
		||||
		uint8_t recovery_count;
 | 
			
		||||
	} tunend;
 | 
			
		||||
	} gtp;
 | 
			
		||||
 | 
			
		||||
	/* Tunnel forwarding via linux netfilter */
 | 
			
		||||
	struct {
 | 
			
		||||
		/* if true, don't actually send commands to nftables, just return success. */
 | 
			
		||||
		bool mockup;
 | 
			
		||||
 | 
			
		||||
		struct nft_ctx *nft_ctx;
 | 
			
		||||
		char *table_name;
 | 
			
		||||
		int priority_pre;
 | 
			
		||||
		int priority_post;
 | 
			
		||||
		uint32_t next_chain_id_state;
 | 
			
		||||
		/* hashtable of (struct upf_nft_tun)->node_by_chain_id: */
 | 
			
		||||
		DECLARE_HASHTABLE(nft_tun_by_chain_id, 10);
 | 
			
		||||
	} tunmap;
 | 
			
		||||
 | 
			
		||||
	struct {
 | 
			
		||||
		uint32_t next_local_teid_state;
 | 
			
		||||
		/* hashtable of (struct pdr)->node_by_local_f_teid: */
 | 
			
		||||
		DECLARE_HASHTABLE(pdrs_by_local_f_teid, 10);
 | 
			
		||||
		uint16_t next_echo_seq_nr;
 | 
			
		||||
	} gtp;
 | 
			
		||||
 | 
			
		||||
	struct llist_head netinst;
 | 
			
		||||
		int priority;
 | 
			
		||||
		uint32_t next_id_state;
 | 
			
		||||
	} nft;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern struct g_upf *g_upf;
 | 
			
		||||
@@ -141,11 +109,7 @@ enum upf_log_subsys {
 | 
			
		||||
 | 
			
		||||
void g_upf_alloc(void *ctx);
 | 
			
		||||
void upf_vty_init();
 | 
			
		||||
int upf_pfcp_init(void);
 | 
			
		||||
int upf_pfcp_listen(void);
 | 
			
		||||
int upf_pfcp_listen();
 | 
			
		||||
 | 
			
		||||
int upf_gtp_devs_open();
 | 
			
		||||
void upf_gtp_devs_close();
 | 
			
		||||
 | 
			
		||||
uint32_t upf_next_local_teid(void);
 | 
			
		||||
uint32_t upf_next_chain_id(void);
 | 
			
		||||
 
 | 
			
		||||
@@ -24,15 +24,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/core/hashtable.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/upf/upf_tun.h>
 | 
			
		||||
 | 
			
		||||
#define LOG_GTP_DEV(DEV, LEVEL, FMT, ARGS...) \
 | 
			
		||||
	LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_dev_to_str_c(OTC_SELECT, (DEV)), ##ARGS)
 | 
			
		||||
 | 
			
		||||
#define PORT_GTP0_C 3386
 | 
			
		||||
#define PORT_GTP0_U 3386
 | 
			
		||||
 | 
			
		||||
#define PORT_GTP1_C 2123
 | 
			
		||||
#define PORT_GTP1_U 2152
 | 
			
		||||
 | 
			
		||||
struct upf_gtp_dev {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
 | 
			
		||||
@@ -54,34 +57,29 @@ struct upf_gtp_dev {
 | 
			
		||||
 | 
			
		||||
	uint32_t ifidx;
 | 
			
		||||
 | 
			
		||||
	/* list of struct upf_gtp_tunend */
 | 
			
		||||
	struct llist_head tunnels;
 | 
			
		||||
	/* hashtable of (struct upf_gtp_tunen) with key desc.access.local.teid */
 | 
			
		||||
	DECLARE_HASHTABLE(tunnels_by_local_f_teid, 10);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Description of a GTP encapsulation / decapsulation.
 | 
			
		||||
 * The active state to operate the GTP kernel module accordingly is kept in struct upf_gtp_tunend. */
 | 
			
		||||
struct upf_tunend {
 | 
			
		||||
	struct upf_tun access;
 | 
			
		||||
	struct {
 | 
			
		||||
		struct osmo_sockaddr ue_local_addr;
 | 
			
		||||
	} core;
 | 
			
		||||
struct upf_gtp_tun_desc {
 | 
			
		||||
	uint32_t local_teid;
 | 
			
		||||
	uint32_t remote_teid;
 | 
			
		||||
	struct osmo_sockaddr ue_addr;
 | 
			
		||||
	struct osmo_sockaddr gtp_remote_addr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int upf_gtp_tunend_cmp(const struct upf_tunend *a, const struct upf_tunend *b);
 | 
			
		||||
int upf_gtp_tun_desc_cmp(const struct upf_gtp_tun_desc *a, const struct upf_gtp_tun_desc *b);
 | 
			
		||||
 | 
			
		||||
int upf_gtp_genl_ensure_open();
 | 
			
		||||
int upf_gtp_genl_open();
 | 
			
		||||
void upf_gtp_genl_close();
 | 
			
		||||
 | 
			
		||||
int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_addr, bool listen_for_gtpv0,
 | 
			
		||||
		     bool sgsn_mode);
 | 
			
		||||
struct upf_gtp_dev *upf_gtp_dev_find_by_name(const char *name);
 | 
			
		||||
struct upf_gtp_dev *upf_gtp_dev_find_by_local_addr(const struct osmo_sockaddr *local_addr);
 | 
			
		||||
struct upf_gtp_dev *upf_gtp_dev_first();
 | 
			
		||||
 | 
			
		||||
int upf_gtp_dev_tunend_add(struct upf_gtp_dev *dev, const struct upf_tunend *t);
 | 
			
		||||
int upf_gtp_dev_tunend_del(struct upf_gtp_dev *dev, const struct upf_tunend *t);
 | 
			
		||||
int upf_gtp_dev_tunnel_add(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *t);
 | 
			
		||||
bool upf_gtp_dev_is_tunnel_active(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *t);
 | 
			
		||||
int upf_gtp_dev_tunnel_del(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *t);
 | 
			
		||||
 | 
			
		||||
int upf_gtp_dev_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_dev *dev);
 | 
			
		||||
char *upf_gtp_dev_to_str_c(void *ctx, const struct upf_gtp_dev *dev);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,4 +2,3 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
int upf_gtpu_echo_setup(struct upf_gtp_dev *dev);
 | 
			
		||||
int upf_gtpu_echo_req_tx(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, uint16_t seq_nr);
 | 
			
		||||
 
 | 
			
		||||
@@ -24,30 +24,27 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <osmocom/core/hashtable.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/upf/upf_tun.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
 | 
			
		||||
struct upf_nft_tun {
 | 
			
		||||
	struct hlist_node node_by_chain_id; /* item in g_upf->tunmap.nft_tun_by_chain_id */
 | 
			
		||||
	struct upf_tun tun;
 | 
			
		||||
	uint32_t chain_id;
 | 
			
		||||
#define NFT_CHAIN_NAME_PREFIX_TUNMAP "tunmap"
 | 
			
		||||
 | 
			
		||||
struct upf_nft_tunmap_desc {
 | 
			
		||||
	struct {
 | 
			
		||||
		struct osmo_sockaddr gtp_remote_addr;
 | 
			
		||||
		uint32_t local_teid;
 | 
			
		||||
		uint32_t remote_teid;
 | 
			
		||||
	} access;
 | 
			
		||||
	struct {
 | 
			
		||||
		struct osmo_sockaddr gtp_remote_addr;
 | 
			
		||||
		uint32_t local_teid;
 | 
			
		||||
		uint32_t remote_teid;
 | 
			
		||||
	} core;
 | 
			
		||||
	uint32_t id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct upf_tunmap {
 | 
			
		||||
	struct upf_nft_tun access;
 | 
			
		||||
	struct upf_nft_tun core;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int upf_nft_tunmap_to_str_buf(char *buf, size_t buflen, const struct upf_tunmap *tunmap);
 | 
			
		||||
char *upf_nft_tunmap_to_str_c(void *ctx, const struct upf_tunmap *tunmap);
 | 
			
		||||
 | 
			
		||||
int upf_nft_init();
 | 
			
		||||
int upf_nft_free();
 | 
			
		||||
 | 
			
		||||
char *upf_nft_tunmap_get_table_init_str(void *ctx);
 | 
			
		||||
char *upf_nft_tunmap_get_vmap_init_str(void *ctx);
 | 
			
		||||
char *upf_nft_tunmap_get_ruleset_str(void *ctx, struct upf_tunmap *tunmap);
 | 
			
		||||
char *upf_nft_tunmap_get_ruleset_del_str(void *ctx, struct upf_tunmap *tunmap);
 | 
			
		||||
int upf_nft_tunmap_create(struct upf_tunmap *tunmap);
 | 
			
		||||
int upf_nft_tunmap_delete(struct upf_tunmap *tunmap);
 | 
			
		||||
int upf_nft_tunmap_create(struct upf_nft_tunmap_desc *tunmap);
 | 
			
		||||
int upf_nft_tunmap_delete(struct upf_nft_tunmap_desc *tunmap);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
# (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
# (C) 2021-2022 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
 | 
			
		||||
# 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
 | 
			
		||||
@@ -15,13 +15,13 @@
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>
 | 
			
		||||
 | 
			
		||||
app_configs = {
 | 
			
		||||
    "osmo-upf": ["doc/examples/osmo-upf/osmo-upf-mockup.cfg"]
 | 
			
		||||
    "osmo-upf": ["doc/examples/osmo-upf/osmo-upf.cfg"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apps = [(4275, "src/osmo-upf/osmo-upf", "OsmoUPF", "osmo-upf")
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
vty_command = ["./src/osmo-upf/osmo-upf", "-c",
 | 
			
		||||
               "doc/examples/osmo-upf/osmo-upf-mockup.cfg"]
 | 
			
		||||
               "doc/examples/osmo-upf/osmo-upf.cfg"]
 | 
			
		||||
 | 
			
		||||
vty_app = apps[0]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	libosmo-gtlv \
 | 
			
		||||
	libosmo-pfcp \
 | 
			
		||||
	osmo-upf \
 | 
			
		||||
	osmo-pfcp-tool \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								src/libosmo-gtlv/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/libosmo-gtlv/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
AM_CPPFLAGS = \
 | 
			
		||||
	$(all_includes) \
 | 
			
		||||
	-I$(top_srcdir)/include \
 | 
			
		||||
	-I$(top_builddir) \
 | 
			
		||||
	-I$(builddir) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_CFLAGS = \
 | 
			
		||||
	-Wall \
 | 
			
		||||
	$(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
	$(COVERAGE_CFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_LDFLAGS = \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(COVERAGE_LDFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
noinst_LIBRARIES = \
 | 
			
		||||
	libosmo-gtlv.a \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
libosmo_gtlv_a_SOURCES = \
 | 
			
		||||
	gtlv.c \
 | 
			
		||||
	gtlv_dec_enc.c \
 | 
			
		||||
	gtlv_gen.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
							
								
								
									
										342
									
								
								src/libosmo-gtlv/gtlv.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								src/libosmo-gtlv/gtlv.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,342 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/gtlv/gtlv.h>
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_tag_inst_cmp(const struct osmo_gtlv_tag_inst *a, const struct osmo_gtlv_tag_inst *b)
 | 
			
		||||
{
 | 
			
		||||
	int cmp;
 | 
			
		||||
	if (a == b)
 | 
			
		||||
		return 0;
 | 
			
		||||
	if (!a)
 | 
			
		||||
		return -1;
 | 
			
		||||
	if (!b)
 | 
			
		||||
		return 1;
 | 
			
		||||
	cmp = OSMO_CMP(a->tag, b->tag);
 | 
			
		||||
	if (cmp)
 | 
			
		||||
		return cmp;
 | 
			
		||||
	cmp = OSMO_CMP(a->instance_present ? 1 : 0, b->instance_present ? 1 : 0);
 | 
			
		||||
	if (cmp)
 | 
			
		||||
		return cmp;
 | 
			
		||||
	if (a->instance_present)
 | 
			
		||||
		return OSMO_CMP(a->instance, b->instance);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_tag_inst_to_str_buf(char *buf, size_t buflen, const struct osmo_gtlv_tag_inst *ti,
 | 
			
		||||
				 const struct value_string *tag_names)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	if (!tag_names)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%u", ti->tag);
 | 
			
		||||
	else
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%s", get_value_string(tag_names, ti->tag));
 | 
			
		||||
	if (ti->instance_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "[%u]", ti->instance);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *osmo_gtlv_tag_inst_to_str_c(void *ctx, const struct osmo_gtlv_tag_inst *ti,
 | 
			
		||||
				 const struct value_string *tag_names)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_gtlv_tag_inst_to_str_buf, ti, tag_names)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int next_tl_valid(const struct osmo_gtlv_load *gtlv, const uint8_t **ie_start_p, size_t *buflen_left_p)
 | 
			
		||||
{
 | 
			
		||||
	const uint8_t *ie_start;
 | 
			
		||||
	size_t buflen_left;
 | 
			
		||||
 | 
			
		||||
	/* Start of next IE, or first IE for first invocation. */
 | 
			
		||||
	if (!gtlv->val)
 | 
			
		||||
		ie_start = gtlv->src.data;
 | 
			
		||||
	else
 | 
			
		||||
		ie_start = gtlv->val + gtlv->len;
 | 
			
		||||
 | 
			
		||||
	/* Sanity */
 | 
			
		||||
	if (ie_start < gtlv->src.data || ie_start > gtlv->src.data + gtlv->src.len)
 | 
			
		||||
		return -ENOSPC;
 | 
			
		||||
 | 
			
		||||
	buflen_left = gtlv->src.len - (ie_start - gtlv->src.data);
 | 
			
		||||
 | 
			
		||||
	/* Too short for parsing an IE? Check also against integer overflow. */
 | 
			
		||||
	if (buflen_left && ((buflen_left < gtlv->cfg->tl_min_size) || (buflen_left > gtlv->src.len)))
 | 
			
		||||
		return -EBADMSG;
 | 
			
		||||
 | 
			
		||||
	*ie_start_p = ie_start;
 | 
			
		||||
	*buflen_left_p = buflen_left;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Return a TLV IE from a message buffer.
 | 
			
		||||
 *
 | 
			
		||||
 * Return the first or next TLV data found in the data buffer, based on the state of the gtlv parameter.
 | 
			
		||||
 * When gtlv->val is NULL, return the first IE in the data buffer.
 | 
			
		||||
 * Otherwise assume that gtlv points at a valid IE in the data structure, and return the subsequent IE.
 | 
			
		||||
 *
 | 
			
		||||
 * Usage example:
 | 
			
		||||
 *
 | 
			
		||||
 *   struct osmo_gtlv gtlv = {
 | 
			
		||||
 *           .cfg = osmo_t16l16v_cfg,
 | 
			
		||||
 *           .src = { .data = msgb_l3(msg), .len = msgb_l3len(msg) },
 | 
			
		||||
 *   };
 | 
			
		||||
 *   for (;;) {
 | 
			
		||||
 *           if (osmo_gtlv_next(>lv)) {
 | 
			
		||||
 *                   printf("Error\n");
 | 
			
		||||
 *                   break;
 | 
			
		||||
 *           }
 | 
			
		||||
 *           if (!gtlv.val) {
 | 
			
		||||
 *                   printf("End\n");
 | 
			
		||||
 *                   break;
 | 
			
		||||
 *           }
 | 
			
		||||
 *           printf("Tag %u: %zu octets: %s\n", gtlv.tag, gtlv.len, osmo_hexdump(gtlv.val, gtlv.len));
 | 
			
		||||
 *   }
 | 
			
		||||
 *
 | 
			
		||||
 * \param[inout] gtlv  Buffer to return the IE data, and state for TLV parsing position. gtlv->msg should indicate the
 | 
			
		||||
 *                   overall message buffer. The other gtlv members should be zero initialized before the first call, and
 | 
			
		||||
 *                   remain unchanged between invocations of this function.
 | 
			
		||||
 * \returns 0 on success, negative on TLV parsing error. The IE data is returned in gtlv->tag, gtlv->len and gtlv->val;
 | 
			
		||||
 *          gtlv->val == NULL if no more IEs remain in the buffer.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlv_load_next(struct osmo_gtlv_load *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	const uint8_t *ie_start;
 | 
			
		||||
	const uint8_t *ie_end;
 | 
			
		||||
	size_t buflen_left;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	rc = next_tl_valid(gtlv, &ie_start, &buflen_left);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* No more IEs? */
 | 
			
		||||
	if (!buflen_left) {
 | 
			
		||||
		gtlv->val = NULL;
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Locate next IE */
 | 
			
		||||
	OSMO_ASSERT(gtlv->cfg->load_tl);
 | 
			
		||||
	gtlv->ti = (struct osmo_gtlv_tag_inst){};
 | 
			
		||||
	rc = gtlv->cfg->load_tl(gtlv, ie_start, buflen_left);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* Sanity */
 | 
			
		||||
	ie_end = gtlv->val + gtlv->len;
 | 
			
		||||
	if (ie_end < gtlv->src.data || ie_end > gtlv->src.data + gtlv->src.len)
 | 
			
		||||
		return -EBADMSG;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Return the tag of the IE that osmo_gtlv_next() would yield, do not change the gtlv state.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] gtlv  state for TLV parsing position; is not modified.
 | 
			
		||||
 * \param[out] tag  the tag number on success, if NULL don't return the tag.
 | 
			
		||||
 * \param[out] instance  the instance number or OSMO_GTLV_NO_INSTANCE if there is no instance value,
 | 
			
		||||
 *                       if NULL don't return the instance value.
 | 
			
		||||
 * \returns 0 on success, negative on TLV parsing error, -ENOENT when no more tags follow.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlv_load_peek_tag(const struct osmo_gtlv_load *gtlv, struct osmo_gtlv_tag_inst *ti)
 | 
			
		||||
{
 | 
			
		||||
	const uint8_t *ie_start;
 | 
			
		||||
	size_t buflen_left;
 | 
			
		||||
	int rc;
 | 
			
		||||
	/* Guard against modification by load_tl(). */
 | 
			
		||||
	struct osmo_gtlv_load mtlv = *gtlv;
 | 
			
		||||
	mtlv.ti = (struct osmo_gtlv_tag_inst){};
 | 
			
		||||
 | 
			
		||||
	rc = next_tl_valid(&mtlv, &ie_start, &buflen_left);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	if (!buflen_left)
 | 
			
		||||
		return -ENOENT;
 | 
			
		||||
 | 
			
		||||
	/* Return next IE tag*/
 | 
			
		||||
	OSMO_ASSERT(mtlv.cfg->load_tl);
 | 
			
		||||
	rc = gtlv->cfg->load_tl(&mtlv, ie_start, buflen_left);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return -EBADMSG;
 | 
			
		||||
	if (ti)
 | 
			
		||||
		*ti = mtlv.ti;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Same as osmo_gtlv_load_next(), but skip any IEs until the given tag is reached. Change the gtlv state only when success
 | 
			
		||||
 * is returned.
 | 
			
		||||
 * \param[out] gtlv  Return the next IE's TLV info.
 | 
			
		||||
 * \param[in] tag  Tag value to match.
 | 
			
		||||
 * \param[in] instance  Instance value to match; For IEs that have no instance value (no TLIV), pass
 | 
			
		||||
 *                      OSMO_GTLV_NO_INSTANCE.
 | 
			
		||||
 * \return 0 when the tag is found. Return -ENOENT when no such tag follows and keep the gtlv unchanged. */
 | 
			
		||||
int osmo_gtlv_load_next_by_tag(struct osmo_gtlv_load *gtlv, unsigned int tag)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gtlv_tag_inst ti = { .tag = tag };
 | 
			
		||||
	return osmo_gtlv_load_next_by_tag_inst(gtlv, &ti);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_gtlv_load_next_by_tag_inst(struct osmo_gtlv_load *gtlv, const struct osmo_gtlv_tag_inst *ti)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gtlv_load work = *gtlv;
 | 
			
		||||
	for (;;) {
 | 
			
		||||
		int rc = osmo_gtlv_load_next(&work);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
		if (!work.val)
 | 
			
		||||
			return -ENOENT;
 | 
			
		||||
		if (!osmo_gtlv_tag_inst_cmp(&work.ti, ti)) {
 | 
			
		||||
			*gtlv = work;
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Put tag header and length at the end of the msgb, according to gtlv->cfg->store_tl().
 | 
			
		||||
 * If the length is not known yet, it can be passed as 0 at first, and osmo_gtlv_put_update_tl() can determine the
 | 
			
		||||
 * resulting length after the value part was put into the msgb.
 | 
			
		||||
 *
 | 
			
		||||
 * Usage example:
 | 
			
		||||
 *
 | 
			
		||||
 *     struct msgb *msg = msgb_alloc(1024, "foo"),
 | 
			
		||||
 *     struct osmo_gtlv_put gtlv = {
 | 
			
		||||
 *             .cfg = osmo_t16l16v_cfg,
 | 
			
		||||
 *             .dst = msg,
 | 
			
		||||
 *     }
 | 
			
		||||
 *
 | 
			
		||||
 *     osmo_gtlv_put_tl(gtlv, 23, 0); // tag 23, length 0 = not known yet
 | 
			
		||||
 *
 | 
			
		||||
 *     msgb_put(msg, 42);
 | 
			
		||||
 *     ...
 | 
			
		||||
 *     msgb_put(msg, 42);
 | 
			
		||||
 *     ...
 | 
			
		||||
 *     msgb_put(msg, 42);
 | 
			
		||||
 *
 | 
			
		||||
 *     osmo_gtlv_put_update_tl(gtlv);
 | 
			
		||||
 *
 | 
			
		||||
 * Return 0 on success, -EINVAL if the tag value is invalid, -EMSGSIZE if len is too large.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlv_put_tl(struct osmo_gtlv_put *gtlv, unsigned int tag, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gtlv_tag_inst ti = { .tag = tag };
 | 
			
		||||
	return osmo_gtlv_put_tli(gtlv, &ti, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Put tag header, instance value and length at the end of the msgb, according to gtlv->cfg->store_tl().
 | 
			
		||||
 * This is the same as osmo_gtlv_put_tl(), only osmo_gtlv_put_tl() passes instance = 0.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlv_put_tli(struct osmo_gtlv_put *gtlv, const struct osmo_gtlv_tag_inst *ti, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	uint8_t *last_tl;
 | 
			
		||||
	OSMO_ASSERT(gtlv->cfg->store_tl);
 | 
			
		||||
	last_tl = gtlv->dst->tail;
 | 
			
		||||
	rc = gtlv->cfg->store_tl(gtlv->dst->tail, msgb_tailroom(gtlv->dst), ti, len, gtlv);
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		return rc;
 | 
			
		||||
	if (rc > 0)
 | 
			
		||||
		msgb_put(gtlv->dst, rc);
 | 
			
		||||
	gtlv->last_ti = *ti;
 | 
			
		||||
	gtlv->last_tl = last_tl;
 | 
			
		||||
	gtlv->last_val = gtlv->dst->tail;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Update the length of the last put IE header (last call to osmo_gtlv_put_tl()) to match with the current
 | 
			
		||||
 * gtlv->dst->tail.
 | 
			
		||||
 * Return 0 on success, -EMSGSIZE if the amount of data written since osmo_gtlv_put_tl() is too large.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlv_put_update_tl(struct osmo_gtlv_put *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	size_t len = gtlv->dst->tail - gtlv->last_val;
 | 
			
		||||
	int rc = gtlv->cfg->store_tl(gtlv->last_tl, gtlv->last_val - gtlv->last_tl, >lv->last_ti, len, gtlv);
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		return rc;
 | 
			
		||||
	/* In case the TL has changed in size, hopefully the implementation has moved the msgb data. Make sure last_val
 | 
			
		||||
	 * points at the right place now. */
 | 
			
		||||
	gtlv->last_val = gtlv->last_tl + rc;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int t8l8v_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t src_data_len)
 | 
			
		||||
{
 | 
			
		||||
	/* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 2. */
 | 
			
		||||
	gtlv->ti.tag = src_data[0];
 | 
			
		||||
	gtlv->len = src_data[1];
 | 
			
		||||
	gtlv->val = src_data + 2;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int t8l8v_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_gtlv_tag_inst *ti, size_t len,
 | 
			
		||||
			  struct osmo_gtlv_put *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	if (ti->tag > UINT8_MAX)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	if (len > UINT8_MAX)
 | 
			
		||||
		return -EMSGSIZE;
 | 
			
		||||
	if (dst_data_avail < 2)
 | 
			
		||||
		return -ENOSPC;
 | 
			
		||||
	dst_data[0] = ti->tag;
 | 
			
		||||
	dst_data[1] = len;
 | 
			
		||||
	return 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct osmo_gtlv_cfg osmo_t8l8v_cfg = {
 | 
			
		||||
	.tl_min_size = 2,
 | 
			
		||||
	.load_tl = t8l8v_load_tl,
 | 
			
		||||
	.store_tl = t8l8v_store_tl,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int t16l16v_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t src_data_len)
 | 
			
		||||
{
 | 
			
		||||
	/* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 4. */
 | 
			
		||||
	gtlv->ti.tag = osmo_load16be(src_data);
 | 
			
		||||
	gtlv->len = osmo_load16be(src_data + 2);
 | 
			
		||||
	gtlv->val = src_data + 4;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int t16l16v_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_gtlv_tag_inst *ti, size_t len,
 | 
			
		||||
			    struct osmo_gtlv_put *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	if (ti->tag > UINT16_MAX)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	if (len > UINT16_MAX)
 | 
			
		||||
		return -EMSGSIZE;
 | 
			
		||||
	if (dst_data_avail < 4)
 | 
			
		||||
		return -ENOSPC;
 | 
			
		||||
	osmo_store16be(ti->tag, dst_data);
 | 
			
		||||
	osmo_store16be(len, dst_data + 2);
 | 
			
		||||
	return 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct osmo_gtlv_cfg osmo_t16l16v_cfg = {
 | 
			
		||||
	.tl_min_size = 4,
 | 
			
		||||
	.load_tl = t16l16v_load_tl,
 | 
			
		||||
	.store_tl = t16l16v_store_tl,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										530
									
								
								src/libosmo-gtlv/gtlv_dec_enc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										530
									
								
								src/libosmo-gtlv/gtlv_dec_enc.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,530 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gtlv/gtlv_dec_enc.h>
 | 
			
		||||
 | 
			
		||||
/* Reverse offsetof(): return the address of the struct member for a given osmo_gtlv_msg and member ofs_foo value. */
 | 
			
		||||
#define MEMB(M, MEMB_OFS) ((void *)((char *)(M) + (MEMB_OFS)))
 | 
			
		||||
 | 
			
		||||
#define RETURN_ERROR(RC, TAG_INST, FMT, ARGS...) \
 | 
			
		||||
	do {\
 | 
			
		||||
		if (err_cb) { \
 | 
			
		||||
			if ((TAG_INST).instance_present) \
 | 
			
		||||
				err_cb(err_cb_data, (void *)decoded_struct, __FILE__, __LINE__, \
 | 
			
		||||
				       "tag 0x%x = %s instance %u: " FMT " (%d: %s)\n", \
 | 
			
		||||
				       (TAG_INST).tag, get_value_string(iei_strs, (TAG_INST).tag), \
 | 
			
		||||
				       (TAG_INST).instance, ##ARGS, \
 | 
			
		||||
				       RC, strerror((RC) > 0 ? (RC) : -(RC))); \
 | 
			
		||||
			else \
 | 
			
		||||
				err_cb(err_cb_data, (void *)decoded_struct, __FILE__, __LINE__, \
 | 
			
		||||
				       "tag 0x%x = %s: " FMT " (%d: %s)\n", \
 | 
			
		||||
				       (TAG_INST).tag, get_value_string(iei_strs, (TAG_INST).tag), ##ARGS, \
 | 
			
		||||
				       RC, strerror((RC) > 0 ? (RC) : -(RC))); \
 | 
			
		||||
		} \
 | 
			
		||||
		return RC; \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*! Decode a TLV structure from raw data to a decoded struct, for unordered TLV IEs.
 | 
			
		||||
 * How to decode IE values and where to place them in the decoded struct, is defined by ie_coding, an array terminated
 | 
			
		||||
 * by a '{}' entry.
 | 
			
		||||
 * The IEs may appear in any ordering in the TLV data.
 | 
			
		||||
 * For unordered decoding, only IEs with has_presence_flag == true or has_count == true may repeat. Other IE definitions
 | 
			
		||||
 * cause the last read TLV to overwrite all previous decodings, all into the first occurrence in ie_coding.
 | 
			
		||||
 * \param[out] decoded_struct  Pointer to the struct to write parsed IE data to.
 | 
			
		||||
 * \param[in] obj_ofs  Pass as zero. Used for nested IEs: offset added to decoded_struct to get to a sub-struct.
 | 
			
		||||
 * \param[in] gtlv  TLV data to parse, as given in gtlv->msg.*. Must be ready for osmo_gtlv_load_start().
 | 
			
		||||
 * \param[in] ie_coding  A list of permitted/expected IEI tags and instructions for decoding.
 | 
			
		||||
 * \param[in] err_cb  Function to call to report an error message, or NULL.
 | 
			
		||||
 * \param[in] err_cb_data  Caller supplied context to pass to the err_cb as 'data' argument.
 | 
			
		||||
 * \param[in] iei_strs  value_string array to give IEI names in error messages passed to err_cb(), or NULL.
 | 
			
		||||
 * \return 0 on success, negative on error.
 | 
			
		||||
 */
 | 
			
		||||
static int osmo_gtlvs_decode_unordered(void *decoded_struct, unsigned int obj_ofs, struct osmo_gtlv_load *gtlv,
 | 
			
		||||
				      const struct osmo_gtlv_coding *ie_coding,
 | 
			
		||||
				      osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)
 | 
			
		||||
{
 | 
			
		||||
	void *obj = MEMB(decoded_struct, obj_ofs);
 | 
			
		||||
	const struct osmo_gtlv_coding *iec;
 | 
			
		||||
	unsigned int *multi_count_p;
 | 
			
		||||
 | 
			
		||||
	/* To check for presence of mandatory IEs, need to keep a flag stack of seen ie_coding entries. This array has
 | 
			
		||||
	 * to have at least the nr of entries that the ie_coding array has. Let's allow up to this many ie_coding
 | 
			
		||||
	 * entries to avoid dynamic allocation. Seems like enough. */
 | 
			
		||||
	bool seen_ie_coding_entries[4096] = {};
 | 
			
		||||
	bool *seen_p;
 | 
			
		||||
#define CHECK_SEEN(IEC) do { \
 | 
			
		||||
			unsigned int ie_coding_idx = (IEC) - ie_coding; \
 | 
			
		||||
			if (ie_coding_idx >= ARRAY_SIZE(seen_ie_coding_entries)) \
 | 
			
		||||
				RETURN_ERROR(-ENOTSUP, gtlv->ti, \
 | 
			
		||||
					     "Too many IE definitions for decoding an unordered TLV structure"); \
 | 
			
		||||
			seen_p = &seen_ie_coding_entries[ie_coding_idx]; \
 | 
			
		||||
		} while (0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	osmo_gtlv_load_start(gtlv);
 | 
			
		||||
 | 
			
		||||
	/* IEs are allowed to come in any order.  So traverse the TLV structure once, and find an IE parser for each (if
 | 
			
		||||
	 * any). */
 | 
			
		||||
	for (;;) {
 | 
			
		||||
		int rc;
 | 
			
		||||
		bool *presence_flag_p;
 | 
			
		||||
		unsigned int memb_next_array_idx;
 | 
			
		||||
		unsigned int memb_ofs;
 | 
			
		||||
		unsigned int ie_max_allowed_count;
 | 
			
		||||
 | 
			
		||||
		rc = osmo_gtlv_load_next(gtlv);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			RETURN_ERROR(rc, gtlv->ti, "Decoding IEs failed on or after this tag");
 | 
			
		||||
		if (!gtlv->val) {
 | 
			
		||||
			/* End of the TLV structure */
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* ie_max_allowed_count counts how often the same IEI may appear in a message until all struct members
 | 
			
		||||
		 * that can store them are filled up. */
 | 
			
		||||
		ie_max_allowed_count = 0;
 | 
			
		||||
 | 
			
		||||
		do {
 | 
			
		||||
			/* Find the IE coding for this tag */
 | 
			
		||||
			for (iec = ie_coding;
 | 
			
		||||
			     !osmo_gtlv_coding_end(iec) && osmo_gtlv_tag_inst_cmp(&iec->ti, >lv->ti);
 | 
			
		||||
			     iec++);
 | 
			
		||||
			/* No such IE coding found. */
 | 
			
		||||
			if (osmo_gtlv_coding_end(iec))
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			/* Keep track how often this tag can occur */
 | 
			
		||||
			ie_max_allowed_count += iec->has_count ? iec->count_max : 1;
 | 
			
		||||
 | 
			
		||||
			/* Was this iec instance already decoded? Then skip to the next one, if any. */
 | 
			
		||||
			presence_flag_p = iec->has_presence_flag ? MEMB(obj, iec->presence_flag_ofs) : NULL;
 | 
			
		||||
			multi_count_p = iec->has_count ? MEMB(obj, iec->count_ofs) : NULL;
 | 
			
		||||
			if ((presence_flag_p && *presence_flag_p)
 | 
			
		||||
			    || (multi_count_p && *multi_count_p >= iec->count_max))
 | 
			
		||||
				continue;
 | 
			
		||||
			/* For IEs with a presence flag or a multi count, the decoded struct provides the information
 | 
			
		||||
			 * whether the IE has already been decoded. Do the same for mandatory IEs, using local state in
 | 
			
		||||
			 * seen_ie_coding_entries[]. */
 | 
			
		||||
			CHECK_SEEN(iec);
 | 
			
		||||
			if (*seen_p)
 | 
			
		||||
				continue;
 | 
			
		||||
		} while (0);
 | 
			
		||||
		if (osmo_gtlv_coding_end(iec)) {
 | 
			
		||||
			if (ie_max_allowed_count) {
 | 
			
		||||
				/* There have been IE definitions for this IEI, but all slots to decode it are already
 | 
			
		||||
				 * filled. */
 | 
			
		||||
				RETURN_ERROR(-ENOTSUP, gtlv->ti, "Only %u instances of this IE are supported per message",
 | 
			
		||||
					     ie_max_allowed_count);
 | 
			
		||||
			}
 | 
			
		||||
			/* No such IE defined in ie_coding, just skip the TLV. */
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* If this is a repeated IE, decode into the correct array index memb[idx],
 | 
			
		||||
		 * next idx == (*multi_count_p). We've already guaranteed above that *multi_count_p < count_max. */
 | 
			
		||||
		memb_next_array_idx = multi_count_p ? *multi_count_p : 0;
 | 
			
		||||
		memb_ofs = iec->memb_ofs + memb_next_array_idx * iec->memb_array_pitch;
 | 
			
		||||
 | 
			
		||||
		/* Decode IE value part */
 | 
			
		||||
		if (iec->nested_ies) {
 | 
			
		||||
			/* A nested IE: the value part of this TLV is in turn a TLV structure. Decode the inner
 | 
			
		||||
			 * IEs. */
 | 
			
		||||
			struct osmo_gtlv_load inner_tlv = {
 | 
			
		||||
				.cfg = iec->nested_ies_cfg ? : gtlv->cfg,
 | 
			
		||||
				.src = {
 | 
			
		||||
					.data = gtlv->val,
 | 
			
		||||
					.len = gtlv->len,
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			bool ordered;
 | 
			
		||||
			switch (iec->nested_ies_ordered) {
 | 
			
		||||
			case OSMO_GTLV_NESTED_IES_ORDERED:
 | 
			
		||||
				ordered = true;
 | 
			
		||||
				break;
 | 
			
		||||
			case OSMO_GTLV_NESTED_IES_ORDERING_SAME:
 | 
			
		||||
			case OSMO_GTLV_NESTED_IES_UNORDERED:
 | 
			
		||||
				ordered = false;
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				OSMO_ASSERT(0);
 | 
			
		||||
			}
 | 
			
		||||
			rc = osmo_gtlvs_decode(decoded_struct, obj_ofs + memb_ofs, &inner_tlv, ordered, iec->nested_ies,
 | 
			
		||||
					      err_cb, err_cb_data, iei_strs);
 | 
			
		||||
			if (rc)
 | 
			
		||||
				RETURN_ERROR(rc, gtlv->ti, "Error while decoding TLV structure nested inside this IE");
 | 
			
		||||
		} else {
 | 
			
		||||
			/* Normal IE, decode the specific IE data. */
 | 
			
		||||
			if (!iec->dec_func)
 | 
			
		||||
				RETURN_ERROR(-EIO, gtlv->ti, "IE definition lacks a dec_func()");
 | 
			
		||||
			rc = iec->dec_func(decoded_struct, MEMB(obj, memb_ofs), gtlv);
 | 
			
		||||
			if (rc)
 | 
			
		||||
				RETURN_ERROR(rc, gtlv->ti, "Error while decoding this IE");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (multi_count_p) {
 | 
			
		||||
			/* A repeated IE, record that we've added one entry. This increments the foo_count value in the
 | 
			
		||||
			 * decoded osmo_gtlv_msg.ies.*.
 | 
			
		||||
			 * For example, multi_count_p points at osmo_gtlv_msg_session_est_req.create_pdr_count,
 | 
			
		||||
			 * and memb_ofs points at osmo_gtlv_msg_session_est_req.create_pdr. */
 | 
			
		||||
			(*multi_count_p)++;
 | 
			
		||||
		}
 | 
			
		||||
		if (presence_flag_p) {
 | 
			
		||||
			*presence_flag_p = true;
 | 
			
		||||
		}
 | 
			
		||||
		CHECK_SEEN(iec);
 | 
			
		||||
		*seen_p = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check presence of mandatory IEs */
 | 
			
		||||
	for (iec = ie_coding; !osmo_gtlv_coding_end(iec); iec++) {
 | 
			
		||||
		if (iec->has_presence_flag)
 | 
			
		||||
			continue;
 | 
			
		||||
		multi_count_p = iec->has_count ? MEMB(obj, iec->count_ofs) : NULL;
 | 
			
		||||
		if (multi_count_p) {
 | 
			
		||||
			if (*multi_count_p < iec->count_mandatory)
 | 
			
		||||
				RETURN_ERROR(-EINVAL, iec->ti, "%u instances of this IE are mandatory, got %u",
 | 
			
		||||
					     iec->count_mandatory, *multi_count_p);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		/* Neither an optional nor a multi member, hence it must be mandatory. */
 | 
			
		||||
		CHECK_SEEN(iec);
 | 
			
		||||
		if (!*seen_p)
 | 
			
		||||
			RETURN_ERROR(-EINVAL, iec->ti, "Missing mandatory IE");
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode a TLV structure from raw data to a decoded struct, for ordered TLV IEs.
 | 
			
		||||
 * How to decode IE values and where to place them in the decoded struct, is defined by ie_coding, an array terminated
 | 
			
		||||
 * by a '{}' entry.
 | 
			
		||||
 * The IEs in the TLV structure must appear in the same order as they are defined in ie_coding.
 | 
			
		||||
 * cause the last read TLV to overwrite all previous decodings, all into the first occurrence in ie_coding.
 | 
			
		||||
 * \param[out] decoded_struct  Pointer to the struct to write parsed IE data to.
 | 
			
		||||
 * \param[in] obj_ofs  Pass as zero. Used for nested IEs: offset added to decoded_struct to get to a sub-struct.
 | 
			
		||||
 * \param[in] gtlv  TLV data to parse, as given in gtlv->msg.*. Must be ready for osmo_gtlv_load_start().
 | 
			
		||||
 * \param[in] ie_coding  A list of permitted/expected IEI tags and instructions for decoding.
 | 
			
		||||
 * \param[in] err_cb  Function to call to report an error message, or NULL.
 | 
			
		||||
 * \param[in] err_cb_data  Caller supplied context to pass to the err_cb as 'data' argument.
 | 
			
		||||
 * \param[in] iei_strs  value_string array to give IEI names in error messages passed to err_cb(), or NULL.
 | 
			
		||||
 * \return 0 on success, negative on error.
 | 
			
		||||
 */
 | 
			
		||||
static int osmo_gtlvs_decode_ordered(void *decoded_struct, unsigned int obj_ofs, struct osmo_gtlv_load *gtlv,
 | 
			
		||||
				    const struct osmo_gtlv_coding *ie_coding,
 | 
			
		||||
				    osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)
 | 
			
		||||
{
 | 
			
		||||
	void *obj = MEMB(decoded_struct, obj_ofs);
 | 
			
		||||
 | 
			
		||||
	osmo_gtlv_load_start(gtlv);
 | 
			
		||||
 | 
			
		||||
	for (; !osmo_gtlv_coding_end(ie_coding); ie_coding++) {
 | 
			
		||||
		int rc;
 | 
			
		||||
		bool *presence_flag = ie_coding->has_presence_flag ? MEMB(obj, ie_coding->presence_flag_ofs) : NULL;
 | 
			
		||||
		unsigned int *multi_count = ie_coding->has_count ? MEMB(obj, ie_coding->count_ofs) : NULL;
 | 
			
		||||
		struct osmo_gtlv_tag_inst peek_ti;
 | 
			
		||||
 | 
			
		||||
		rc = osmo_gtlv_load_next_by_tag_inst(gtlv, &ie_coding->ti);
 | 
			
		||||
		switch (rc) {
 | 
			
		||||
		case 0:
 | 
			
		||||
			break;
 | 
			
		||||
		case -ENOENT:
 | 
			
		||||
			if (!presence_flag && (!multi_count || *multi_count < ie_coding->count_mandatory))
 | 
			
		||||
				RETURN_ERROR(rc, ie_coding->ti, "Missing mandatory IE");
 | 
			
		||||
			if (presence_flag)
 | 
			
		||||
				*presence_flag = false;
 | 
			
		||||
			continue;
 | 
			
		||||
		default:
 | 
			
		||||
			RETURN_ERROR(rc, ie_coding->ti, "Error in TLV structure");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (;;) {
 | 
			
		||||
			/* If this is a repeated IE, decode into the correct array index memb[idx],
 | 
			
		||||
			 * next idx == (*multi_count) */
 | 
			
		||||
			unsigned int memb_next_array_idx = multi_count ? *multi_count : 0;
 | 
			
		||||
			unsigned int memb_ofs = ie_coding->memb_ofs + memb_next_array_idx * ie_coding->memb_array_pitch;
 | 
			
		||||
 | 
			
		||||
			if (multi_count && memb_next_array_idx >= ie_coding->count_max)
 | 
			
		||||
				RETURN_ERROR(-ENOTSUP, ie_coding->ti, "Only %u instances of this IE are supported per message",
 | 
			
		||||
					     ie_coding->count_max);
 | 
			
		||||
 | 
			
		||||
			/* Decode IE value part */
 | 
			
		||||
			if (ie_coding->nested_ies) {
 | 
			
		||||
				/* A nested IE: the value part of this TLV is in turn a TLV structure. Decode the inner
 | 
			
		||||
				 * IEs. */
 | 
			
		||||
				struct osmo_gtlv_load inner_tlv = {
 | 
			
		||||
					.cfg = ie_coding->nested_ies_cfg ? : gtlv->cfg,
 | 
			
		||||
					.src = {
 | 
			
		||||
						.data = gtlv->val,
 | 
			
		||||
						.len = gtlv->len,
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
				bool ordered;
 | 
			
		||||
				switch (ie_coding->nested_ies_ordered) {
 | 
			
		||||
				case OSMO_GTLV_NESTED_IES_ORDERING_SAME:
 | 
			
		||||
				case OSMO_GTLV_NESTED_IES_ORDERED:
 | 
			
		||||
					ordered = true;
 | 
			
		||||
					break;
 | 
			
		||||
				case OSMO_GTLV_NESTED_IES_UNORDERED:
 | 
			
		||||
					ordered = false;
 | 
			
		||||
					break;
 | 
			
		||||
				default:
 | 
			
		||||
					OSMO_ASSERT(0);
 | 
			
		||||
				}
 | 
			
		||||
				rc = osmo_gtlvs_decode(decoded_struct, obj_ofs + memb_ofs, &inner_tlv, ordered,
 | 
			
		||||
						      ie_coding->nested_ies, err_cb, err_cb_data, iei_strs);
 | 
			
		||||
				if (rc)
 | 
			
		||||
					RETURN_ERROR(rc, ie_coding->ti,
 | 
			
		||||
						     "Error while decoding TLV structure nested inside this IE");
 | 
			
		||||
			} else {
 | 
			
		||||
				/* Normal IE, decode the specific IE data. */
 | 
			
		||||
				if (!ie_coding->dec_func)
 | 
			
		||||
					RETURN_ERROR(-EIO, ie_coding->ti, "IE definition lacks a dec_func()");
 | 
			
		||||
				rc = ie_coding->dec_func(decoded_struct, MEMB(obj, memb_ofs), gtlv);
 | 
			
		||||
				if (rc)
 | 
			
		||||
					RETURN_ERROR(rc, ie_coding->ti, "Error while decoding this IE");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (presence_flag)
 | 
			
		||||
				*presence_flag = true;
 | 
			
		||||
 | 
			
		||||
			if (!multi_count) {
 | 
			
		||||
				/* Not a repeated IE. */
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* A repeated IE, record that we've added one entry. This increments the foo_count value in the
 | 
			
		||||
			 * decoded osmo_pfcp_msg.ies.*.
 | 
			
		||||
			 * For example, multi_count points at osmo_pfcp_msg_session_est_req.create_pdr_count,
 | 
			
		||||
			 * and memb_ofs points at osmo_pfcp_msg_session_est_req.create_pdr. */
 | 
			
		||||
			(*multi_count)++;
 | 
			
		||||
 | 
			
		||||
			/* Does another one of these IEs follow? */
 | 
			
		||||
			if (osmo_gtlv_load_peek_tag(gtlv, &peek_ti)
 | 
			
		||||
			    || osmo_gtlv_tag_inst_cmp(&peek_ti, >lv->ti)) {
 | 
			
		||||
				/* Next tag is a different IE, end the repetition. */
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* continue, parsing the next repetition of this tag. */
 | 
			
		||||
			rc = osmo_gtlv_load_next(gtlv);
 | 
			
		||||
			if (rc)
 | 
			
		||||
				return rc;
 | 
			
		||||
		}
 | 
			
		||||
		/* continue parsing the next tag. */
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Decode an entire TLV message from raw data to decoded struct.
 | 
			
		||||
 * How to decode IE values and where to put them in the decoded struct is defined by ie_coding, an array terminated by
 | 
			
		||||
 * a '{}' entry.
 | 
			
		||||
 * \param[out] decoded_struct  Pointer to the struct to write parsed IE data to.
 | 
			
		||||
 * \param[in] obj_ofs  Pass as zero. Used for nested IEs: offset added to decoded_struct to get to a sub-struct.
 | 
			
		||||
 * \param[in] gtlv  TLV data to parse, as given in gtlv->msg.*. Must be ready for osmo_gtlv_load_start().
 | 
			
		||||
 * \param[in] ie_coding  A list of permitted/expected IEI tags and instructions for decoding.
 | 
			
		||||
 * \param[in] err_cb  Function to call to report an error message, or NULL.
 | 
			
		||||
 * \param[in] err_cb_data  Caller supplied context to pass to the err_cb as 'data' argument.
 | 
			
		||||
 * \param[in] iei_strs  value_string array to give IEI names in error messages passed to err_cb(), or NULL.
 | 
			
		||||
 * \return 0 on success, negative on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlvs_decode(void *decoded_struct, unsigned int obj_ofs, struct osmo_gtlv_load *gtlv, bool tlv_ordered,
 | 
			
		||||
		     const struct osmo_gtlv_coding *ie_coding,
 | 
			
		||||
		     osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)
 | 
			
		||||
{
 | 
			
		||||
	if (!ie_coding)
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	if (tlv_ordered)
 | 
			
		||||
		return osmo_gtlvs_decode_ordered(decoded_struct, obj_ofs, gtlv, ie_coding, err_cb, err_cb_data, iei_strs);
 | 
			
		||||
	else
 | 
			
		||||
		return osmo_gtlvs_decode_unordered(decoded_struct, obj_ofs, gtlv, ie_coding, err_cb, err_cb_data,
 | 
			
		||||
						  iei_strs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Encode a TLV structure from decoded struct to raw data.
 | 
			
		||||
 * How to encode IE values and where to read them in the decoded struct is defined by ie_coding, an array terminated by
 | 
			
		||||
 * a '{}' entry.
 | 
			
		||||
 * The IEs will be encoded in the order they appear in ie_coding.
 | 
			
		||||
 * \param[out] gtlv  Write data using this TLV definition to gtlv->dst.
 | 
			
		||||
 * \param[in] decoded_struct  C struct data to encode.
 | 
			
		||||
 * \param[in] obj_ofs  Nesting offset, pass as 0.
 | 
			
		||||
 * \param[in] ie_coding  A {} terminated list of IEI tags to encode (if present) and instructions for encoding.
 | 
			
		||||
 * \param[in] err_cb  Function to call to report an error message, or NULL.
 | 
			
		||||
 * \param[in] err_cb_data  Caller supplied context to pass to the err_cb as 'data' argument.
 | 
			
		||||
 * \param[in] iei_strs  value_string array to give IEI names in error messages passed to err_cb(), or NULL.
 | 
			
		||||
 * \return 0 on success, negative on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlvs_encode(struct osmo_gtlv_put *gtlv, const void *decoded_struct, unsigned int obj_ofs,
 | 
			
		||||
		     const struct osmo_gtlv_coding *ie_coding,
 | 
			
		||||
		     osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)
 | 
			
		||||
{
 | 
			
		||||
	void *obj = MEMB(decoded_struct, obj_ofs);
 | 
			
		||||
 | 
			
		||||
	if (!ie_coding)
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
 | 
			
		||||
	for (; !osmo_gtlv_coding_end(ie_coding); ie_coding++) {
 | 
			
		||||
		int rc;
 | 
			
		||||
		bool *presence_flag_p = ie_coding->has_presence_flag ? MEMB(obj, ie_coding->presence_flag_ofs) : NULL;
 | 
			
		||||
		unsigned int *multi_count_p = ie_coding->has_count ? MEMB(obj, ie_coding->count_ofs) : NULL;
 | 
			
		||||
		unsigned int n;
 | 
			
		||||
		unsigned int i;
 | 
			
		||||
 | 
			
		||||
		if (presence_flag_p && !*presence_flag_p)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (multi_count_p) {
 | 
			
		||||
			n = *multi_count_p;
 | 
			
		||||
			if (!ie_coding->memb_array_pitch)
 | 
			
		||||
				RETURN_ERROR(-EFAULT, ie_coding->ti,
 | 
			
		||||
					     "Error in protocol definition: The ie_coding lacks a memb_array_pitch"
 | 
			
		||||
					     " value, cannot be used as multi-IE\n");
 | 
			
		||||
		} else {
 | 
			
		||||
			n = 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (i = 0; i < n; i++) {
 | 
			
		||||
			unsigned int memb_ofs;
 | 
			
		||||
 | 
			
		||||
			osmo_gtlv_put_tli(gtlv, &ie_coding->ti, 0);
 | 
			
		||||
 | 
			
		||||
			/* If this is a repeated IE, encode from the correct array index */
 | 
			
		||||
			if (multi_count_p && i >= ie_coding->count_max)
 | 
			
		||||
				RETURN_ERROR(-ENOTSUP, ie_coding->ti,
 | 
			
		||||
					     "Only %u instances of this IE are supported per message", ie_coding->count_max);
 | 
			
		||||
			memb_ofs = ie_coding->memb_ofs + i * ie_coding->memb_array_pitch;
 | 
			
		||||
 | 
			
		||||
			if (ie_coding->nested_ies) {
 | 
			
		||||
				struct osmo_gtlv_put nested_tlv = {
 | 
			
		||||
					.cfg = ie_coding->nested_ies_cfg ? : gtlv->cfg,
 | 
			
		||||
					.dst = gtlv->dst,
 | 
			
		||||
				};
 | 
			
		||||
				rc = osmo_gtlvs_encode(&nested_tlv, decoded_struct, obj_ofs + memb_ofs,
 | 
			
		||||
						      ie_coding->nested_ies, err_cb, err_cb_data, iei_strs);
 | 
			
		||||
				if (rc)
 | 
			
		||||
					RETURN_ERROR(rc, ie_coding->ti,
 | 
			
		||||
						     "Error while encoding TLV structure nested inside this IE");
 | 
			
		||||
			} else {
 | 
			
		||||
				rc = ie_coding->enc_func(gtlv, decoded_struct, MEMB(obj, memb_ofs));
 | 
			
		||||
				if (rc)
 | 
			
		||||
					RETURN_ERROR(rc, ie_coding->ti, "Error while encoding this IE");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			osmo_gtlv_put_update_tl(gtlv);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Compose a human readable string describing a decoded struct.
 | 
			
		||||
 * How to encode IE values and where to read them in the decoded struct is defined by ie_coding, an array terminated by
 | 
			
		||||
 * a '{}' entry.
 | 
			
		||||
 * The IEs will be encoded in the order they appear in ie_coding.
 | 
			
		||||
 * \param[out] buf  Return the string in this buffer.
 | 
			
		||||
 * \param[in] buflen  Size of buf.
 | 
			
		||||
 * \param[in] decoded_struct  C struct data to encode.
 | 
			
		||||
 * \param[in] obj_ofs  Nesting offset, pass as 0.
 | 
			
		||||
 * \param[in] ie_coding  A {} terminated list of IEI tags to encode (if present) and instructions for encoding.
 | 
			
		||||
 * \param[in] iei_strs  value_string array to give IEI names in tag headers, or NULL.
 | 
			
		||||
 * \return number of characters that would be written if the buffer is large enough, like snprintf().
 | 
			
		||||
 */
 | 
			
		||||
int osmo_gtlvs_encode_to_str_buf(char *buf, size_t buflen, const void *decoded_struct, unsigned int obj_ofs,
 | 
			
		||||
				const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
 | 
			
		||||
	void *obj = MEMB(decoded_struct, obj_ofs);
 | 
			
		||||
 | 
			
		||||
	if (!ie_coding)
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
 | 
			
		||||
	for (; !osmo_gtlv_coding_end(ie_coding); ie_coding++) {
 | 
			
		||||
		bool *presence_flag_p = ie_coding->has_presence_flag ? MEMB(obj, ie_coding->presence_flag_ofs) : NULL;
 | 
			
		||||
		unsigned int *multi_count_p = ie_coding->has_count ? MEMB(obj, ie_coding->count_ofs) : NULL;
 | 
			
		||||
		unsigned int n;
 | 
			
		||||
		unsigned int i;
 | 
			
		||||
 | 
			
		||||
		if (presence_flag_p && !*presence_flag_p)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (multi_count_p) {
 | 
			
		||||
			n = *multi_count_p;
 | 
			
		||||
		} else {
 | 
			
		||||
			n = 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!n)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " '%s'=", get_value_string(iei_strs, ie_coding->ti.tag));
 | 
			
		||||
		if (multi_count_p)
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, "{ ");
 | 
			
		||||
 | 
			
		||||
		for (i = 0; i < n; i++) {
 | 
			
		||||
			unsigned int memb_ofs;
 | 
			
		||||
 | 
			
		||||
			/* If this is a repeated IE, encode from the correct array index */
 | 
			
		||||
			if (multi_count_p && i >= ie_coding->count_max)
 | 
			
		||||
				return -ENOTSUP;
 | 
			
		||||
			if (i > 0)
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, ", ");
 | 
			
		||||
 | 
			
		||||
			memb_ofs = ie_coding->memb_ofs + i * ie_coding->memb_array_pitch;
 | 
			
		||||
 | 
			
		||||
			if (ie_coding->nested_ies) {
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, "{");
 | 
			
		||||
				OSMO_STRBUF_APPEND(sb, osmo_gtlvs_encode_to_str_buf, decoded_struct, obj_ofs + memb_ofs,
 | 
			
		||||
						   ie_coding->nested_ies, iei_strs);
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, " }");
 | 
			
		||||
			} else {
 | 
			
		||||
				if (ie_coding->enc_to_str_func)
 | 
			
		||||
					OSMO_STRBUF_APPEND(sb, ie_coding->enc_to_str_func, MEMB(obj, memb_ofs));
 | 
			
		||||
				else
 | 
			
		||||
					OSMO_STRBUF_PRINTF(sb, "(enc_to_str_func==NULL)");
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (multi_count_p)
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, " }");
 | 
			
		||||
	}
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Compose a human readable string describing a decoded struct.
 | 
			
		||||
 * Like osmo_gtlvs_encode_to_str_buf() but returns a talloc allocated string.
 | 
			
		||||
 * \param[in] ctx  talloc context to allocate from, e.g. OTC_SELECT.
 | 
			
		||||
 * \param[in] decoded_struct  C struct data to encode.
 | 
			
		||||
 * \param[in] obj_ofs  Nesting offset, pass as 0.
 | 
			
		||||
 * \param[in] ie_coding  A {} terminated list of IEI tags to encode (if present) and instructions for encoding.
 | 
			
		||||
 * \param[in] iei_strs  value_string array to give IEI names in tag headers, or NULL.
 | 
			
		||||
 * \return human readable string.
 | 
			
		||||
 */
 | 
			
		||||
char *osmo_gtlvs_encode_to_str_c(void *ctx, const void *decoded_struct, unsigned int obj_ofs,
 | 
			
		||||
				const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 256, "ERROR", osmo_gtlvs_encode_to_str_buf, decoded_struct, obj_ofs, ie_coding, iei_strs)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										420
									
								
								src/libosmo-gtlv/gtlv_gen.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								src/libosmo-gtlv/gtlv_gen.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,420 @@
 | 
			
		||||
/* Write h and c source files for TLV protocol definitions, based on very sparse TLV definitions.
 | 
			
		||||
 * For a usage example see tests/libosmo-gtlv/test_gtlv_gen/. */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gtlv/gtlv_gen.h>
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_cfg *g_cfg = NULL;
 | 
			
		||||
 | 
			
		||||
const struct osmo_gtlv_gen_ie osmo_gtlv_gen_ie_auto = {};
 | 
			
		||||
 | 
			
		||||
/* Helps avoid redundant definitions of the same type. */
 | 
			
		||||
struct seen_entry {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	char str[256];
 | 
			
		||||
	const void *from_def;
 | 
			
		||||
};
 | 
			
		||||
static LLIST_HEAD(seen_list);
 | 
			
		||||
 | 
			
		||||
static bool seen(const char *str, const void *from_def)
 | 
			
		||||
{
 | 
			
		||||
	struct seen_entry *s;
 | 
			
		||||
	llist_for_each_entry(s, &seen_list, entry) {
 | 
			
		||||
		if (!strcmp(s->str, str)) {
 | 
			
		||||
			if (from_def != s->from_def) {
 | 
			
		||||
				fprintf(stderr, "ERROR: %s: multiple definitions use the same name: '%s'\n",
 | 
			
		||||
					g_cfg->proto_name, str);
 | 
			
		||||
				exit(1);
 | 
			
		||||
			}
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	s = talloc_zero(NULL, struct seen_entry);
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(s->str, str);
 | 
			
		||||
	s->from_def = from_def;
 | 
			
		||||
	llist_add(&s->entry, &seen_list);
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
static void clear_seen()
 | 
			
		||||
{
 | 
			
		||||
	struct seen_entry *s;
 | 
			
		||||
	while ((s = llist_first_entry_or_null(&seen_list, struct seen_entry, entry))) {
 | 
			
		||||
		llist_del(&s->entry);
 | 
			
		||||
		talloc_free(s);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Return "struct foo_ie_bar" from g_cfg->decoded_type_prefix and ie. */
 | 
			
		||||
static inline const char *decoded_type(const struct osmo_gtlv_gen_ie_o *ie_o)
 | 
			
		||||
{
 | 
			
		||||
	static char b[255];
 | 
			
		||||
	const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
 | 
			
		||||
	const char *tag_name;
 | 
			
		||||
	if (ie && ie->decoded_type)
 | 
			
		||||
		return ie->decoded_type;
 | 
			
		||||
	/* "struct foo_ie_" + "bar" = struct foo_ie_bar*/
 | 
			
		||||
	tag_name = ie ? ie->tag_name : NULL;
 | 
			
		||||
	snprintf(b, sizeof(b), "%s%s", g_cfg->decoded_type_prefix, tag_name ? : ie_o->name);
 | 
			
		||||
	return b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* --- .h file --- */
 | 
			
		||||
 | 
			
		||||
/* Write a listing of struct members like
 | 
			
		||||
 *     bool foo_present;
 | 
			
		||||
 *     int foo;
 | 
			
		||||
 *     struct myproto_ie_bar bar;
 | 
			
		||||
 *     struct abc abc[10];
 | 
			
		||||
 *     int abc_count;
 | 
			
		||||
 */
 | 
			
		||||
static void write_ie_members(const struct osmo_gtlv_gen_ie_o ies[])
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_gtlv_gen_ie_o *ie_o;
 | 
			
		||||
	for (ie_o = ies; ie_o->ie; ie_o++) {
 | 
			
		||||
		if (ie_o->optional)
 | 
			
		||||
			printf("\tbool %s_present;\n", ie_o->name);
 | 
			
		||||
		printf("\t%s %s", decoded_type(ie_o), ie_o->name);
 | 
			
		||||
		if (ie_o->multi) {
 | 
			
		||||
			printf("[%u];\n", ie_o->multi);
 | 
			
		||||
			printf("\tunsigned int %s_count", ie_o->name);
 | 
			
		||||
		}
 | 
			
		||||
		printf(";\n");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Traverse nesting levels in the message definitions and generate the structs for all as needed. */
 | 
			
		||||
static void write_ie_auto_structs(const struct osmo_gtlv_gen_ie_o ies[])
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_gtlv_gen_ie_o *ie_o;
 | 
			
		||||
	if (!ies)
 | 
			
		||||
		return;
 | 
			
		||||
	for (ie_o = ies; ie_o->ie; ie_o++) {
 | 
			
		||||
		const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
 | 
			
		||||
		if (!ie || !ie->nested_ies)
 | 
			
		||||
			continue;
 | 
			
		||||
		/* Recurse to write inner layers first, so that they can be referenced in outer layers. */
 | 
			
		||||
		write_ie_auto_structs(ie->nested_ies);
 | 
			
		||||
 | 
			
		||||
		/* Various IE definitions can use the same underlying type. Only generate each type once. */
 | 
			
		||||
		if (seen(decoded_type(ie_o), NULL))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Print:
 | 
			
		||||
		 *
 | 
			
		||||
		 * \* spec ref *\
 | 
			
		||||
		 * struct myproto_ie_goo {
 | 
			
		||||
		 *     bool foo_present;
 | 
			
		||||
		 *     int foo;
 | 
			
		||||
		 *     struct myproto_ie_bar bar;
 | 
			
		||||
		 *     struct abc abc[10];
 | 
			
		||||
		 *     int abc_count;
 | 
			
		||||
		 * };
 | 
			
		||||
		 */
 | 
			
		||||
		printf("\n");
 | 
			
		||||
		if (ie->spec_ref)
 | 
			
		||||
			printf("/* %s%s */\n", g_cfg->spec_ref_prefix, ie->spec_ref);
 | 
			
		||||
		printf("%s {\n", decoded_type(ie_o));
 | 
			
		||||
		write_ie_members(ie->nested_ies);
 | 
			
		||||
		printf("};\n");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write all auto-generated structs, starting with the outer message definitions and nesting into all contained IE
 | 
			
		||||
 * definitions. */
 | 
			
		||||
static void write_auto_structs()
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_gtlv_gen_msg *gen_msg;
 | 
			
		||||
	clear_seen();
 | 
			
		||||
	for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
 | 
			
		||||
		write_ie_auto_structs(gen_msg->ies);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write the struct definitions for each message, i.e. for each entry in the outer PDU's message union, as well as the
 | 
			
		||||
 * union itself.
 | 
			
		||||
 *
 | 
			
		||||
 * struct myproto_msg_foo {
 | 
			
		||||
 *    ...
 | 
			
		||||
 * }:
 | 
			
		||||
 * struct myproto_msg_goo {
 | 
			
		||||
 *    ...
 | 
			
		||||
 * };
 | 
			
		||||
 * union myproto_ies {
 | 
			
		||||
 *        myproto_msg_foo foo;
 | 
			
		||||
 *        myproto_msg_goo goo;
 | 
			
		||||
 * };
 | 
			
		||||
 */
 | 
			
		||||
static void write_msg_union()
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_gtlv_gen_msg *gen_msg;
 | 
			
		||||
	for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
 | 
			
		||||
		/* "struct foo_msg" + "_%s" { *
 | 
			
		||||
		 * struct foo_msg_goo_request { ... }; */
 | 
			
		||||
		printf("\nstruct %s_msg_%s {\n",
 | 
			
		||||
		       g_cfg->proto_name,
 | 
			
		||||
		       gen_msg->name);
 | 
			
		||||
		write_ie_members(gen_msg->ies);
 | 
			
		||||
		printf("};\n");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printf("\nunion %s_ies {\n", g_cfg->proto_name);
 | 
			
		||||
	for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
 | 
			
		||||
		printf("\tstruct %s_msg_%s %s;\n", g_cfg->proto_name,
 | 
			
		||||
		       gen_msg->name, gen_msg->name);
 | 
			
		||||
	}
 | 
			
		||||
	printf("};\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write the C header, myproto_ies_auto.h */
 | 
			
		||||
static void write_h()
 | 
			
		||||
{
 | 
			
		||||
	printf("/* THIS FILE IS GENERATED FROM %s */\n", __FILE__);
 | 
			
		||||
	printf("#include <stdint.h>\n");
 | 
			
		||||
	printf("#include <osmocom/gtlv/gtlv_dec_enc.h>\n");
 | 
			
		||||
	if (g_cfg->h_header)
 | 
			
		||||
		printf("\n%s\n", g_cfg->h_header);
 | 
			
		||||
	write_auto_structs();
 | 
			
		||||
	write_msg_union();
 | 
			
		||||
	printf("\nconst struct osmo_gtlv_coding *%s_get_msg_coding(%s message_type);\n",
 | 
			
		||||
	       g_cfg->proto_name, g_cfg->message_type_enum ? : "int");
 | 
			
		||||
	printf("\n"
 | 
			
		||||
		"int %s_ies_decode(union %s_ies *dst, struct osmo_gtlv_load *gtlv, bool tlv_ordered,\n"
 | 
			
		||||
		"	%s message_type, osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs);\n",
 | 
			
		||||
		g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? : "int");
 | 
			
		||||
	printf("\n"
 | 
			
		||||
		"int %s_ies_encode(struct osmo_gtlv_put *gtlv, const union %s_ies *src,\n"
 | 
			
		||||
		"	%s message_type, osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs);\n",
 | 
			
		||||
		g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? : "int");
 | 
			
		||||
	printf("\n"
 | 
			
		||||
		"int %s_ies_encode_to_str(char *buf, size_t buflen, const union %s_ies *src,\n"
 | 
			
		||||
		"	%s message_type, const struct value_string *iei_strs);\n",
 | 
			
		||||
		g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? : "int");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* --- .c file --- */
 | 
			
		||||
 | 
			
		||||
/* Write a listing of:
 | 
			
		||||
 * extern int myproto_dec_foo(...);
 | 
			
		||||
 * extern int myproto_enc_foo(...);
 | 
			
		||||
 */
 | 
			
		||||
static void write_extern_dec_enc(const struct osmo_gtlv_gen_ie_o *ies)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_gtlv_gen_ie_o *ie_o;
 | 
			
		||||
	for (ie_o = ies; ie_o->ie; ie_o++) {
 | 
			
		||||
		const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
 | 
			
		||||
		const char *dec_enc = ie_o->name;
 | 
			
		||||
		if (ie)
 | 
			
		||||
			dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie_o->name);
 | 
			
		||||
		if (ie && ie->nested_ies) {
 | 
			
		||||
			write_extern_dec_enc(ie->nested_ies);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (seen(dec_enc, NULL))
 | 
			
		||||
			continue;
 | 
			
		||||
		printf("extern int %s_dec_%s(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv);\n",
 | 
			
		||||
		       g_cfg->proto_name, dec_enc);
 | 
			
		||||
		printf("extern int %s_enc_%s(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from);\n",
 | 
			
		||||
		       g_cfg->proto_name, dec_enc);
 | 
			
		||||
		if (g_cfg->add_enc_to_str)
 | 
			
		||||
			printf("extern int %s_enc_to_str_%s(char *buf, size_t buflen, const void *encode_from);\n",
 | 
			
		||||
			       g_cfg->proto_name, dec_enc);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* For a nested IE, write the struct osmo_gtlv_coding array of the inner IEs.
 | 
			
		||||
 * { { MYPROTO_IEI_BAR },
 | 
			
		||||
 *   .memb_ofs = offsetof(struct myproto_foo, bar),
 | 
			
		||||
 *   .dec_func = myproto_dec_bar,
 | 
			
		||||
 *   .enc_func = myproto_enc_bar,
 | 
			
		||||
 * },
 | 
			
		||||
 */
 | 
			
		||||
static void write_ies_array(const char *indent, const struct osmo_gtlv_gen_ie_o *ies, const char *obj_type, const char *substruct)
 | 
			
		||||
{
 | 
			
		||||
#define printi(FMT, ARGS...) printf("%s" FMT, indent, ##ARGS)
 | 
			
		||||
 | 
			
		||||
	const struct osmo_gtlv_gen_ie_o *ie_o;
 | 
			
		||||
	for (ie_o = ies; ie_o->ie; ie_o++) {
 | 
			
		||||
		const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
 | 
			
		||||
		const char *tag_name = (ie && ie->tag_name) ? ie->tag_name : ie_o->name;
 | 
			
		||||
		printi("{ { %s%s", g_cfg->tag_prefix, osmo_str_toupper(tag_name));
 | 
			
		||||
		if (ie_o->instance)
 | 
			
		||||
			printf(", true, %s", ie_o->instance);
 | 
			
		||||
		printf(" },\n");
 | 
			
		||||
		printi("  .memb_ofs = offsetof(%s, %s%s),\n", obj_type, substruct, ie_o->name);
 | 
			
		||||
		if (ie->nested_ies) {
 | 
			
		||||
			printi("  .nested_ies = ies_in_%s,\n", tag_name);
 | 
			
		||||
		} else {
 | 
			
		||||
			const char *dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie_o->name);
 | 
			
		||||
			printi("  .dec_func = %s_dec_%s,\n", g_cfg->proto_name, dec_enc);
 | 
			
		||||
			printi("  .enc_func = %s_enc_%s,\n", g_cfg->proto_name, dec_enc);
 | 
			
		||||
			if (g_cfg->add_enc_to_str)
 | 
			
		||||
				printi("  .enc_to_str_func = %s_enc_to_str_%s,\n", g_cfg->proto_name, dec_enc);
 | 
			
		||||
		}
 | 
			
		||||
		if (ie_o->multi) {
 | 
			
		||||
			printi("  .memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(%s, %s%s),\n",
 | 
			
		||||
			       obj_type, substruct, ie_o->name);
 | 
			
		||||
			printi("  .has_count = true, .count_max = %u,\n", ie_o->multi);
 | 
			
		||||
			printi("  .count_mandatory = %u,\n", ie_o->multi_mandatory);
 | 
			
		||||
			printi("  .count_ofs = offsetof(%s, %s%s_count),\n", obj_type, substruct, ie_o->name);
 | 
			
		||||
		}
 | 
			
		||||
		if (ie_o->optional) {
 | 
			
		||||
			printi("  .has_presence_flag = true,\n");
 | 
			
		||||
			printi("  .presence_flag_ofs = offsetof(%s, %s%s_present),\n", obj_type, substruct, ie_o->name);
 | 
			
		||||
		}
 | 
			
		||||
		printi("},\n");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* For a nested IE, write the struct osmo_gtlv_coding array of the inner IEs.
 | 
			
		||||
 * static const struct osmo_gtlv_coding ies_in_foo[] = {
 | 
			
		||||
 *         { {MYPROTO_IEI_BAR},
 | 
			
		||||
 *           .memb_ofs = offsetof(struct myproto_foo, bar),
 | 
			
		||||
 *           .dec_func = myproto_dec_bar,
 | 
			
		||||
 *           .enc_func = myproto_enc_bar,
 | 
			
		||||
 *         },
 | 
			
		||||
 *         ...
 | 
			
		||||
 * };
 | 
			
		||||
 */
 | 
			
		||||
static void write_nested_ies_array(const struct osmo_gtlv_gen_ie_o *ies)
 | 
			
		||||
{
 | 
			
		||||
	const char *indent = "\t";
 | 
			
		||||
	const struct osmo_gtlv_gen_ie_o *ie_o;
 | 
			
		||||
	for (ie_o = ies; ie_o->ie; ie_o++) {
 | 
			
		||||
		const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
 | 
			
		||||
		if (!ie || !ie->nested_ies)
 | 
			
		||||
			continue;
 | 
			
		||||
		write_nested_ies_array(ie->nested_ies);
 | 
			
		||||
 | 
			
		||||
		const char *ies_in_name = ie->tag_name ? : ie_o->name;
 | 
			
		||||
		if (seen(ies_in_name, ie))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		printf("\nstatic const struct osmo_gtlv_coding ies_in_%s[] = {\n", ies_in_name);
 | 
			
		||||
		write_ies_array(indent, ie->nested_ies, decoded_type(ie_o), "");
 | 
			
		||||
		printi("{}\n");
 | 
			
		||||
		printf("};\n");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write the bulk of the C code: on the basis of the list of messages (g_cfg->msg_defs), write all dec/enc function
 | 
			
		||||
 * declarations, all IEs arrays as well as the list of message types, first triggering to write the C code for any inner
 | 
			
		||||
 * layers. */
 | 
			
		||||
static void write_c()
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_gtlv_gen_msg *gen_msg;
 | 
			
		||||
 | 
			
		||||
	printf("/* THIS FILE IS GENERATED FROM %s */\n", __FILE__);
 | 
			
		||||
	printf("#include <stddef.h>\n");
 | 
			
		||||
	printf("#include <errno.h>\n");
 | 
			
		||||
	printf("#include <osmocom/core/utils.h>\n");
 | 
			
		||||
	printf("#include <osmocom/gtlv/gtlv.h>\n");
 | 
			
		||||
	printf("#include <osmocom/gtlv/gtlv_dec_enc.h>\n");
 | 
			
		||||
	printf("#include <osmocom/gtlv/gtlv_gen.h>\n");
 | 
			
		||||
	if (g_cfg->c_header)
 | 
			
		||||
		printf("\n%s\n", g_cfg->c_header);
 | 
			
		||||
 | 
			
		||||
	printf("\n");
 | 
			
		||||
	clear_seen();
 | 
			
		||||
	for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
 | 
			
		||||
		write_extern_dec_enc(gen_msg->ies);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clear_seen();
 | 
			
		||||
	for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
 | 
			
		||||
		write_nested_ies_array(gen_msg->ies);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
 | 
			
		||||
		char *obj_type = talloc_asprintf(NULL, "union %s_ies", g_cfg->proto_name);
 | 
			
		||||
		char *substruct = talloc_asprintf(NULL, "%s.", gen_msg->name);
 | 
			
		||||
		printf("\nstatic const struct osmo_gtlv_coding ies_in_msg_%s[] = {\n", gen_msg->name);
 | 
			
		||||
		write_ies_array("\t", gen_msg->ies, obj_type, substruct);
 | 
			
		||||
		printf("\t{}\n};\n");
 | 
			
		||||
		talloc_free(substruct);
 | 
			
		||||
		talloc_free(obj_type);
 | 
			
		||||
	}
 | 
			
		||||
	printf("\nstatic const struct osmo_gtlv_coding *msg_defs[] = {\n");
 | 
			
		||||
	for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
 | 
			
		||||
		printf("\t[%s%s] = ies_in_msg_%s,\n", g_cfg->message_type_prefix, osmo_str_toupper(gen_msg->name), gen_msg->name);
 | 
			
		||||
	}
 | 
			
		||||
	printf("};\n");
 | 
			
		||||
 | 
			
		||||
	/* print this code snippet into the .c file, because only there can we do ARRAY_SIZE(foo_msg_coding). */
 | 
			
		||||
	printf("\n"
 | 
			
		||||
		"const struct osmo_gtlv_coding *%s_get_msg_coding(%s message_type)\n"
 | 
			
		||||
		"{\n"
 | 
			
		||||
		"	if (message_type >= ARRAY_SIZE(msg_defs))\n"
 | 
			
		||||
		"		return NULL;\n"
 | 
			
		||||
		"	return msg_defs[message_type];\n"
 | 
			
		||||
		"}\n",
 | 
			
		||||
		g_cfg->proto_name, g_cfg->message_type_enum ? : "int");
 | 
			
		||||
 | 
			
		||||
	printf("\n"
 | 
			
		||||
		"int %s_ies_decode(union %s_ies *dst, struct osmo_gtlv_load *gtlv, bool tlv_ordered,\n"
 | 
			
		||||
		"	%s message_type,\n"
 | 
			
		||||
		"	osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)\n"
 | 
			
		||||
		"{\n"
 | 
			
		||||
		"	return osmo_gtlvs_decode(dst, 0, gtlv, tlv_ordered, %s_get_msg_coding(message_type), err_cb, err_cb_data, iei_strs);\n"
 | 
			
		||||
		"}\n",
 | 
			
		||||
		g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? : "int", g_cfg->proto_name);
 | 
			
		||||
	printf("\n"
 | 
			
		||||
		"int %s_ies_encode(struct osmo_gtlv_put *gtlv, const union %s_ies *src,\n"
 | 
			
		||||
		"	%s message_type, osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)\n"
 | 
			
		||||
		"{\n"
 | 
			
		||||
		"	return osmo_gtlvs_encode(gtlv, src, 0, %s_get_msg_coding(message_type), err_cb, err_cb_data, iei_strs);\n"
 | 
			
		||||
		"}\n",
 | 
			
		||||
		g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? : "int", g_cfg->proto_name);
 | 
			
		||||
	printf("\n"
 | 
			
		||||
		"int %s_ies_encode_to_str(char *buf, size_t buflen, const union %s_ies *src,\n"
 | 
			
		||||
		"	%s message_type, const struct value_string *iei_strs)\n"
 | 
			
		||||
		"{\n"
 | 
			
		||||
		"	return osmo_gtlvs_encode_to_str_buf(buf, buflen, src, 0, %s_get_msg_coding(message_type), iei_strs);\n"
 | 
			
		||||
		"}\n",
 | 
			
		||||
		g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? : "int", g_cfg->proto_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Call this from your main(). */
 | 
			
		||||
int osmo_gtlv_gen_main(const struct osmo_gtlv_gen_cfg *cfg, int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
	if (argc < 2)
 | 
			
		||||
		return 1;
 | 
			
		||||
 | 
			
		||||
	g_cfg = cfg;
 | 
			
		||||
 | 
			
		||||
	if (strcmp(argv[1], "h") == 0)
 | 
			
		||||
		write_h();
 | 
			
		||||
	else if (strcmp(argv[1], "c") == 0)
 | 
			
		||||
		write_c();
 | 
			
		||||
	else
 | 
			
		||||
		return 1;
 | 
			
		||||
 | 
			
		||||
	clear_seen();
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								src/libosmo-pfcp/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/libosmo-pfcp/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
AM_CPPFLAGS = \
 | 
			
		||||
	$(all_includes) \
 | 
			
		||||
	-I$(top_srcdir)/include \
 | 
			
		||||
	-I$(top_builddir)/include \
 | 
			
		||||
	-I$(top_builddir) \
 | 
			
		||||
	-I$(builddir) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_CFLAGS = \
 | 
			
		||||
	-Wall \
 | 
			
		||||
	$(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOVTY_CFLAGS) \
 | 
			
		||||
	$(COVERAGE_CFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_LDFLAGS = \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(LIBOSMOVTY_LIBS) \
 | 
			
		||||
	$(COVERAGE_LDFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
noinst_LIBRARIES = \
 | 
			
		||||
	libosmo-pfcp.a \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
libosmo_pfcp_a_SOURCES = \
 | 
			
		||||
	pfcp_endpoint.c \
 | 
			
		||||
	pfcp_heartbeat_fsm.c \
 | 
			
		||||
	pfcp_ies_custom.c \
 | 
			
		||||
	pfcp_msg.c \
 | 
			
		||||
	pfcp_strs.c \
 | 
			
		||||
	\
 | 
			
		||||
	pfcp_ies_auto.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
BUILT_SOURCES = \
 | 
			
		||||
	pfcp_ies_auto.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
CLEANFILES = \
 | 
			
		||||
	pfcp_ies_auto.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
pfcp_ies_auto.c: $(srcdir)/gen__pfcp_ies_auto.c \
 | 
			
		||||
		 $(top_srcdir)/src/libosmo-gtlv/gtlv_gen.c \
 | 
			
		||||
		 $(top_srcdir)/include/osmocom/gtlv/gtlv_gen.h
 | 
			
		||||
	$(MAKE) -C $(top_builddir)/src/libosmo-gtlv
 | 
			
		||||
	$(MAKE) gen__pfcp_ies_auto
 | 
			
		||||
	$(builddir)/gen__pfcp_ies_auto c > $(builddir)/pfcp_ies_auto.c
 | 
			
		||||
 | 
			
		||||
noinst_PROGRAMS = \
 | 
			
		||||
	gen__pfcp_ies_auto \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
gen__pfcp_ies_auto_SOURCES = \
 | 
			
		||||
	gen__pfcp_ies_auto.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
gen__pfcp_ies_auto_LDADD = \
 | 
			
		||||
	$(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(COVERAGE_LDFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
							
								
								
									
										365
									
								
								src/libosmo-pfcp/gen__pfcp_ies_auto.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								src/libosmo-pfcp/gen__pfcp_ies_auto.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,365 @@
 | 
			
		||||
/* Tool to generate C source code of structs and IE arrays for de- and encoding PFCP messages. */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/gtlv/gtlv_gen.h>
 | 
			
		||||
 | 
			
		||||
#define O OSMO_GTLV_GEN_O
 | 
			
		||||
#define M OSMO_GTLV_GEN_M
 | 
			
		||||
#define O_MULTI OSMO_GTLV_GEN_O_MULTI
 | 
			
		||||
#define M_MULTI OSMO_GTLV_GEN_M_MULTI
 | 
			
		||||
#define ALL_FROM_NAME osmo_gtlv_gen_ie_auto
 | 
			
		||||
 | 
			
		||||
#define Ms(MEMB_NAME) M(MEMB_NAME, #MEMB_NAME)
 | 
			
		||||
#define Os(MEMB_NAME) O(MEMB_NAME, #MEMB_NAME)
 | 
			
		||||
#define O_MULTIs(N, MEMB_NAME) O_MULTI(N, MEMB_NAME, #MEMB_NAME)
 | 
			
		||||
#define M_MULTIs(N, M, MEMB_NAME) M_MULTI(N, M, MEMB_NAME, #MEMB_NAME)
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie recovery_time_stamp = {
 | 
			
		||||
	"uint32_t",
 | 
			
		||||
	.dec_enc = "32be",
 | 
			
		||||
	.spec_ref = "7.4.2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie cause = {
 | 
			
		||||
	"enum osmo_pfcp_cause",
 | 
			
		||||
	.spec_ref = "8.2.1",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie offending_ie = {
 | 
			
		||||
	.decoded_type = "enum osmo_pfcp_iei",
 | 
			
		||||
	.spec_ref = "8.2.22",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie f_seid = {
 | 
			
		||||
	.tag_name = "f_seid",
 | 
			
		||||
	.spec_ref = "8.2.37",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie pdr_id = {
 | 
			
		||||
	.decoded_type = "uint16_t",
 | 
			
		||||
	.dec_enc = "16be",
 | 
			
		||||
	.spec_ref = "8.2.36",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie precedence = {
 | 
			
		||||
	.decoded_type = "uint32_t",
 | 
			
		||||
	.dec_enc = "32be",
 | 
			
		||||
	.spec_ref = "8.2.11",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie source_iface = {
 | 
			
		||||
	.decoded_type = "enum osmo_pfcp_source_iface",
 | 
			
		||||
	.spec_ref = "8.2.2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie f_teid = {
 | 
			
		||||
	.tag_name = "f_teid",
 | 
			
		||||
	.spec_ref = "8.2.3",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie traffic_endpoint_id = {
 | 
			
		||||
	.tag_name = "traffic_endpoint_id",
 | 
			
		||||
	.decoded_type = "uint8_t",
 | 
			
		||||
	.dec_enc = "8",
 | 
			
		||||
	.spec_ref = "8.2.92",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie iface_type = {
 | 
			
		||||
	.decoded_type = "enum osmo_pfcp_3gpp_iface_type",
 | 
			
		||||
	.tag_name = "3gpp_iface_type",
 | 
			
		||||
	.spec_ref = "8.2.118",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_pdi[] = {
 | 
			
		||||
	Ms(source_iface),
 | 
			
		||||
	O(f_teid, "local_f_teid"),
 | 
			
		||||
	O(ALL_FROM_NAME, "ue_ip_address"),
 | 
			
		||||
	Os(traffic_endpoint_id),
 | 
			
		||||
	O(iface_type, "source_iface_type"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie pdi = {
 | 
			
		||||
	.nested_ies = ies_in_pdi,
 | 
			
		||||
	.spec_ref = "7.5.2.2-2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie far_id = {
 | 
			
		||||
	.decoded_type = "uint32_t",
 | 
			
		||||
	.dec_enc = "32be",
 | 
			
		||||
	.spec_ref = "8.2.74",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_create_pdr[] = {
 | 
			
		||||
	Ms(pdr_id),
 | 
			
		||||
	Ms(precedence),
 | 
			
		||||
	Ms(pdi),
 | 
			
		||||
	O(ALL_FROM_NAME, "outer_header_removal"),
 | 
			
		||||
	Os(far_id),
 | 
			
		||||
	O(ALL_FROM_NAME, "activate_predefined_rules"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie create_pdr = {
 | 
			
		||||
	.nested_ies = ies_in_create_pdr,
 | 
			
		||||
	.spec_ref = "7.5.2.2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_created_pdr[] = {
 | 
			
		||||
	Ms(pdr_id),
 | 
			
		||||
	O(f_teid, "local_f_teid"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie created_pdr = {
 | 
			
		||||
	.nested_ies = ies_in_created_pdr,
 | 
			
		||||
	.spec_ref = "7.5.3.2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_upd_pdr[] = {
 | 
			
		||||
	Ms(pdr_id),
 | 
			
		||||
	O(ALL_FROM_NAME, "outer_header_removal"),
 | 
			
		||||
	Os(pdi),
 | 
			
		||||
	Os(far_id),
 | 
			
		||||
	O(ALL_FROM_NAME, "activate_predefined_rules"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie upd_pdr = {
 | 
			
		||||
	.nested_ies = ies_in_upd_pdr,
 | 
			
		||||
	.spec_ref = "7.5.4.2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_updated_pdr[] = {
 | 
			
		||||
	Ms(pdr_id),
 | 
			
		||||
	O(f_teid, "local_f_teid"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie updated_pdr = {
 | 
			
		||||
	.nested_ies = ies_in_updated_pdr,
 | 
			
		||||
	.spec_ref = "7.5.9.3",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_remove_pdr[] = {
 | 
			
		||||
	Ms(pdr_id),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie remove_pdr = {
 | 
			
		||||
	.nested_ies = ies_in_remove_pdr,
 | 
			
		||||
	.spec_ref = "7.5.4.6",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie destination_iface = {
 | 
			
		||||
	.decoded_type = "enum osmo_pfcp_dest_iface",
 | 
			
		||||
	.dec_enc = "dest_iface",
 | 
			
		||||
	.spec_ref = "8.2.24",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_forw_params[] = {
 | 
			
		||||
	Ms(destination_iface),
 | 
			
		||||
	O(ALL_FROM_NAME, "network_inst"),
 | 
			
		||||
	O(ALL_FROM_NAME, "outer_header_creation"),
 | 
			
		||||
	O(traffic_endpoint_id, "linked_te_id"),
 | 
			
		||||
	O(iface_type, "destination_iface_type"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie forw_params = {
 | 
			
		||||
	.nested_ies = ies_in_forw_params,
 | 
			
		||||
	.spec_ref = "7.5.2.3-2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_upd_forw_params[] = {
 | 
			
		||||
	Os(destination_iface),
 | 
			
		||||
	O(ALL_FROM_NAME, "network_inst"),
 | 
			
		||||
	O(ALL_FROM_NAME, "outer_header_creation"),
 | 
			
		||||
	O(traffic_endpoint_id, "linked_te_id"),
 | 
			
		||||
	O(iface_type, "destination_iface_type"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie upd_forw_params = {
 | 
			
		||||
	.nested_ies = ies_in_upd_forw_params,
 | 
			
		||||
	.spec_ref = "7.5.4.3-2",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_create_far[] = {
 | 
			
		||||
	Ms(far_id),
 | 
			
		||||
	M(ALL_FROM_NAME, "apply_action"),
 | 
			
		||||
	Os(forw_params),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie create_far = {
 | 
			
		||||
	.nested_ies = ies_in_create_far,
 | 
			
		||||
	.spec_ref = "7.5.2.3",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_remove_far[] = {
 | 
			
		||||
	Ms(far_id),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie remove_far = {
 | 
			
		||||
	.nested_ies = ies_in_remove_far,
 | 
			
		||||
	.spec_ref = "7.5.4.6",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_upd_far[] = {
 | 
			
		||||
	Ms(far_id),
 | 
			
		||||
	O(ALL_FROM_NAME, "apply_action"),
 | 
			
		||||
	Os(upd_forw_params),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie upd_far = {
 | 
			
		||||
	.nested_ies = ies_in_upd_far,
 | 
			
		||||
	.spec_ref = "7.5.4.3",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_heartbeat_req[] = {
 | 
			
		||||
	Ms(recovery_time_stamp),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_heartbeat_resp[] = {
 | 
			
		||||
	Ms(recovery_time_stamp),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_setup_req[] = {
 | 
			
		||||
	M(ALL_FROM_NAME, "node_id"),
 | 
			
		||||
	Ms(recovery_time_stamp),
 | 
			
		||||
	O(ALL_FROM_NAME, "up_function_features"),
 | 
			
		||||
	O(ALL_FROM_NAME, "cp_function_features"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_setup_resp[] = {
 | 
			
		||||
	M(ALL_FROM_NAME, "node_id"),
 | 
			
		||||
	Ms(cause),
 | 
			
		||||
	Ms(recovery_time_stamp),
 | 
			
		||||
	O(ALL_FROM_NAME, "up_function_features"),
 | 
			
		||||
	O(ALL_FROM_NAME, "cp_function_features"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_release_req[] = {
 | 
			
		||||
	M(ALL_FROM_NAME, "node_id"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_release_resp[] = {
 | 
			
		||||
	M(ALL_FROM_NAME, "node_id"),
 | 
			
		||||
	Ms(cause),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_est_req[] = {
 | 
			
		||||
	M(ALL_FROM_NAME, "node_id"),
 | 
			
		||||
	O(f_seid, "cp_f_seid"),
 | 
			
		||||
	M_MULTIs(32, 1, create_pdr),
 | 
			
		||||
	M_MULTI(32, 1, create_far, "create_far"),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_est_resp[] = {
 | 
			
		||||
	M(ALL_FROM_NAME, "node_id"),
 | 
			
		||||
	Ms(cause),
 | 
			
		||||
	Os(offending_ie),
 | 
			
		||||
	O(f_seid, "up_f_seid"),
 | 
			
		||||
	O_MULTIs(32, created_pdr),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_mod_req[] = {
 | 
			
		||||
	O(f_seid, "cp_f_seid"),
 | 
			
		||||
	O_MULTIs(32, remove_pdr),
 | 
			
		||||
	O_MULTIs(32, remove_far),
 | 
			
		||||
	O_MULTIs(32, create_pdr),
 | 
			
		||||
	O_MULTIs(32, create_far),
 | 
			
		||||
	O_MULTIs(32, upd_pdr),
 | 
			
		||||
	O_MULTIs(32, upd_far),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_mod_resp[] = {
 | 
			
		||||
	Ms(cause),
 | 
			
		||||
	Os(offending_ie),
 | 
			
		||||
	O_MULTIs(32, created_pdr),
 | 
			
		||||
	O_MULTIs(32, updated_pdr),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_del_req[] = {
 | 
			
		||||
	/* no IEs */
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_del_resp[] = {
 | 
			
		||||
	Ms(cause),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define MSG(NAME) { #NAME, ies_in_msg_##NAME }
 | 
			
		||||
static const struct osmo_gtlv_gen_msg pfcp_msg_defs[] = {
 | 
			
		||||
	MSG(heartbeat_req),
 | 
			
		||||
	MSG(heartbeat_resp),
 | 
			
		||||
	MSG(assoc_setup_req),
 | 
			
		||||
	MSG(assoc_setup_resp),
 | 
			
		||||
	MSG(assoc_release_req),
 | 
			
		||||
	MSG(assoc_release_resp),
 | 
			
		||||
	MSG(session_est_req),
 | 
			
		||||
	MSG(session_est_resp),
 | 
			
		||||
	MSG(session_mod_req),
 | 
			
		||||
	MSG(session_mod_resp),
 | 
			
		||||
	MSG(session_del_req),
 | 
			
		||||
	MSG(session_del_resp),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int main(int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gtlv_gen_cfg cfg = {
 | 
			
		||||
		.proto_name = "osmo_pfcp",
 | 
			
		||||
		.spec_ref_prefix = "3GPP TS 29.244 ",
 | 
			
		||||
		.message_type_enum = "enum osmo_pfcp_message_type",
 | 
			
		||||
		.message_type_prefix = "OSMO_PFCP_MSGT_",
 | 
			
		||||
		.tag_enum = "enum osmo_pfcp_iei",
 | 
			
		||||
		.tag_prefix = "OSMO_PFCP_IEI_",
 | 
			
		||||
		.decoded_type_prefix = "struct osmo_pfcp_ie_",
 | 
			
		||||
		.h_header = "#include <osmocom/pfcp/pfcp_ies_custom.h>",
 | 
			
		||||
		.c_header = "#include <osmocom/pfcp/pfcp_ies_auto.h>",
 | 
			
		||||
		.msg_defs = pfcp_msg_defs,
 | 
			
		||||
		.add_enc_to_str = true,
 | 
			
		||||
	};
 | 
			
		||||
	return osmo_gtlv_gen_main(&cfg, argc, argv);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										416
									
								
								src/libosmo-pfcp/pfcp_endpoint.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								src/libosmo-pfcp/pfcp_endpoint.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,416 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
#include <osmocom/core/tdef.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_endpoint.h>
 | 
			
		||||
#include <osmocom/pfcp/pfcp_msg.h>
 | 
			
		||||
 | 
			
		||||
/*! Entry of pfcp_endpoint message queue of PFCP messages, for re-transsions. */
 | 
			
		||||
struct osmo_pfcp_queue_entry {
 | 
			
		||||
	/* entry in per-peer list of messages waiting for a response */
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
	/* back-pointer */
 | 
			
		||||
	struct osmo_pfcp_endpoint *ep;
 | 
			
		||||
	/* message we have transmitted */
 | 
			
		||||
	struct osmo_pfcp_msg *m;
 | 
			
		||||
	/* T1 timer: How long to wait for response before retransmitting */
 | 
			
		||||
	struct osmo_timer_list t1;
 | 
			
		||||
	/* N1: number of pending re-transmissions */
 | 
			
		||||
	unsigned int n1_remaining;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* find a matching osmo_pfcp_queue_entry for given rx_hdr */
 | 
			
		||||
static struct osmo_pfcp_queue_entry *
 | 
			
		||||
osmo_pfcp_queue_find(struct llist_head *queue, const struct osmo_pfcp_msg *rx)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_queue_entry *qe;
 | 
			
		||||
	/* It's important to match only a Request to a Response and vice versa, because the remote peer makes its own
 | 
			
		||||
	 * sequence_nr. There could be a collision of sequence_nr. But as long as all Requests look for a Response and
 | 
			
		||||
	 * vice versa, the sequence_nr scopes don't overlap. */
 | 
			
		||||
	llist_for_each_entry(qe, queue, entry) {
 | 
			
		||||
		if (qe->m->is_response != rx->is_response
 | 
			
		||||
		    && qe->m->h.sequence_nr == rx->h.sequence_nr)
 | 
			
		||||
			return qe;
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* clean up and deallocate the given osmo_pfcp_queue_entry */
 | 
			
		||||
static void osmo_pfcp_queue_del(struct osmo_pfcp_queue_entry *qe)
 | 
			
		||||
{
 | 
			
		||||
	/* see also the talloc destructor */
 | 
			
		||||
	talloc_free(qe);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_queue_destructor(struct osmo_pfcp_queue_entry *qe)
 | 
			
		||||
{
 | 
			
		||||
	osmo_timer_del(&qe->t1);
 | 
			
		||||
	llist_del(&qe->entry);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_tdef osmo_pfcp_tdefs[] = {
 | 
			
		||||
	{ .T = -19, .default_val = 15, .unit = OSMO_TDEF_S,
 | 
			
		||||
	  .desc = "PFCP Heartbeat Request period, how long to wait between issuing requests"
 | 
			
		||||
	},
 | 
			
		||||
	{ .T = -20, .default_val = 15, .unit = OSMO_TDEF_S,
 | 
			
		||||
	  .desc = "PFCP Heartbeat Response timeout, the time after which to regard a non-responding peer as disconnected"
 | 
			
		||||
	},
 | 
			
		||||
	{ .T = -21, .default_val = 15, .unit = OSMO_TDEF_S,
 | 
			
		||||
	  .desc = "PFCP peer graceful shutdown timeout, how long to keep the peer's state after a peer requested"
 | 
			
		||||
		  " graceful shutdown"
 | 
			
		||||
	},
 | 
			
		||||
	{ .T = OSMO_PFCP_TIMER_T1, .default_val = 3000, .unit = OSMO_TDEF_MS,
 | 
			
		||||
	  .desc = "PFCP request timeout, how long after a missing response to retransmit a PFCP request"
 | 
			
		||||
	},
 | 
			
		||||
	{ .T = OSMO_PFCP_TIMER_N1, .default_val = 3, .unit = OSMO_TDEF_CUSTOM,
 | 
			
		||||
	  .desc = "Number of PFCP request retransmission attempts"
 | 
			
		||||
	},
 | 
			
		||||
	{ .T = OSMO_PFCP_TIMER_KEEP_RESP, .default_val = 10000, .unit = OSMO_TDEF_MS,
 | 
			
		||||
	  .desc = "PFCP response timeout, how long to keep a response, in case its same request is retransmitted by the peer"
 | 
			
		||||
	},
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_endpoint *osmo_pfcp_endpoint_create(void *ctx, void *priv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_endpoint *ep = talloc_zero(ctx, struct osmo_pfcp_endpoint);
 | 
			
		||||
	uint32_t unix_time;
 | 
			
		||||
	if (!ep)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	INIT_LLIST_HEAD(&ep->retrans_queue);
 | 
			
		||||
 | 
			
		||||
	ep->cfg.tdefs = osmo_pfcp_tdefs;
 | 
			
		||||
	ep->priv = priv;
 | 
			
		||||
	ep->pfcp_fd.fd = -1;
 | 
			
		||||
 | 
			
		||||
	/* time() returns seconds since 1970 (UNIX epoch), but the recovery_time_stamp is coded in the NTP format, which is
 | 
			
		||||
	 * seconds since 1900, the NTP era 0. 2208988800L is the offset between UNIX epoch and NTP era 0.
 | 
			
		||||
	 * TODO: what happens when we enter NTP era 1? Is it sufficient to integer-wrap? */
 | 
			
		||||
	unix_time = time(NULL);
 | 
			
		||||
	ep->recovery_time_stamp = unix_time + 2208988800L;
 | 
			
		||||
	LOGP(DLPFCP, LOGL_NOTICE, "PFCP endpoint: recovery timestamp = 0x%08x (%u seconds since UNIX epoch,"
 | 
			
		||||
	     " which is %u seconds since NTP era 0; IETF RFC 5905)\n",
 | 
			
		||||
	     ep->recovery_time_stamp, unix_time, ep->recovery_time_stamp);
 | 
			
		||||
 | 
			
		||||
	return ep;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t osmo_pfcp_endpoint_next_seq_nr(struct osmo_pfcp_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	ep->seq_nr_state++;
 | 
			
		||||
	ep->seq_nr_state &= 0xffffff;
 | 
			
		||||
	return ep->seq_nr_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static unsigned int ep_n1(struct osmo_pfcp_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	return osmo_tdef_get(ep->cfg.tdefs, OSMO_PFCP_TIMER_N1, OSMO_TDEF_CUSTOM, -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned int ep_t1(struct osmo_pfcp_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	return osmo_tdef_get(ep->cfg.tdefs, OSMO_PFCP_TIMER_T1, OSMO_TDEF_MS, -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned int ep_keep_resp(struct osmo_pfcp_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	return osmo_tdef_get(ep->cfg.tdefs, OSMO_PFCP_TIMER_KEEP_RESP, OSMO_TDEF_MS, -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Return true to keep the message in the queue, false for dropping from the queue. */
 | 
			
		||||
static bool pfcp_queue_retrans(struct osmo_pfcp_queue_entry *qe)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_endpoint *endpoint = qe->ep;
 | 
			
		||||
	unsigned int t1_ms = ep_t1(endpoint);
 | 
			
		||||
	struct osmo_pfcp_msg *m = qe->m;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* re-transmit */
 | 
			
		||||
	if (qe->n1_remaining)
 | 
			
		||||
		qe->n1_remaining--;
 | 
			
		||||
	OSMO_LOG_PFCP_MSG(m, LOGL_DEBUG, "re-sending (%u attempts remaining)\n", qe->n1_remaining);
 | 
			
		||||
 | 
			
		||||
	rc = osmo_pfcp_endpoint_tx_data(endpoint, m);
 | 
			
		||||
	/* If encoding failed, it cannot ever succeed. Drop the queue entry. */
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return false;
 | 
			
		||||
	/* if no more attempts remaining, drop from queue */
 | 
			
		||||
	if (!qe->n1_remaining)
 | 
			
		||||
		return false;
 | 
			
		||||
	/* re-schedule timer, keep in queue */
 | 
			
		||||
	osmo_timer_schedule(&qe->t1, t1_ms/1000, t1_ms%1000);
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* T1 for a given queue entry has expired */
 | 
			
		||||
static void pfcp_queue_timer_cb(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_queue_entry *qe = data;
 | 
			
		||||
	bool keep;
 | 
			
		||||
 | 
			
		||||
	if (qe->m->is_response) {
 | 
			
		||||
		/* The response has waited in the queue for any retransmissions of its initiating request. Now that time
 | 
			
		||||
		 * has passed and the response can be dropped from the queue. */
 | 
			
		||||
		keep = false;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* The request is still here, which means it has not received a response from the remote side.
 | 
			
		||||
		 * Retransmit the request. */
 | 
			
		||||
		keep = pfcp_queue_retrans(qe);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (keep)
 | 
			
		||||
		return;
 | 
			
		||||
	/* Drop the queue entry. No more retransmissions. */
 | 
			
		||||
	/* FIXME: notify user */
 | 
			
		||||
	osmo_pfcp_queue_del(qe);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Directly encode and transmit the message, without storing in the retrans_queue. */
 | 
			
		||||
int osmo_pfcp_endpoint_tx_data_no_logging(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	msg = msgb_alloc_c(OTC_SELECT, OSMO_PFCP_MSGB_ALLOC_SIZE, "PFCP-tx");
 | 
			
		||||
	rc = osmo_pfcp_msg_encode(msg, m);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	rc = sendto(ep->pfcp_fd.fd, msgb_data(msg), msgb_length(msg), 0,
 | 
			
		||||
		    (struct sockaddr*)&m->remote_addr, sizeof(m->remote_addr));
 | 
			
		||||
	if (rc != msgb_length(msg))
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_endpoint_tx_data(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "sending\n");
 | 
			
		||||
	return osmo_pfcp_endpoint_tx_data_no_logging(ep, m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_endpoint_tx_heartbeat_req(struct osmo_pfcp_endpoint *ep, const struct osmo_sockaddr *remote_addr)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_msg *tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, remote_addr, NULL, NULL,
 | 
			
		||||
							  OSMO_PFCP_MSGT_HEARTBEAT_REQ);
 | 
			
		||||
	tx->ies.heartbeat_req.recovery_time_stamp = ep->recovery_time_stamp;
 | 
			
		||||
	tx->h.sequence_nr = osmo_pfcp_endpoint_next_seq_nr(ep);
 | 
			
		||||
	return osmo_pfcp_endpoint_tx_data(ep, tx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* add a given msgb to the queue of per-peer messages waiting for a response */
 | 
			
		||||
static int osmo_pfcp_endpoint_retrans_queue_add(struct osmo_pfcp_endpoint *endpoint, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_queue_entry *qe;
 | 
			
		||||
	unsigned int n1 = ep_n1(endpoint);
 | 
			
		||||
	unsigned int t1_ms = ep_t1(endpoint);
 | 
			
		||||
	unsigned int keep_resp_ms = ep_keep_resp(endpoint);
 | 
			
		||||
	unsigned int timeout = m->is_response ? keep_resp_ms : t1_ms;
 | 
			
		||||
 | 
			
		||||
	LOGP(DLPFCP, LOGL_DEBUG, "retransmit unanswered Requests %u x %ums; keep sent Responses for %ums\n",
 | 
			
		||||
	     n1, t1_ms, keep_resp_ms);
 | 
			
		||||
	/* If there are no retransmissions or no timeout, it makes no sense to add to the queue. */
 | 
			
		||||
	if (!n1 || !t1_ms)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	qe = talloc(endpoint, struct osmo_pfcp_queue_entry);
 | 
			
		||||
	OSMO_ASSERT(qe);
 | 
			
		||||
	*qe = (struct osmo_pfcp_queue_entry){
 | 
			
		||||
		.ep = endpoint,
 | 
			
		||||
		.m = m,
 | 
			
		||||
		.n1_remaining = m->is_response ? 0 : n1,
 | 
			
		||||
	};
 | 
			
		||||
	talloc_steal(qe, m);
 | 
			
		||||
	llist_add_tail(&qe->entry, &endpoint->retrans_queue);
 | 
			
		||||
	talloc_set_destructor(qe, osmo_pfcp_queue_destructor);
 | 
			
		||||
 | 
			
		||||
	osmo_timer_setup(&qe->t1, pfcp_queue_timer_cb, qe);
 | 
			
		||||
	osmo_timer_schedule(&qe->t1, timeout/1000, timeout%1000);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Transmit a PFCP message.
 | 
			
		||||
 * Store the message in the local message queue for possible retransmissions. */
 | 
			
		||||
int osmo_pfcp_endpoint_tx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_node_id *node_id;
 | 
			
		||||
	if (!m->is_response)
 | 
			
		||||
		m->h.sequence_nr = osmo_pfcp_endpoint_next_seq_nr(ep);
 | 
			
		||||
	node_id = osmo_pfcp_msg_node_id(m);
 | 
			
		||||
	if (node_id)
 | 
			
		||||
		*node_id = ep->cfg.local_node_id;
 | 
			
		||||
 | 
			
		||||
	osmo_pfcp_endpoint_retrans_queue_add(ep, m);
 | 
			
		||||
	return osmo_pfcp_endpoint_tx_data(ep, m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void osmo_pfcp_endpoint_handle_rx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	bool dispatch_rx = true;
 | 
			
		||||
	struct osmo_pfcp_queue_entry *prev_msg;
 | 
			
		||||
 | 
			
		||||
	OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "received\n");
 | 
			
		||||
 | 
			
		||||
	if (m->h.message_type == OSMO_PFCP_MSGT_HEARTBEAT_REQ) {
 | 
			
		||||
		/* Directly answer with a Heartbeat Response. Still also dispatch the Rx event to the peer. */
 | 
			
		||||
		struct osmo_pfcp_msg *resp = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, NULL, m, OSMO_PFCP_MSGT_HEARTBEAT_RESP);
 | 
			
		||||
		resp->ies.heartbeat_resp.recovery_time_stamp = ep->recovery_time_stamp;
 | 
			
		||||
		osmo_pfcp_endpoint_tx_data(ep, resp);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* If this is receiving a response, search for matching request that is now completed.
 | 
			
		||||
	 * If this is receiving a request, search for a matching response that can be retransmitted.
 | 
			
		||||
	 * Either way see if a matching sequence_nr is already in the queue. */
 | 
			
		||||
	prev_msg = osmo_pfcp_queue_find(&ep->retrans_queue, m);
 | 
			
		||||
	if (prev_msg) {
 | 
			
		||||
		if (m->is_response && !prev_msg->m->is_response) {
 | 
			
		||||
			/* Got a response, the original request is now ACKed and can be dropped from the retransmission
 | 
			
		||||
			 * queue. */
 | 
			
		||||
			osmo_pfcp_queue_del(prev_msg);
 | 
			
		||||
		} else if (!m->is_response && prev_msg->m->is_response) {
 | 
			
		||||
			/* Got a request, but we have already sent a response to this same request earlier. Retransmit
 | 
			
		||||
			 * the same response, and don't dispatch the msg rx. Keep our response queued in case the
 | 
			
		||||
			 * request is retransmitted yet another time. */
 | 
			
		||||
 | 
			
		||||
			OSMO_LOG_PFCP_MSG(prev_msg->m, LOGL_INFO, "re-sending cached response\n");
 | 
			
		||||
			osmo_pfcp_endpoint_tx_data_no_logging(ep, prev_msg->m);
 | 
			
		||||
			dispatch_rx = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (dispatch_rx)
 | 
			
		||||
		ep->rx_msg(ep, m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* call-back for PFCP socket file descriptor */
 | 
			
		||||
static int osmo_pfcp_fd_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct osmo_pfcp_endpoint *ep = ofd->data;
 | 
			
		||||
 | 
			
		||||
	if (what & OSMO_FD_READ) {
 | 
			
		||||
		struct osmo_sockaddr remote;
 | 
			
		||||
		socklen_t remote_len = sizeof(remote);
 | 
			
		||||
		struct msgb *msg = msgb_alloc_c(OTC_SELECT, OSMO_PFCP_MSGB_ALLOC_SIZE, "PFCP-rx");
 | 
			
		||||
		if (!msg)
 | 
			
		||||
			return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
		msg->l3h = msg->tail;
 | 
			
		||||
		rc = recvfrom(ofd->fd, msg->tail, msgb_tailroom(msg), 0, (struct sockaddr *)&remote, &remote_len);
 | 
			
		||||
		if (rc <= 0)
 | 
			
		||||
			return -EIO;
 | 
			
		||||
		msgb_put(msg, rc);
 | 
			
		||||
 | 
			
		||||
		OSMO_ASSERT(ep->rx_msg);
 | 
			
		||||
 | 
			
		||||
		/* This may be a bundle of PFCP messages. Parse and receive each message received, by shifting l4h
 | 
			
		||||
		 * through the message bundle. */
 | 
			
		||||
		msg->l4h = msg->l3h;
 | 
			
		||||
		while (msgb_l4len(msg)) {
 | 
			
		||||
			struct osmo_gtlv_load tlv;
 | 
			
		||||
			struct osmo_pfcp_msg *m = osmo_pfcp_msg_alloc_rx(OTC_SELECT, &remote);
 | 
			
		||||
 | 
			
		||||
			rc = osmo_pfcp_msg_decode_header(&tlv, m, msg);
 | 
			
		||||
			if (rc < 0)
 | 
			
		||||
				break;
 | 
			
		||||
			msg->l4h += rc;
 | 
			
		||||
 | 
			
		||||
			rc = osmo_pfcp_msg_decode_tlv(m, &tlv);
 | 
			
		||||
			if (rc)
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			/* Populate message context to point at peer and session, if applicable */
 | 
			
		||||
			if (ep->set_msg_ctx)
 | 
			
		||||
				ep->set_msg_ctx(ep, m);
 | 
			
		||||
 | 
			
		||||
			osmo_pfcp_endpoint_handle_rx(ep, m);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! bind a PFCP endpoint to its configured address (ep->cfg.local_addr).
 | 
			
		||||
 * \return 0 on success, negative on error. */
 | 
			
		||||
int osmo_pfcp_endpoint_bind(struct osmo_pfcp_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	/* close the existing socket, if any */
 | 
			
		||||
	osmo_pfcp_endpoint_close(ep);
 | 
			
		||||
 | 
			
		||||
	if (!ep->rx_msg) {
 | 
			
		||||
		LOGP(DLPFCP, LOGL_ERROR, "missing rx_msg cb at osmo_pfcp_endpoint\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* create the new socket, binding to configured local address */
 | 
			
		||||
	ep->pfcp_fd.cb = osmo_pfcp_fd_cb;
 | 
			
		||||
	ep->pfcp_fd.data = ep;
 | 
			
		||||
	rc = osmo_sock_init_osa_ofd(&ep->pfcp_fd, SOCK_DGRAM, IPPROTO_UDP, &ep->cfg.local_addr, NULL, OSMO_SOCK_F_BIND);
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		return rc;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_endpoint_close(struct osmo_pfcp_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_queue_entry *qe;
 | 
			
		||||
	while ((qe = llist_first_entry_or_null(&ep->retrans_queue, struct osmo_pfcp_queue_entry, entry))) {
 | 
			
		||||
		osmo_pfcp_queue_del(qe);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ep->pfcp_fd.fd != -1) {
 | 
			
		||||
		osmo_fd_unregister(&ep->pfcp_fd);
 | 
			
		||||
		close(ep->pfcp_fd.fd);
 | 
			
		||||
		ep->pfcp_fd.fd = -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_endpoint_free(struct osmo_pfcp_endpoint **ep)
 | 
			
		||||
{
 | 
			
		||||
	if (!*ep)
 | 
			
		||||
		return;
 | 
			
		||||
	osmo_pfcp_endpoint_close(*ep);
 | 
			
		||||
	talloc_free(*ep);
 | 
			
		||||
	*ep = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if 0
 | 
			
		||||
void osmo_pfcp_endpoint_invalidate_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_fsm_inst *deleted_fi)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_queue_entry *qe;
 | 
			
		||||
	llist_for_each_entry(qe, &ep->retrans_queue, entry) {
 | 
			
		||||
		osmo_pfcp_msg_invalidate_ctx(qe->m, deleted_fi);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										179
									
								
								src/libosmo-pfcp/pfcp_heartbeat_fsm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/libosmo-pfcp/pfcp_heartbeat_fsm.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
#include <osmocom/core/tdef.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_heartbeat_fsm.h>
 | 
			
		||||
 | 
			
		||||
enum heartbeat_fsm_state {
 | 
			
		||||
	HEARTBEAT_ST_IDLE,
 | 
			
		||||
	HEARTBEAT_ST_WAIT_RESP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct value_string heartbeat_fsm_event_names[] = {
 | 
			
		||||
	OSMO_VALUE_STRING(OSMO_PFCP_HEARTBEAT_EV_RX_RESP),
 | 
			
		||||
	OSMO_VALUE_STRING(OSMO_PFCP_HEARTBEAT_EV_RX_REQ),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm heartbeat_fsm;
 | 
			
		||||
 | 
			
		||||
static const struct osmo_tdef_state_timeout heartbeat_fsm_timeouts[32] = {
 | 
			
		||||
	[HEARTBEAT_ST_IDLE] = { .T = 0 },
 | 
			
		||||
	[HEARTBEAT_ST_WAIT_RESP] = { .T = 0 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Transition to a state, using the T timer defined in heartbeat_fsm_timeouts.
 | 
			
		||||
 * Assumes local variable fi exists. */
 | 
			
		||||
#define heartbeat_state_chg(state) \
 | 
			
		||||
	osmo_tdef_fsm_inst_state_chg(fi, state, \
 | 
			
		||||
				     heartbeat_fsm_timeouts, \
 | 
			
		||||
				     ((struct heartbeat*)(fi->priv))->tdefs, \
 | 
			
		||||
				     5)
 | 
			
		||||
 | 
			
		||||
struct heartbeat {
 | 
			
		||||
	struct osmo_fsm_inst *fi;
 | 
			
		||||
	uint32_t parent_event_tx_heartbeat;
 | 
			
		||||
	struct osmo_tdef *tdefs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_fsm_inst *osmo_pfcp_heartbeat_alloc(struct osmo_fsm_inst *parent_fi,
 | 
			
		||||
						uint32_t parent_event_tx_heartbeat, uint32_t parent_event_term,
 | 
			
		||||
						struct osmo_tdef *tdefs)
 | 
			
		||||
{
 | 
			
		||||
	struct heartbeat *heartbeat;
 | 
			
		||||
 | 
			
		||||
	struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&heartbeat_fsm, parent_fi, parent_event_term);
 | 
			
		||||
	OSMO_ASSERT(fi);
 | 
			
		||||
 | 
			
		||||
	heartbeat = talloc(fi, struct heartbeat);
 | 
			
		||||
	OSMO_ASSERT(heartbeat);
 | 
			
		||||
	fi->priv = heartbeat;
 | 
			
		||||
	*heartbeat = (struct heartbeat){
 | 
			
		||||
		.fi = fi,
 | 
			
		||||
		.parent_event_tx_heartbeat = parent_event_tx_heartbeat,
 | 
			
		||||
		.tdefs = tdefs,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return heartbeat->fi;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int heartbeat_fsm_timer_cb(struct osmo_fsm_inst *fi)
 | 
			
		||||
{
 | 
			
		||||
	/* Return 1 to terminate FSM instance, 0 to keep running */
 | 
			
		||||
	switch (fi->state) {
 | 
			
		||||
	case HEARTBEAT_ST_IDLE:
 | 
			
		||||
		/* Time for another heartbeat request */
 | 
			
		||||
		heartbeat_state_chg(HEARTBEAT_ST_WAIT_RESP);
 | 
			
		||||
		return 0;
 | 
			
		||||
	case HEARTBEAT_ST_WAIT_RESP:
 | 
			
		||||
		/* Response did not arrive. Emit parent_event_term to the parent_fi. */
 | 
			
		||||
		return 1;
 | 
			
		||||
	default:
 | 
			
		||||
		OSMO_ASSERT(false);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void pfcp_heartbeat_idle_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 | 
			
		||||
{
 | 
			
		||||
	switch (event) {
 | 
			
		||||
 | 
			
		||||
	case OSMO_PFCP_HEARTBEAT_EV_RX_RESP:
 | 
			
		||||
		/* A retransmission? */
 | 
			
		||||
	case OSMO_PFCP_HEARTBEAT_EV_RX_REQ:
 | 
			
		||||
		/* Either way, if we've seen any Heartbeat message from the peer, consider a Heartbeat to have succeeded
 | 
			
		||||
		 * and restart the idle timeout. */
 | 
			
		||||
		heartbeat_state_chg(HEARTBEAT_ST_IDLE);
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		OSMO_ASSERT(false);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void pfcp_heartbeat_wait_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
 | 
			
		||||
{
 | 
			
		||||
	struct heartbeat *heartbeat = fi->priv;
 | 
			
		||||
	/* Let the caller's implementation figure out how exactly to encode the Heartbeat Request and send it.
 | 
			
		||||
	 * Just dispatching events here. */
 | 
			
		||||
	osmo_fsm_inst_dispatch(fi->proc.parent, heartbeat->parent_event_tx_heartbeat, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void pfcp_heartbeat_wait_resp_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 | 
			
		||||
{
 | 
			
		||||
	switch (event) {
 | 
			
		||||
 | 
			
		||||
	case OSMO_PFCP_HEARTBEAT_EV_RX_RESP:
 | 
			
		||||
		heartbeat_state_chg(HEARTBEAT_ST_IDLE);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case OSMO_PFCP_HEARTBEAT_EV_RX_REQ:
 | 
			
		||||
		/* Doesn't matter whether the peer is also requesting, still waiting for the response to our own
 | 
			
		||||
		 * request. */
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		OSMO_ASSERT(false);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define S(x)    (1 << (x))
 | 
			
		||||
 | 
			
		||||
static const struct osmo_fsm_state heartbeat_fsm_states[] = {
 | 
			
		||||
	[HEARTBEAT_ST_IDLE] = {
 | 
			
		||||
		.name = "idle",
 | 
			
		||||
		.in_event_mask = 0
 | 
			
		||||
			,
 | 
			
		||||
		.out_state_mask = 0
 | 
			
		||||
			| S(HEARTBEAT_ST_WAIT_RESP)
 | 
			
		||||
			,
 | 
			
		||||
		.action = pfcp_heartbeat_idle_action,
 | 
			
		||||
	},
 | 
			
		||||
	[HEARTBEAT_ST_WAIT_RESP] = {
 | 
			
		||||
		.name = "wait_resp",
 | 
			
		||||
		.in_event_mask = 0
 | 
			
		||||
			| S(OSMO_PFCP_HEARTBEAT_EV_RX_RESP)
 | 
			
		||||
			| S(OSMO_PFCP_HEARTBEAT_EV_RX_REQ)
 | 
			
		||||
			,
 | 
			
		||||
		.out_state_mask = 0
 | 
			
		||||
			,
 | 
			
		||||
		.onenter = pfcp_heartbeat_wait_resp_onenter,
 | 
			
		||||
		.action = pfcp_heartbeat_wait_resp_action,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm pfcp_heartbeat_fsm = {
 | 
			
		||||
	.name = "pfcp_heartbeat",
 | 
			
		||||
	.states = heartbeat_fsm_states,
 | 
			
		||||
	.num_states = ARRAY_SIZE(heartbeat_fsm_states),
 | 
			
		||||
	.log_subsys = DLPFCP,
 | 
			
		||||
	.event_names = heartbeat_fsm_event_names,
 | 
			
		||||
	.timer_cb = heartbeat_fsm_timer_cb,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static __attribute__((constructor)) void pfcp_heartbeat_fsm_register(void)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_ASSERT(osmo_fsm_register(&pfcp_heartbeat_fsm) == 0);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										990
									
								
								src/libosmo-pfcp/pfcp_ies_custom.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										990
									
								
								src/libosmo-pfcp/pfcp_ies_custom.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,990 @@
 | 
			
		||||
/* Decoded PFCP IEs, to be used by the auto-generated pfcp_ies_auto.c. */
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <inttypes.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gtlv/gtlv.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_ies_custom.h>
 | 
			
		||||
#include <osmocom/pfcp/pfcp_strs.h>
 | 
			
		||||
#include <osmocom/pfcp/pfcp_msg.h>
 | 
			
		||||
 | 
			
		||||
/* Assumes presence of local variable osmo_pfcp_msg *m. m->log_ctx may be NULL. */
 | 
			
		||||
#define RETURN_ERROR(RC, FMT, ARGS...) \
 | 
			
		||||
	do {\
 | 
			
		||||
		OSMO_ASSERT(decoded_struct); \
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(OSMO_PFCP_MSG_FOR_IES(decoded_struct), LOGL_ERROR, FMT " (%d: %s)\n", ##ARGS, RC, \
 | 
			
		||||
				  strerror((RC) > 0 ? (RC) : -(RC))); \
 | 
			
		||||
		return RC; \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
/* Assumes presence of local variable osmo_gtlv_load *tlv. Usage:
 | 
			
		||||
 *   ENSURE_LENGTH_IS_EXACTLY(2);
 | 
			
		||||
 */
 | 
			
		||||
#define ENSURE_LENGTH_IS_EXACTLY(VAL) \
 | 
			
		||||
	do { \
 | 
			
		||||
		if (!(tlv->len == VAL)) \
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "IE has length = %zu, expected length == " #VAL, tlv->len); \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
/* Assumes presence of local variable osmo_gtlv_load *tlv. Usage:
 | 
			
		||||
 *   ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
 */
 | 
			
		||||
#define ENSURE_LENGTH_IS_AT_LEAST(VAL) \
 | 
			
		||||
	do { \
 | 
			
		||||
		if (!(tlv->len >= VAL)) \
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "IE has length = %zu, expected length >= " #VAL, tlv->len); \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
/* Assumes presence of local variable osmo_gtlv_load *tlv. Usage:
 | 
			
		||||
 *   const uint8_t *pos = tlv->val;
 | 
			
		||||
 *   ENSURE_REMAINING_LENGTH_IS_AT_LEAST("first part", pos, 23);
 | 
			
		||||
 *   <parse first part>
 | 
			
		||||
 *   pos += 23;
 | 
			
		||||
 *   ENSURE_REMAINING_LENGTH_IS_AT_LEAST("very long part", pos, 235);
 | 
			
		||||
 *   <parse very long part>
 | 
			
		||||
 *   pos += 235;
 | 
			
		||||
 */
 | 
			
		||||
#define ENSURE_REMAINING_LENGTH_IS_AT_LEAST(NAME, POS, MIN_VAL) \
 | 
			
		||||
	do { \
 | 
			
		||||
		if (!((tlv->len - ((POS) - tlv->val)) >= MIN_VAL)) \
 | 
			
		||||
			RETURN_ERROR(-EINVAL, \
 | 
			
		||||
				     "at value octet %d: %zu octets remaining, but " #NAME " requires length >= " #MIN_VAL, \
 | 
			
		||||
				     (int)((POS) - tlv->val), \
 | 
			
		||||
				     tlv->len - ((POS) - tlv->val)); \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_ie_f_seid_set(struct osmo_pfcp_ie_f_seid *f_seid, uint64_t seid, const struct osmo_sockaddr *remote_addr)
 | 
			
		||||
{
 | 
			
		||||
	*f_seid = (struct osmo_pfcp_ie_f_seid) {
 | 
			
		||||
		.seid = seid,
 | 
			
		||||
	};
 | 
			
		||||
	osmo_pfcp_ip_addrs_set(&f_seid->ip_addr, remote_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_cause(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	enum osmo_pfcp_cause *cause = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_EXACTLY(1);
 | 
			
		||||
	*cause = *tlv->val;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_cause(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_cause *cause = encode_from;
 | 
			
		||||
	msgb_put_u8(tlv->dst, *cause);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_cause(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_cause *cause = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%s", osmo_pfcp_cause_str(*cause));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_offending_ie(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	enum osmo_pfcp_iei *offending_ie = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_EXACTLY(2);
 | 
			
		||||
	*offending_ie = osmo_load16be(tlv->val);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_offending_ie(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_iei *offending_ie = encode_from;
 | 
			
		||||
	msgb_put_u16(tlv->dst, *offending_ie);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_offending_ie(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_iei *offending_ie = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%s", osmo_pfcp_iei_str(*offending_ie));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_8(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t *u8 = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
	*u8 = tlv->val[0];
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_8(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const uint8_t *u8 = encode_from;
 | 
			
		||||
	msgb_put_u8(tlv->dst, *u8);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_8(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const uint8_t *u8 = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%u", *u8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_16be(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t *u16 = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(2);
 | 
			
		||||
	*u16 = osmo_load16be(tlv->val);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_16be(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const uint16_t *u16 = encode_from;
 | 
			
		||||
	msgb_put_u16(tlv->dst, *u16);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_16be(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const uint16_t *u16 = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%u", *u16);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_32be(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t *u32 = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(4);
 | 
			
		||||
	*u32 = osmo_load32be(tlv->val);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_32be(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const uint32_t *u32 = encode_from;
 | 
			
		||||
	msgb_put_u32(tlv->dst, *u32);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_32be(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const uint32_t *u32 = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%u", *u32);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_3gpp_iface_type(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
	*_3gpp_iface_type = tlv->val[0] & 0x3f;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_3gpp_iface_type(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = encode_from;
 | 
			
		||||
	msgb_put_u8(tlv->dst, (uint8_t)(*_3gpp_iface_type) & 0x3f);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_3gpp_iface_type(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%s", osmo_pfcp_3gpp_iface_type_str(*_3gpp_iface_type));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_source_iface(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	enum osmo_pfcp_source_iface *source_iface = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
	*source_iface = tlv->val[0] & 0xf;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_source_iface(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_source_iface *source_iface = encode_from;
 | 
			
		||||
	msgb_put_u8(tlv->dst, (uint8_t)(*source_iface) & 0xf);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_source_iface(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_source_iface *source_iface = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%s", osmo_pfcp_source_iface_str(*source_iface));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_dest_iface(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	enum osmo_pfcp_dest_iface *dest_interface = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
	*dest_interface = tlv->val[0] & 0xf;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_dest_iface(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_dest_iface *dest_interface = encode_from;
 | 
			
		||||
	msgb_put_u8(tlv->dst, (uint8_t)(*dest_interface) & 0xf);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_dest_iface(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const enum osmo_pfcp_dest_iface *dest_iface = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%s", osmo_pfcp_dest_iface_str(*dest_iface));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_node_id(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_node_id *node_id = decode_to;
 | 
			
		||||
	const void *ip;
 | 
			
		||||
	unsigned int ip_len;
 | 
			
		||||
	unsigned int want_len;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
	node_id->type = *(uint8_t *)tlv->val;
 | 
			
		||||
	ip = &tlv->val[1];
 | 
			
		||||
	ip_len = tlv->len - 1;
 | 
			
		||||
 | 
			
		||||
	switch (node_id->type) {
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV4:
 | 
			
		||||
		want_len = sizeof(node_id->ip.u.sin.sin_addr);
 | 
			
		||||
		if (ip_len != want_len)
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "Node ID: wrong IPv4 address value length %u, expected %u",
 | 
			
		||||
				     ip_len, want_len);
 | 
			
		||||
		osmo_sockaddr_from_octets(&node_id->ip, ip, ip_len);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV6:
 | 
			
		||||
		want_len = sizeof(node_id->ip.u.sin6.sin6_addr);
 | 
			
		||||
		if (ip_len != want_len)
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "Node ID: wrong IPv6 address value length %u, expected %u",
 | 
			
		||||
				     ip_len, want_len);
 | 
			
		||||
		osmo_sockaddr_from_octets(&node_id->ip, ip, ip_len);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_FQDN:
 | 
			
		||||
		/* Copy and add a trailing nul */
 | 
			
		||||
		OSMO_STRLCPY_ARRAY(node_id->fqdn, ip);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "Invalid Node ID Type: %d", node_id->type);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_node_id(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int l;
 | 
			
		||||
	const struct osmo_pfcp_ie_node_id *node_id = encode_from;
 | 
			
		||||
	msgb_put_u8(tlv->dst, node_id->type);
 | 
			
		||||
	switch (node_id->type) {
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV4:
 | 
			
		||||
		l = sizeof(node_id->ip.u.sin.sin_addr);
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &node_id->ip);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV6:
 | 
			
		||||
		l = sizeof(node_id->ip.u.sin6.sin6_addr);
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &node_id->ip);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_FQDN:
 | 
			
		||||
		l = strnlen(node_id->fqdn, sizeof(node_id->fqdn));
 | 
			
		||||
		/* Copy without trailing nul */
 | 
			
		||||
		memcpy((char *)msgb_put(tlv->dst, l), node_id->fqdn, l);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "Invalid Node ID Type: %d", node_id->type);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_node_id(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_node_id *node_id = encode_from;
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
 | 
			
		||||
	switch (node_id->type) {
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV4:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "v4:");
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV6:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "v6:");
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_FQDN:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "fqdn:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_quote_str_buf3,
 | 
			
		||||
				   node_id->fqdn, strnlen(node_id->fqdn, sizeof(node_id->fqdn)));
 | 
			
		||||
		return sb.chars_needed;
 | 
			
		||||
	default:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "unknown-node-id-type-%u", node_id->type);
 | 
			
		||||
		return sb.chars_needed;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &node_id->ip);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool osmo_pfcp_bits_get(const uint8_t *bits, unsigned int bitpos)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int bytenum = bitpos / 8;
 | 
			
		||||
	unsigned int bitmask = 1 << (bitpos % 8);
 | 
			
		||||
 | 
			
		||||
	return (bool)(bits[bytenum] & bitmask);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_bits_set(uint8_t *bits, unsigned int bitpos, bool val)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int bytenum = bitpos / 8;
 | 
			
		||||
	unsigned int bitmask = 1 << (bitpos % 8);
 | 
			
		||||
 | 
			
		||||
	if (val)
 | 
			
		||||
		bits[bytenum] |= bitmask;
 | 
			
		||||
	else
 | 
			
		||||
		bits[bytenum] &= ~bitmask;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_bits_to_str_buf(char *buf, size_t buflen, const uint8_t *bits, const struct value_string *bit_strs)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "(");
 | 
			
		||||
	for (; bit_strs->str; bit_strs++) {
 | 
			
		||||
		if (osmo_pfcp_bits_get(bits, bit_strs->value)) {
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, " %s", bit_strs->str);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " )");
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *osmo_pfcp_bits_to_str_c(void *ctx, const uint8_t *bits, const struct value_string *bit_str)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_pfcp_bits_to_str_buf, bits, bit_str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_up_function_features(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_up_function_features *up_function_features = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(6);
 | 
			
		||||
	memcpy(up_function_features->bits, tlv->val, 6);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_up_function_features(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_up_function_features *up_function_features = encode_from;
 | 
			
		||||
	memcpy(msgb_put(tlv->dst, 6), up_function_features->bits, 6);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_up_function_features(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_up_function_features *up_function_features = encode_from;
 | 
			
		||||
	return osmo_pfcp_bits_to_str_buf(buf, buflen, up_function_features->bits, osmo_pfcp_up_feature_strs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_cp_function_features(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_cp_function_features *cp_function_features = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(sizeof(cp_function_features->bits));
 | 
			
		||||
	memcpy(cp_function_features->bits, tlv->val, sizeof(cp_function_features->bits));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_cp_function_features(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_cp_function_features *cp_function_features = encode_from;
 | 
			
		||||
	memcpy(msgb_put(tlv->dst, sizeof(cp_function_features->bits)),
 | 
			
		||||
	       cp_function_features->bits, sizeof(cp_function_features->bits));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_cp_function_features(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_cp_function_features *cp_function_features = encode_from;
 | 
			
		||||
	return osmo_pfcp_bits_to_str_buf(buf, buflen, cp_function_features->bits, osmo_pfcp_cp_feature_strs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_f_seid(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_f_seid *f_seid = decode_to;
 | 
			
		||||
	uint8_t flags;
 | 
			
		||||
	uint8_t pos;
 | 
			
		||||
	unsigned int l;
 | 
			
		||||
	/* flags and 8 octet seid */
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(9);
 | 
			
		||||
	flags = tlv->val[0];
 | 
			
		||||
	f_seid->ip_addr.v6_present = flags & 1;
 | 
			
		||||
	f_seid->ip_addr.v4_present = flags & 2;
 | 
			
		||||
	f_seid->seid = osmo_load64be(&tlv->val[1]);
 | 
			
		||||
	pos = 9;
 | 
			
		||||
	if (f_seid->ip_addr.v4_present) {
 | 
			
		||||
		l = sizeof(f_seid->ip_addr.v4.u.sin.sin_addr);
 | 
			
		||||
		if (pos + l > tlv->len)
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "F-SEID IE is too short for the IPv4 address: %zu", tlv->len);
 | 
			
		||||
		osmo_sockaddr_from_octets(&f_seid->ip_addr.v4, &tlv->val[pos], l);
 | 
			
		||||
		pos += l;
 | 
			
		||||
	}
 | 
			
		||||
	if (f_seid->ip_addr.v6_present) {
 | 
			
		||||
		l = sizeof(f_seid->ip_addr.v4.u.sin6.sin6_addr);
 | 
			
		||||
		if (pos + l > tlv->len)
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "F-SEID IE is too short for the IPv6 address: %zu", tlv->len);
 | 
			
		||||
		osmo_sockaddr_from_octets(&f_seid->ip_addr.v6, &tlv->val[pos], l);
 | 
			
		||||
		pos += l;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_f_seid(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_f_seid *f_seid = encode_from;
 | 
			
		||||
	unsigned int l;
 | 
			
		||||
	uint8_t flags = (f_seid->ip_addr.v6_present ? 1 : 0) + (f_seid->ip_addr.v4_present ? 2 : 0);
 | 
			
		||||
	/* flags and 8 octet seid */
 | 
			
		||||
	msgb_put_u8(tlv->dst, flags);
 | 
			
		||||
	osmo_store64be(f_seid->seid, msgb_put(tlv->dst, 8));
 | 
			
		||||
 | 
			
		||||
	if (f_seid->ip_addr.v4_present) {
 | 
			
		||||
		if (f_seid->ip_addr.v4.u.sin.sin_family != AF_INET)
 | 
			
		||||
			RETURN_ERROR(-EINVAL,
 | 
			
		||||
				     "f_seid IE indicates IPv4 address, but there is no ipv4_addr");
 | 
			
		||||
		l = sizeof(f_seid->ip_addr.v4.u.sin.sin_addr);
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &f_seid->ip_addr.v4);
 | 
			
		||||
	}
 | 
			
		||||
	if (f_seid->ip_addr.v6_present) {
 | 
			
		||||
		if (f_seid->ip_addr.v6.u.sin6.sin6_family != AF_INET6)
 | 
			
		||||
			RETURN_ERROR(-EINVAL,
 | 
			
		||||
				     "f_seid IE indicates IPv6 address, but there is no ipv6_addr");
 | 
			
		||||
		l = sizeof(f_seid->ip_addr.v6.u.sin6.sin6_addr);
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &f_seid->ip_addr.v6);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int ip_addrs_to_str_buf(char *buf, size_t buflen, const struct osmo_pfcp_ip_addrs *addrs)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	if (addrs->v4_present) {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",v4:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &addrs->v4);
 | 
			
		||||
	}
 | 
			
		||||
	if (addrs->v6_present) {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",v6:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &addrs->v6);
 | 
			
		||||
	}
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_f_seid(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_f_seid *f_seid = encode_from;
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "0x%"PRIx64, f_seid->seid);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &f_seid->ip_addr);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_f_teid(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_f_teid *f_teid = decode_to;
 | 
			
		||||
	uint8_t flags;
 | 
			
		||||
	const uint8_t *pos;
 | 
			
		||||
 | 
			
		||||
	*f_teid = (struct osmo_pfcp_ie_f_teid){};
 | 
			
		||||
 | 
			
		||||
	pos = tlv->val;
 | 
			
		||||
 | 
			
		||||
	ENSURE_REMAINING_LENGTH_IS_AT_LEAST("flags", pos, 1);
 | 
			
		||||
	flags = *pos;
 | 
			
		||||
	pos++;
 | 
			
		||||
	f_teid->choose_flag = flags & 4;
 | 
			
		||||
 | 
			
		||||
	if (!f_teid->choose_flag) {
 | 
			
		||||
		/* A fixed TEID and address are provided */
 | 
			
		||||
		f_teid->fixed.ip_addr.v4_present = flags & 1;
 | 
			
		||||
		f_teid->fixed.ip_addr.v6_present = flags & 2;
 | 
			
		||||
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("TEID", pos, 4);
 | 
			
		||||
		f_teid->fixed.teid = osmo_load32be(pos);
 | 
			
		||||
		pos += 4;
 | 
			
		||||
 | 
			
		||||
		if (f_teid->fixed.ip_addr.v4_present) {
 | 
			
		||||
			osmo_static_assert(sizeof(f_teid->fixed.ip_addr.v4.u.sin.sin_addr) == 4, sin_addr_size_is_4);
 | 
			
		||||
			ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv4 address", pos, 4);
 | 
			
		||||
			osmo_sockaddr_from_octets(&f_teid->fixed.ip_addr.v4, pos, 4);
 | 
			
		||||
			pos += 4;
 | 
			
		||||
		}
 | 
			
		||||
		if (f_teid->fixed.ip_addr.v6_present) {
 | 
			
		||||
			osmo_static_assert(sizeof(f_teid->fixed.ip_addr.v6.u.sin6.sin6_addr) == 16, sin6_addr_size_is_16);
 | 
			
		||||
			ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv6 address", pos, 16);
 | 
			
		||||
			osmo_sockaddr_from_octets(&f_teid->fixed.ip_addr.v6, pos, 16);
 | 
			
		||||
			pos += 16;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		/* CH flag is 1, choose an F-TEID. */
 | 
			
		||||
		f_teid->choose.ipv4_addr = flags & 1;
 | 
			
		||||
		f_teid->choose.ipv6_addr = flags & 2;
 | 
			
		||||
		f_teid->choose.choose_id_present = flags & 8;
 | 
			
		||||
 | 
			
		||||
		if (f_teid->choose.choose_id_present) {
 | 
			
		||||
			ENSURE_REMAINING_LENGTH_IS_AT_LEAST("CHOOSE ID", pos, 1);
 | 
			
		||||
			f_teid->choose.choose_id = *pos;
 | 
			
		||||
			pos++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_f_teid(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_f_teid *f_teid = encode_from;
 | 
			
		||||
	uint8_t flags;
 | 
			
		||||
 | 
			
		||||
	flags = (f_teid->choose_flag ? 4 : 0);
 | 
			
		||||
 | 
			
		||||
	if (!f_teid->choose_flag) {
 | 
			
		||||
		/* A fixed TEID and address are provided */
 | 
			
		||||
		flags |= (f_teid->fixed.ip_addr.v4_present ? 1 : 0)
 | 
			
		||||
			 + (f_teid->fixed.ip_addr.v6_present ? 2 : 0);
 | 
			
		||||
 | 
			
		||||
		msgb_put_u8(tlv->dst, flags);
 | 
			
		||||
		msgb_put_u32(tlv->dst, f_teid->fixed.teid);
 | 
			
		||||
 | 
			
		||||
		if (f_teid->fixed.ip_addr.v4_present) {
 | 
			
		||||
			if (f_teid->fixed.ip_addr.v4.u.sin.sin_family != AF_INET)
 | 
			
		||||
				RETURN_ERROR(-EINVAL,
 | 
			
		||||
					     "f_teid IE indicates IPv4 address, but there is no ipv4_addr"
 | 
			
		||||
					     " (sin_family = %d != AF_INET)", f_teid->fixed.ip_addr.v4.u.sin.sin_family);
 | 
			
		||||
			osmo_sockaddr_to_octets(msgb_put(tlv->dst, 4), 4, &f_teid->fixed.ip_addr.v4);
 | 
			
		||||
		}
 | 
			
		||||
		if (f_teid->fixed.ip_addr.v6_present) {
 | 
			
		||||
			if (f_teid->fixed.ip_addr.v6.u.sin6.sin6_family != AF_INET6)
 | 
			
		||||
				RETURN_ERROR(-EINVAL,
 | 
			
		||||
					     "f_teid IE indicates IPv6 address, but there is no ipv6_addr"
 | 
			
		||||
					     " (sin6_family = %d != AF_INET6)", f_teid->fixed.ip_addr.v6.u.sin6.sin6_family);
 | 
			
		||||
			osmo_sockaddr_to_octets(msgb_put(tlv->dst, 16), 16, &f_teid->fixed.ip_addr.v6);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		flags |= (f_teid->choose.ipv4_addr ? 1 : 0)
 | 
			
		||||
			 + (f_teid->choose.ipv6_addr ? 2 : 0)
 | 
			
		||||
			 + (f_teid->choose.choose_id_present ? 8 : 0);
 | 
			
		||||
		msgb_put_u8(tlv->dst, flags);
 | 
			
		||||
		if (f_teid->choose.choose_id_present)
 | 
			
		||||
			msgb_put_u8(tlv->dst, f_teid->choose.choose_id);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ie_f_teid_to_str_buf(char *buf, size_t buflen, const struct osmo_pfcp_ie_f_teid *ft)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	if (ft->choose_flag) {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "CHOOSE");
 | 
			
		||||
		if (ft->choose.ipv4_addr)
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, "-v4");
 | 
			
		||||
		if (ft->choose.ipv6_addr)
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, "-v6");
 | 
			
		||||
		if (ft->choose.choose_id_present)
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, "-id%u", ft->choose.choose_id);
 | 
			
		||||
	} else {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "TEID-0x%x", ft->fixed.teid);
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &ft->fixed.ip_addr);
 | 
			
		||||
	}
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *osmo_pfcp_ie_f_teid_to_str_c(void *ctx, const struct osmo_pfcp_ie_f_teid *ft)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_pfcp_ie_f_teid_to_str_buf, ft)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_f_teid(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_f_teid *f_teid = encode_from;
 | 
			
		||||
	return osmo_pfcp_ie_f_teid_to_str_buf(buf, buflen, f_teid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_apply_action(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_apply_action *apply_action = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
	*apply_action = (struct osmo_pfcp_ie_apply_action){};
 | 
			
		||||
	memcpy(apply_action->bits, tlv->val, OSMO_MIN(tlv->len, sizeof(apply_action->bits)));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_apply_action(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_apply_action *apply_action = encode_from;
 | 
			
		||||
	memcpy(msgb_put(tlv->dst, sizeof(apply_action->bits)),
 | 
			
		||||
	       apply_action->bits, sizeof(apply_action->bits));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_apply_action(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_apply_action *apply_action = encode_from;
 | 
			
		||||
	return osmo_pfcp_bits_to_str_buf(buf, buflen, apply_action->bits, osmo_pfcp_apply_action_strs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_network_inst(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_network_inst *network_inst = decode_to;
 | 
			
		||||
	osmo_strlcpy(network_inst->str, (const char *)tlv->val, OSMO_MIN(sizeof(network_inst->str), tlv->len+1));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_network_inst(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_network_inst *network_inst = encode_from;
 | 
			
		||||
	unsigned int l = strlen(network_inst->str);
 | 
			
		||||
	if (l)
 | 
			
		||||
		memcpy(msgb_put(tlv->dst, l), network_inst->str, l);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_network_inst(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_network_inst *network_inst = encode_from;
 | 
			
		||||
	return osmo_quote_str_buf3(buf, buflen, network_inst->str,
 | 
			
		||||
				   strnlen(network_inst->str, sizeof(network_inst->str)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_outer_header_creation(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_outer_header_creation *ohc = decode_to;
 | 
			
		||||
	const uint8_t *pos;
 | 
			
		||||
	bool gtp_u_udp_ipv4;
 | 
			
		||||
	bool gtp_u_udp_ipv6;
 | 
			
		||||
	bool udp_ipv4;
 | 
			
		||||
	bool udp_ipv6;
 | 
			
		||||
	bool ipv4;
 | 
			
		||||
	bool ipv6;
 | 
			
		||||
	bool c_tag;
 | 
			
		||||
	bool s_tag;
 | 
			
		||||
 | 
			
		||||
	*ohc = (struct osmo_pfcp_ie_outer_header_creation){};
 | 
			
		||||
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(2);
 | 
			
		||||
 | 
			
		||||
	memcpy(ohc->desc_bits, tlv->val, 2);
 | 
			
		||||
 | 
			
		||||
	gtp_u_udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4);
 | 
			
		||||
	udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV4);
 | 
			
		||||
	ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV4);
 | 
			
		||||
	gtp_u_udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV6);
 | 
			
		||||
	udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV6);
 | 
			
		||||
	ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV6);
 | 
			
		||||
	c_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_C_TAG);
 | 
			
		||||
	s_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_S_TAG);
 | 
			
		||||
 | 
			
		||||
	pos = tlv->val + 2;
 | 
			
		||||
	if (gtp_u_udp_ipv4 || gtp_u_udp_ipv6) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("TEID", pos, 4);
 | 
			
		||||
		ohc->teid_present = true;
 | 
			
		||||
		ohc->teid = osmo_load32be(pos);
 | 
			
		||||
		pos += 4;
 | 
			
		||||
	}
 | 
			
		||||
	if (gtp_u_udp_ipv4 || udp_ipv4 || ipv4) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv4 address", pos, 4);
 | 
			
		||||
		ohc->ip_addr.v4_present = true;
 | 
			
		||||
		osmo_sockaddr_from_octets(&ohc->ip_addr.v4, pos, 4);
 | 
			
		||||
		pos += 4;
 | 
			
		||||
	}
 | 
			
		||||
	if (gtp_u_udp_ipv6 || udp_ipv6 || ipv6) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv6 address", pos, 16);
 | 
			
		||||
		ohc->ip_addr.v6_present = true;
 | 
			
		||||
		osmo_sockaddr_from_octets(&ohc->ip_addr.v6, pos, 16);
 | 
			
		||||
		pos += 16;
 | 
			
		||||
	}
 | 
			
		||||
	if (udp_ipv4 || udp_ipv6) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("UDP port number", pos, 2);
 | 
			
		||||
		ohc->port_number_present = true;
 | 
			
		||||
		ohc->port_number = osmo_load16be(pos);
 | 
			
		||||
		pos += 2;
 | 
			
		||||
	}
 | 
			
		||||
	if (c_tag) {
 | 
			
		||||
		ohc->c_tag_present = true;
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("C-TAG", pos, 3);
 | 
			
		||||
		ohc->c_tag_present = true;
 | 
			
		||||
		ohc->c_tag = osmo_load32be_ext_2(pos, 3);
 | 
			
		||||
		pos += 3;
 | 
			
		||||
	}
 | 
			
		||||
	if (s_tag) {
 | 
			
		||||
		ohc->s_tag_present = true;
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("S-TAG", pos, 3);
 | 
			
		||||
		ohc->s_tag_present = true;
 | 
			
		||||
		ohc->s_tag = osmo_load32be_ext_2(pos, 3);
 | 
			
		||||
		pos += 3;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_outer_header_creation(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_outer_header_creation *ohc = encode_from;
 | 
			
		||||
	bool gtp_u_udp_ipv4;
 | 
			
		||||
	bool gtp_u_udp_ipv6;
 | 
			
		||||
	bool udp_ipv4;
 | 
			
		||||
	bool udp_ipv6;
 | 
			
		||||
	bool ipv4;
 | 
			
		||||
	bool ipv6;
 | 
			
		||||
	bool c_tag;
 | 
			
		||||
	bool s_tag;
 | 
			
		||||
 | 
			
		||||
	memcpy(msgb_put(tlv->dst, sizeof(ohc->desc_bits)), ohc->desc_bits, sizeof(ohc->desc_bits));
 | 
			
		||||
 | 
			
		||||
	gtp_u_udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4);
 | 
			
		||||
	udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV4);
 | 
			
		||||
	ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV4);
 | 
			
		||||
	gtp_u_udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV6);
 | 
			
		||||
	udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV6);
 | 
			
		||||
	ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV6);
 | 
			
		||||
	c_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_C_TAG);
 | 
			
		||||
	s_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_S_TAG);
 | 
			
		||||
 | 
			
		||||
	if ((gtp_u_udp_ipv4 || gtp_u_udp_ipv6) != (ohc->teid_present))
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "teid_present = %s does not match the description bits 0x%02x\n",
 | 
			
		||||
			     ohc->teid_present ? "true" : "false",
 | 
			
		||||
			     ohc->desc_bits[0]);
 | 
			
		||||
	if (ohc->teid_present)
 | 
			
		||||
		msgb_put_u32(tlv->dst, ohc->teid);
 | 
			
		||||
 | 
			
		||||
	if ((gtp_u_udp_ipv4 || udp_ipv4 || ipv4) != ohc->ip_addr.v4_present)
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "ipv4_addr_present = %s does not match the description bits 0x%02x\n",
 | 
			
		||||
			     ohc->ip_addr.v4_present ? "true" : "false",
 | 
			
		||||
			     ohc->desc_bits[0]);
 | 
			
		||||
	if (ohc->ip_addr.v4_present)
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, 4), 4, &ohc->ip_addr.v4);
 | 
			
		||||
 | 
			
		||||
	if ((gtp_u_udp_ipv6 || udp_ipv6 || ipv6) != ohc->ip_addr.v6_present)
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "ipv6_addr_present = %s does not match the description bits 0x%02x\n",
 | 
			
		||||
			     ohc->ip_addr.v6_present ? "true" : "false",
 | 
			
		||||
			     ohc->desc_bits[0]);
 | 
			
		||||
	if (ohc->ip_addr.v6_present)
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, 16), 16, &ohc->ip_addr.v6);
 | 
			
		||||
 | 
			
		||||
	if ((udp_ipv4 || udp_ipv6) != ohc->port_number_present)
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "port_number_present = %s does not match the description bits 0x%02x\n",
 | 
			
		||||
			     ohc->port_number_present ? "true" : "false",
 | 
			
		||||
			     ohc->desc_bits[0]);
 | 
			
		||||
	if (ohc->port_number_present)
 | 
			
		||||
		msgb_put_u16(tlv->dst, ohc->port_number);
 | 
			
		||||
 | 
			
		||||
	if (c_tag != ohc->c_tag_present)
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "c_tag_present = %s does not match the description bits 0x%02x%02x\n",
 | 
			
		||||
			     ohc->c_tag_present ? "true" : "false",
 | 
			
		||||
			     ohc->desc_bits[1], ohc->desc_bits[0]);
 | 
			
		||||
	if (ohc->c_tag_present)
 | 
			
		||||
		osmo_store32be_ext(ohc->c_tag, msgb_put(tlv->dst, 3), 3);
 | 
			
		||||
 | 
			
		||||
	if (s_tag != ohc->s_tag_present)
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "s_tag_present = %s does not match the description bits 0x%02x%02x\n",
 | 
			
		||||
			     ohc->s_tag_present ? "true" : "false",
 | 
			
		||||
			     ohc->desc_bits[1], ohc->desc_bits[0]);
 | 
			
		||||
	if (ohc->s_tag_present)
 | 
			
		||||
		osmo_store32be_ext(ohc->s_tag, msgb_put(tlv->dst, 3), 3);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_outer_header_creation(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_outer_header_creation *ohc = encode_from;
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_pfcp_bits_to_str_buf, ohc->desc_bits, osmo_pfcp_outer_header_creation_strs);
 | 
			
		||||
	if (ohc->teid_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",TEID:0x%x", ohc->teid);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &ohc->ip_addr);
 | 
			
		||||
	if (ohc->port_number_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",port:%u", ohc->port_number);
 | 
			
		||||
	if (ohc->c_tag_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",c-tag:%u", ohc->c_tag);
 | 
			
		||||
	if (ohc->s_tag_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",s-tag:%u", ohc->s_tag);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_activate_predefined_rules(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_activate_predefined_rules *activate_predefined_rules = decode_to;
 | 
			
		||||
	osmo_strlcpy(activate_predefined_rules->str, (const char *)tlv->val, OSMO_MIN(sizeof(activate_predefined_rules->str), tlv->len+1));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_activate_predefined_rules(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_activate_predefined_rules *activate_predefined_rules = encode_from;
 | 
			
		||||
	unsigned int l = strlen(activate_predefined_rules->str);
 | 
			
		||||
	if (l)
 | 
			
		||||
		memcpy(msgb_put(tlv->dst, l), activate_predefined_rules->str, l);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_activate_predefined_rules(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_activate_predefined_rules *activate_predefined_rules = encode_from;
 | 
			
		||||
	return osmo_quote_str_buf3(buf, buflen, activate_predefined_rules->str,
 | 
			
		||||
				   strnlen(activate_predefined_rules->str, sizeof(activate_predefined_rules->str)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_outer_header_removal(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = decode_to;
 | 
			
		||||
	ENSURE_LENGTH_IS_AT_LEAST(1);
 | 
			
		||||
	outer_header_removal->desc = tlv->val[0];
 | 
			
		||||
 | 
			
		||||
	if (tlv->len > 1) {
 | 
			
		||||
		outer_header_removal->gtp_u_extension_header_del_present = true;
 | 
			
		||||
		memcpy(outer_header_removal->gtp_u_extension_header_del_bits, &tlv->val[1],
 | 
			
		||||
		       sizeof(outer_header_removal->gtp_u_extension_header_del_bits));
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_outer_header_removal(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = encode_from;
 | 
			
		||||
	msgb_put_u8(tlv->dst, outer_header_removal->desc);
 | 
			
		||||
	if (outer_header_removal->gtp_u_extension_header_del_present) {
 | 
			
		||||
		memcpy(msgb_put(tlv->dst, sizeof(outer_header_removal->gtp_u_extension_header_del_bits)),
 | 
			
		||||
		       outer_header_removal->gtp_u_extension_header_del_bits,
 | 
			
		||||
		       sizeof(outer_header_removal->gtp_u_extension_header_del_bits));
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_outer_header_removal(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = encode_from;
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "%s", osmo_pfcp_outer_header_removal_desc_str(outer_header_removal->desc));
 | 
			
		||||
	if (outer_header_removal->gtp_u_extension_header_del_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",ext-hdr-del:0x%x", outer_header_removal->gtp_u_extension_header_del_bits[0]);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_dec_ue_ip_address(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_ie_ue_ip_address *ue_ip_address = decode_to;
 | 
			
		||||
	const uint8_t *pos;
 | 
			
		||||
	uint8_t flags;
 | 
			
		||||
 | 
			
		||||
	pos = tlv->val;
 | 
			
		||||
 | 
			
		||||
	ENSURE_REMAINING_LENGTH_IS_AT_LEAST("flags", pos, 1);
 | 
			
		||||
	flags = *pos;
 | 
			
		||||
	pos++;
 | 
			
		||||
 | 
			
		||||
	ue_ip_address->ipv6_prefix_length_present = flags & (1 << 6);
 | 
			
		||||
	ue_ip_address->chv6 = flags & (1 << 5);
 | 
			
		||||
	ue_ip_address->chv4 = flags & (1 << 4);
 | 
			
		||||
	ue_ip_address->ipv6_prefix_delegation_bits_present = flags & (1 << 3);
 | 
			
		||||
	ue_ip_address->ip_is_destination = flags & (1 << 2);
 | 
			
		||||
	ue_ip_address->ip_addr.v4_present = flags & (1 << 1);
 | 
			
		||||
	ue_ip_address->ip_addr.v6_present = flags & (1 << 0);
 | 
			
		||||
 | 
			
		||||
	if (ue_ip_address->ip_addr.v4_present) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv4 address", pos, 4);
 | 
			
		||||
		osmo_sockaddr_from_octets(&ue_ip_address->ip_addr.v4, pos, 4);
 | 
			
		||||
		pos += 4;
 | 
			
		||||
	}
 | 
			
		||||
	if (ue_ip_address->ip_addr.v6_present) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv6 address", pos, 16);
 | 
			
		||||
		osmo_sockaddr_from_octets(&ue_ip_address->ip_addr.v6, pos, 16);
 | 
			
		||||
		pos += 16;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ue_ip_address->ipv6_prefix_delegation_bits_present) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv6 prefix delegation bits", pos, 1);
 | 
			
		||||
		ue_ip_address->ipv6_prefix_delegation_bits = *pos;
 | 
			
		||||
		pos++;
 | 
			
		||||
	}
 | 
			
		||||
	if (ue_ip_address->ipv6_prefix_length_present) {
 | 
			
		||||
		ENSURE_REMAINING_LENGTH_IS_AT_LEAST("IPv6 prefix length", pos, 1);
 | 
			
		||||
		ue_ip_address->ipv6_prefix_length = *pos;
 | 
			
		||||
		pos++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_ue_ip_address(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_ue_ip_address *ue_ip_address = encode_from;
 | 
			
		||||
	unsigned int l;
 | 
			
		||||
	uint8_t flags;
 | 
			
		||||
 | 
			
		||||
	flags = 0
 | 
			
		||||
		| (ue_ip_address->ipv6_prefix_length_present ? (1 << 6) : 0)
 | 
			
		||||
		| (ue_ip_address->chv6 ? (1 << 5) : 0)
 | 
			
		||||
		| (ue_ip_address->chv4 ? (1 << 4) : 0)
 | 
			
		||||
		| (ue_ip_address->ipv6_prefix_delegation_bits_present ? (1 << 3) : 0)
 | 
			
		||||
		| (ue_ip_address->ip_is_destination ? (1 << 2) : 0)
 | 
			
		||||
		| (ue_ip_address->ip_addr.v4_present ? (1 << 1) : 0)
 | 
			
		||||
		| (ue_ip_address->ip_addr.v6_present ? (1 << 0) : 0)
 | 
			
		||||
		;
 | 
			
		||||
 | 
			
		||||
	msgb_put_u8(tlv->dst, flags);
 | 
			
		||||
 | 
			
		||||
	if (ue_ip_address->ip_addr.v4_present) {
 | 
			
		||||
		if (ue_ip_address->ip_addr.v4.u.sin.sin_family != AF_INET)
 | 
			
		||||
			RETURN_ERROR(-EINVAL,
 | 
			
		||||
				     "ue_ip_address IE indicates IPv4 address, but there is no ipv4_addr");
 | 
			
		||||
		l = sizeof(ue_ip_address->ip_addr.v4.u.sin.sin_addr);
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &ue_ip_address->ip_addr.v4);
 | 
			
		||||
	}
 | 
			
		||||
	if (ue_ip_address->ip_addr.v6_present) {
 | 
			
		||||
		if (ue_ip_address->ip_addr.v6.u.sin6.sin6_family != AF_INET6)
 | 
			
		||||
			RETURN_ERROR(-EINVAL,
 | 
			
		||||
				     "ue_ip_address IE indicates IPv6 address, but there is no ipv6_addr");
 | 
			
		||||
		l = sizeof(ue_ip_address->ip_addr.v6.u.sin6.sin6_addr);
 | 
			
		||||
		osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &ue_ip_address->ip_addr.v6);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ue_ip_address->ipv6_prefix_delegation_bits_present)
 | 
			
		||||
		msgb_put_u8(tlv->dst, ue_ip_address->ipv6_prefix_delegation_bits);
 | 
			
		||||
 | 
			
		||||
	if (ue_ip_address->ipv6_prefix_length_present)
 | 
			
		||||
		msgb_put_u8(tlv->dst, ue_ip_address->ipv6_prefix_length);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_enc_to_str_ue_ip_address(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_ie_ue_ip_address *uia = encode_from;
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	if (uia->chv4)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "chv4");
 | 
			
		||||
	if (uia->chv6)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%schv4", sb.pos ? "," : "");
 | 
			
		||||
	if (uia->ip_is_destination)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "%sdst", sb.pos ? "," : "");
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &uia->ip_addr);
 | 
			
		||||
	if (uia->ipv6_prefix_delegation_bits_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",ipv6-prefix-deleg:%x", uia->ipv6_prefix_delegation_bits);
 | 
			
		||||
	if (uia->ipv6_prefix_length_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, ",ipv6-prefix-len:%u", uia->ipv6_prefix_length);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										553
									
								
								src/libosmo-pfcp/pfcp_msg.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										553
									
								
								src/libosmo-pfcp/pfcp_msg.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,553 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/endian.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
#include <osmocom/core/use_count.h>
 | 
			
		||||
#include <osmocom/core/bitvec.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_msg.h>
 | 
			
		||||
#include <osmocom/gtlv/gtlv_dec_enc.h>
 | 
			
		||||
 | 
			
		||||
/* Assumes presence of local variable osmo_pfcp_msg *m. m->log_ctx may be NULL. */
 | 
			
		||||
#define RETURN_ERROR(RC, FMT, ARGS...) \
 | 
			
		||||
	do {\
 | 
			
		||||
		OSMO_ASSERT(m); \
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, FMT " (%d: %s)\n", ##ARGS, RC, strerror((RC) > 0? (RC) : -(RC))); \
 | 
			
		||||
		return RC; \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
bool osmo_pfcp_msgtype_is_response(enum osmo_pfcp_message_type message_type)
 | 
			
		||||
{
 | 
			
		||||
	switch (message_type) {
 | 
			
		||||
	case OSMO_PFCP_MSGT_HEARTBEAT_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_PFD_MGMT_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_VERSION_NOT_SUPP_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_NODE_REPORT_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_SET_DEL_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_EST_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_MOD_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_DEL_RESP:
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_REP_RESP:
 | 
			
		||||
		return true;
 | 
			
		||||
	default:
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_header_common {
 | 
			
		||||
	uint8_t seid_present:1,
 | 
			
		||||
		message_priority_present:1,
 | 
			
		||||
		follow_on:1,
 | 
			
		||||
		spare:2,
 | 
			
		||||
		version:3;
 | 
			
		||||
	uint8_t message_type;
 | 
			
		||||
	uint16_t message_length;
 | 
			
		||||
} __attribute__ ((packed));
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_header_no_seid {
 | 
			
		||||
	struct osmo_pfcp_header_common c;
 | 
			
		||||
	uint8_t sequence_nr[3];
 | 
			
		||||
	uint8_t spare;
 | 
			
		||||
} __attribute__ ((packed));
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_header_seid {
 | 
			
		||||
	struct osmo_pfcp_header_common c;
 | 
			
		||||
	uint64_t session_endpoint_identifier;
 | 
			
		||||
	uint8_t sequence_nr[3];
 | 
			
		||||
	uint8_t message_priority:4,
 | 
			
		||||
		spare:4;
 | 
			
		||||
} __attribute__ ((packed));
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ie_node_id_from_osmo_sockaddr(struct osmo_pfcp_ie_node_id *node_id, const struct osmo_sockaddr *os)
 | 
			
		||||
{
 | 
			
		||||
	switch (os->u.sa.sa_family) {
 | 
			
		||||
	case AF_INET:
 | 
			
		||||
		node_id->type = OSMO_PFCP_NODE_ID_T_IPV4;
 | 
			
		||||
		break;
 | 
			
		||||
	case AF_INET6:
 | 
			
		||||
		node_id->type = OSMO_PFCP_NODE_ID_T_IPV6;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
	node_id->ip = *os;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ie_node_id_to_osmo_sockaddr(const struct osmo_pfcp_ie_node_id *node_id, struct osmo_sockaddr *os)
 | 
			
		||||
{
 | 
			
		||||
	switch (node_id->type) {
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV4:
 | 
			
		||||
		if (os->u.sa.sa_family != AF_INET)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_NODE_ID_T_IPV6:
 | 
			
		||||
		if (os->u.sa.sa_family != AF_INET6)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
	*os = node_id->ip;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int pfcp_header_set_message_length(struct osmo_pfcp_header_common *c, unsigned int header_and_payload_len)
 | 
			
		||||
{
 | 
			
		||||
	if (header_and_payload_len < sizeof(struct osmo_pfcp_header_common))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	if (header_and_payload_len - sizeof(struct osmo_pfcp_header_common) > UINT16_MAX)
 | 
			
		||||
		return -EMSGSIZE;
 | 
			
		||||
	osmo_store16be(header_and_payload_len - sizeof(struct osmo_pfcp_header_common),
 | 
			
		||||
		       &c->message_length);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static unsigned int pfcp_header_get_message_length(const struct osmo_pfcp_header_common *c)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int len = osmo_load16be(&c->message_length);
 | 
			
		||||
	return len + sizeof(struct osmo_pfcp_header_common);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Encode and append the given PFCP header to a msgb.
 | 
			
		||||
 * \param[out] msg message buffer to which to push the header.
 | 
			
		||||
 * \param[in] to-be-encoded representation of PFCP header. */
 | 
			
		||||
static int enc_pfcp_header(struct msgb *msg, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_pfcp_header_parsed *parsed = &m->h;
 | 
			
		||||
	struct osmo_pfcp_header_seid *h_seid;
 | 
			
		||||
	struct osmo_pfcp_header_no_seid *h_no_seid;
 | 
			
		||||
	struct osmo_pfcp_header_common *c;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	if (!parsed->seid_present) {
 | 
			
		||||
		h_no_seid = (struct osmo_pfcp_header_no_seid*)msgb_put(msg, sizeof(struct osmo_pfcp_header_no_seid));
 | 
			
		||||
		c = &h_no_seid->c;
 | 
			
		||||
	} else {
 | 
			
		||||
		h_seid = (struct osmo_pfcp_header_seid*)msgb_put(msg, sizeof(struct osmo_pfcp_header_seid));
 | 
			
		||||
		c = &h_seid->c;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*c = (struct osmo_pfcp_header_common){
 | 
			
		||||
		.version = parsed->version,
 | 
			
		||||
		.message_priority_present = (parsed->priority_present ? 1 : 0),
 | 
			
		||||
		.seid_present = (parsed->seid_present ? 1 : 0),
 | 
			
		||||
		.message_type = parsed->message_type,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* Put a preliminary length reflecting only the header, until it is updated later in osmo_pfcp_msg_encode(). */
 | 
			
		||||
	rc = pfcp_header_set_message_length(c, parsed->seid_present ? sizeof(struct osmo_pfcp_header_seid)
 | 
			
		||||
								    : sizeof(struct osmo_pfcp_header_no_seid));
 | 
			
		||||
	if (rc)
 | 
			
		||||
		RETURN_ERROR(rc, "Problem with PFCP message length");
 | 
			
		||||
 | 
			
		||||
	if (!parsed->seid_present) {
 | 
			
		||||
		osmo_store32be_ext(parsed->sequence_nr, h_no_seid->sequence_nr, 3);
 | 
			
		||||
		if (parsed->priority_present)
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "Message Priority can only be present when the SEID is also present");
 | 
			
		||||
	} else {
 | 
			
		||||
		osmo_store64be(parsed->seid, &h_seid->session_endpoint_identifier);
 | 
			
		||||
		osmo_store32be_ext(parsed->sequence_nr, h_seid->sequence_nr, 3);
 | 
			
		||||
		if (parsed->priority_present)
 | 
			
		||||
			h_seid->message_priority = parsed->priority;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void osmo_pfcp_msg_set_memb_ofs(struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_gtlv_coding *mc = osmo_pfcp_get_msg_coding(m->h.message_type);
 | 
			
		||||
	m->ofs_cause = 0;
 | 
			
		||||
	m->ofs_node_id = 0;
 | 
			
		||||
	if (!mc)
 | 
			
		||||
		return;
 | 
			
		||||
	for (; !osmo_gtlv_coding_end(mc) && (m->ofs_cause == 0 || m->ofs_node_id == 0); mc++) {
 | 
			
		||||
		if (mc->ti.tag == OSMO_PFCP_IEI_CAUSE)
 | 
			
		||||
			m->ofs_cause = offsetof(struct osmo_pfcp_msg, ies) + mc->memb_ofs;
 | 
			
		||||
		if (mc->ti.tag == OSMO_PFCP_IEI_NODE_ID)
 | 
			
		||||
			m->ofs_node_id = offsetof(struct osmo_pfcp_msg, ies) + mc->memb_ofs;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if 0
 | 
			
		||||
	switch (m->h.message_type) {
 | 
			
		||||
	case OSMO_PFCP_MSGT_ASSOC_SETUP_REQ:
 | 
			
		||||
		m->ofs_node_id = offsetof(struct osmo_pfcp_msg, ies.assoc_setup_req.node_id);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP:
 | 
			
		||||
		m->ofs_cause = offsetof(struct osmo_pfcp_msg, ies.assoc_setup_resp.cause);
 | 
			
		||||
		m->ofs_node_id = offsetof(struct osmo_pfcp_msg, ies.assoc_setup_resp.node_id);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_EST_REQ:
 | 
			
		||||
		m->ofs_node_id = offsetof(struct osmo_pfcp_msg, ies.session_est_req.node_id);
 | 
			
		||||
		break;
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_EST_RESP:
 | 
			
		||||
		m->ofs_cause = offsetof(struct osmo_pfcp_msg, ies.session_est_resp.cause);
 | 
			
		||||
		m->ofs_node_id = offsetof(struct osmo_pfcp_msg, ies.session_est_resp.node_id);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		m->ofs_cause = 0;
 | 
			
		||||
		m->ofs_node_id = 0;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Decode a single PFCP message's header.
 | 
			
		||||
 *
 | 
			
		||||
 * If msg->l4h is non-NULL, decode at msgb_l4(msg). If l4h is NULL, decode at msgb_l3(msg).
 | 
			
		||||
 * In case of bundled PFCP messages, decode only one message and return the offset to the next message in the buffer.
 | 
			
		||||
 * Hence, to decode a message bundle, increment msg->l4h until all messages are decoded:
 | 
			
		||||
 *
 | 
			
		||||
 *   msg->l4h = msg->l3h;
 | 
			
		||||
 *   while (msgb_l4len(msg)) {
 | 
			
		||||
 *           struct osmo_pfcp_msg m;
 | 
			
		||||
 *           struct osmo_gtlv_load tlv;
 | 
			
		||||
 *           int rc;
 | 
			
		||||
 *           rc = osmo_pfcp_msg_decode_header(&tlv, &m, msg);
 | 
			
		||||
 *           if (rc < 0)
 | 
			
		||||
 *                  error();
 | 
			
		||||
 *           msg->l4h += rc;
 | 
			
		||||
 *
 | 
			
		||||
 *           if (osmo_pfcp_msg_decode_tlv(&m, &tlv))
 | 
			
		||||
 *                  error();
 | 
			
		||||
 *           handle(&m);
 | 
			
		||||
 *   }
 | 
			
		||||
 *
 | 
			
		||||
 * \param[out] tlv  Return TLV start pointer and length in tlv->src.*.
 | 
			
		||||
 * \param[inout] m  Place the decoded data in m->h; use m->ctx.* as logging context.
 | 
			
		||||
 * \param[in] msg  PFCP data to parse, possibly containing a PFCP message bundle.
 | 
			
		||||
 * \return total single PFCP message length (<= data_len) on success, negative on error.
 | 
			
		||||
 */
 | 
			
		||||
int osmo_pfcp_msg_decode_header(struct osmo_gtlv_load *tlv, struct osmo_pfcp_msg *m,
 | 
			
		||||
				const struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_header_parsed *parsed = &m->h;
 | 
			
		||||
	const uint8_t *pfcp_msg_data;
 | 
			
		||||
	unsigned int pfcp_msg_data_len;
 | 
			
		||||
	unsigned int header_len;
 | 
			
		||||
	unsigned int message_length;
 | 
			
		||||
	const struct osmo_pfcp_header_common *c;
 | 
			
		||||
 | 
			
		||||
	if (msg->l4h) {
 | 
			
		||||
		pfcp_msg_data = msgb_l4(msg);
 | 
			
		||||
		pfcp_msg_data_len = msgb_l4len(msg);
 | 
			
		||||
	} else {
 | 
			
		||||
		pfcp_msg_data = msgb_l3(msg);
 | 
			
		||||
		pfcp_msg_data_len = msgb_l3len(msg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!pfcp_msg_data || !pfcp_msg_data_len)
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "No Layer 3 data in this message buffer");
 | 
			
		||||
 | 
			
		||||
	if (pfcp_msg_data_len < sizeof(struct osmo_pfcp_header_common))
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "Message too short for PFCP header: %u", pfcp_msg_data_len);
 | 
			
		||||
 | 
			
		||||
	c = (void*)pfcp_msg_data;
 | 
			
		||||
 | 
			
		||||
	header_len = (c->seid_present ? sizeof(struct osmo_pfcp_header_seid) : sizeof(struct osmo_pfcp_header_no_seid));
 | 
			
		||||
	if (pfcp_msg_data_len < header_len)
 | 
			
		||||
		RETURN_ERROR(-EINVAL, "Message too short for PFCP header: %u", pfcp_msg_data_len);
 | 
			
		||||
 | 
			
		||||
	*parsed = (struct osmo_pfcp_header_parsed){
 | 
			
		||||
		.version = c->version,
 | 
			
		||||
		.priority_present = (bool)c->message_priority_present,
 | 
			
		||||
		.seid_present = (bool)c->seid_present,
 | 
			
		||||
		.message_type = c->message_type,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	m->is_response = osmo_pfcp_msgtype_is_response(parsed->message_type);
 | 
			
		||||
	osmo_pfcp_msg_set_memb_ofs(m);
 | 
			
		||||
 | 
			
		||||
	message_length = pfcp_header_get_message_length(c);
 | 
			
		||||
	if (message_length > pfcp_msg_data_len)
 | 
			
		||||
		RETURN_ERROR(-EMSGSIZE,
 | 
			
		||||
			     "The header's indicated total message length %u is larger than the received data %u",
 | 
			
		||||
			     message_length, pfcp_msg_data_len);
 | 
			
		||||
 | 
			
		||||
	/* T16L16V payload data and len */
 | 
			
		||||
	*tlv = (struct osmo_gtlv_load){
 | 
			
		||||
		.cfg = &osmo_t16l16v_cfg,
 | 
			
		||||
		.src = {
 | 
			
		||||
			.data = pfcp_msg_data + header_len,
 | 
			
		||||
			.len = message_length - header_len,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (c->follow_on) {
 | 
			
		||||
		/* Another PFCP message should follow */
 | 
			
		||||
		if (pfcp_msg_data_len - message_length < sizeof(struct osmo_pfcp_header_common))
 | 
			
		||||
			OSMO_LOG_PFCP_MSG(m, LOGL_INFO,
 | 
			
		||||
					  "PFCP message indicates more messages should follow in the bundle,"
 | 
			
		||||
					  " but remaining size %u is too short", pfcp_msg_data_len - message_length);
 | 
			
		||||
	} else {
 | 
			
		||||
		/* No more PFCP message should follow in the bundle */
 | 
			
		||||
		if (pfcp_msg_data_len > message_length)
 | 
			
		||||
			OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "Surplus data after PFCP message: %u",
 | 
			
		||||
					  pfcp_msg_data_len - message_length);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!parsed->seid_present) {
 | 
			
		||||
		const struct osmo_pfcp_header_no_seid *h_no_seid = (void*)pfcp_msg_data;
 | 
			
		||||
		parsed->sequence_nr = osmo_load32be_ext_2(h_no_seid->sequence_nr, 3);
 | 
			
		||||
		if (parsed->priority_present)
 | 
			
		||||
			RETURN_ERROR(-EINVAL, "Message Priority can only be present when the SEID is also present");
 | 
			
		||||
	} else {
 | 
			
		||||
		const struct osmo_pfcp_header_seid *h_seid = (void*)pfcp_msg_data;
 | 
			
		||||
		parsed->seid = osmo_load64be(&h_seid->session_endpoint_identifier);
 | 
			
		||||
		parsed->sequence_nr = osmo_load32be_ext_2(h_seid->sequence_nr, 3);
 | 
			
		||||
		if (parsed->priority_present)
 | 
			
		||||
			parsed->priority = h_seid->message_priority;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return message_length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_msg_err_cb(void *data, void *decoded_struct, const char *file, int line, const char *fmt, ...)
 | 
			
		||||
{
 | 
			
		||||
	va_list ap;
 | 
			
		||||
	if (log_check_level(DLPFCP, LOGL_ERROR)) {
 | 
			
		||||
		char *errmsg;
 | 
			
		||||
 | 
			
		||||
		va_start(ap, fmt);
 | 
			
		||||
		errmsg = talloc_vasprintf(OTC_SELECT, fmt, ap);
 | 
			
		||||
		va_end(ap);
 | 
			
		||||
		OSMO_LOG_PFCP_MSG_SRC((struct osmo_pfcp_msg *)data, LOGL_ERROR, file, line, "%s", errmsg);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_msg_decode_tlv(struct osmo_pfcp_msg *m, struct osmo_gtlv_load *tlv)
 | 
			
		||||
{
 | 
			
		||||
	return osmo_pfcp_ies_decode(&m->ies, tlv, false, m->h.message_type, osmo_pfcp_msg_err_cb, m, osmo_pfcp_iei_strs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int osmo_pfcp_msg_encode_tlv(struct msgb *msg, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_gtlv_put tlv = {
 | 
			
		||||
		.cfg = &osmo_t16l16v_cfg,
 | 
			
		||||
		.dst = msg,
 | 
			
		||||
	};
 | 
			
		||||
	return osmo_pfcp_ies_encode(&tlv, &m->ies, m->h.message_type, osmo_pfcp_msg_err_cb, (void*)m, osmo_pfcp_iei_strs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Append the encoded PFCP message to the message buffer.
 | 
			
		||||
 *
 | 
			
		||||
 * If msg->l3h is NULL, point it at the start of the encoded message.
 | 
			
		||||
 * Always point msg->l4h at the start of the newly encoded message.
 | 
			
		||||
 * Hence, in a message bundle, msg->l3h always points at the first PFCP message, while msg->l4h always points at the
 | 
			
		||||
 * last PFCP message.
 | 
			
		||||
 *
 | 
			
		||||
 * When adding a PFCP message to a bundle, set the Follow On (FO) flag of the previously last message to 1, and of the
 | 
			
		||||
 * newly encoded, now last message as 0.
 | 
			
		||||
 *
 | 
			
		||||
 * To log errors to a specific osmo_fsm_inst, point m->log_ctx to that instance before calling this function. Otherwise
 | 
			
		||||
 * set log_ctx = NULL.
 | 
			
		||||
 *
 | 
			
		||||
 * \return 0 on success, negative on error. */
 | 
			
		||||
int osmo_pfcp_msg_encode(struct msgb *msg, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_header_common *c;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Forming a bundle? If yes, set the Follow On flag of the currently last message to 1 */
 | 
			
		||||
	if (msg->l4h && msgb_l4len(msg)) {
 | 
			
		||||
		c = msgb_l4(msg);
 | 
			
		||||
		c->follow_on = 1;
 | 
			
		||||
	}
 | 
			
		||||
	/* Make sure l3h points at the first PFCP message in a message bundle */
 | 
			
		||||
	if (!msg->l3h)
 | 
			
		||||
		msg->l3h = msg->tail;
 | 
			
		||||
	/* Make sure l4h points at the last PFCP message in a message bundle */
 | 
			
		||||
	msg->l4h = msg->tail;
 | 
			
		||||
	c = (void*)msg->tail;
 | 
			
		||||
 | 
			
		||||
	rc = enc_pfcp_header(msg, m);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	rc = osmo_pfcp_msg_encode_tlv(msg, m);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* Update the header's message_length */
 | 
			
		||||
	rc = pfcp_header_set_message_length(c, msgb_l4len(msg));
 | 
			
		||||
	if (rc)
 | 
			
		||||
		RETURN_ERROR(rc, "Problem with PFCP message length");
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int osmo_pfcp_msg_destructor(struct osmo_pfcp_msg *m);
 | 
			
		||||
 | 
			
		||||
static struct osmo_pfcp_msg *_osmo_pfcp_msg_alloc(void *ctx, const struct osmo_sockaddr *remote_addr)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_msg *m = talloc(ctx, struct osmo_pfcp_msg);
 | 
			
		||||
	*m = (struct osmo_pfcp_msg){
 | 
			
		||||
		.remote_addr = *remote_addr,
 | 
			
		||||
		.h = {
 | 
			
		||||
			.version = 1,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
	talloc_set_destructor(m, osmo_pfcp_msg_destructor);
 | 
			
		||||
	return m;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_rx(void *ctx, const struct osmo_sockaddr *remote_addr)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_msg *rx = _osmo_pfcp_msg_alloc(ctx, remote_addr);
 | 
			
		||||
	rx->rx = true;
 | 
			
		||||
	return rx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_tx(void *ctx, const struct osmo_sockaddr *remote_addr,
 | 
			
		||||
					     const struct osmo_pfcp_ie_node_id *node_id,
 | 
			
		||||
					     const struct osmo_pfcp_msg *in_reply_to,
 | 
			
		||||
					     enum osmo_pfcp_message_type msg_type)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_msg *tx;
 | 
			
		||||
	if (!remote_addr && in_reply_to)
 | 
			
		||||
		remote_addr = &in_reply_to->remote_addr;
 | 
			
		||||
	OSMO_ASSERT(remote_addr);
 | 
			
		||||
	tx = _osmo_pfcp_msg_alloc(ctx, remote_addr);
 | 
			
		||||
	tx->is_response = osmo_pfcp_msgtype_is_response(msg_type);
 | 
			
		||||
	tx->h.message_type = msg_type;
 | 
			
		||||
	if (in_reply_to)
 | 
			
		||||
		tx->h.sequence_nr = in_reply_to->h.sequence_nr;
 | 
			
		||||
	osmo_pfcp_msg_set_memb_ofs(tx);
 | 
			
		||||
 | 
			
		||||
	/* Write the local node id data to the correct tx->ies.* member. */
 | 
			
		||||
	if (node_id) {
 | 
			
		||||
		struct osmo_pfcp_ie_node_id *tx_node_id = osmo_pfcp_msg_node_id(tx);
 | 
			
		||||
		if (tx_node_id)
 | 
			
		||||
			*tx_node_id = *node_id;
 | 
			
		||||
	}
 | 
			
		||||
	return tx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int osmo_pfcp_msg_destructor(struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_LOG_PFCP_MSG(m, LOGL_DEBUG, "discarding\n");
 | 
			
		||||
	if (m->ctx.session_use_count)
 | 
			
		||||
		osmo_use_count_get_put(m->ctx.session_use_count, m->ctx.session_use_token, -1);
 | 
			
		||||
	m->ctx.session_fi = NULL;
 | 
			
		||||
	m->ctx.session_use_count = NULL;
 | 
			
		||||
	m->ctx.session_use_token = NULL;
 | 
			
		||||
 | 
			
		||||
	if (m->ctx.peer_use_count)
 | 
			
		||||
		osmo_use_count_get_put(m->ctx.peer_use_count, m->ctx.peer_use_token, -1);
 | 
			
		||||
	m->ctx.peer_fi = NULL;
 | 
			
		||||
	m->ctx.peer_use_count = NULL;
 | 
			
		||||
	m->ctx.peer_use_token = NULL;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void osmo_pfcp_msg_free(struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	if (!m)
 | 
			
		||||
		return;
 | 
			
		||||
	talloc_free(m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ie_f_seid_cmp(const struct osmo_pfcp_ie_f_seid *a, const struct osmo_pfcp_ie_f_seid *b)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	if (a == b)
 | 
			
		||||
		return 0;
 | 
			
		||||
	if (!a)
 | 
			
		||||
		return -1;
 | 
			
		||||
	if (!b)
 | 
			
		||||
		return 1;
 | 
			
		||||
	/* It suffices if one of the IP addresses match */
 | 
			
		||||
	if (a->ip_addr.v4_present && b->ip_addr.v4_present)
 | 
			
		||||
		rc = osmo_sockaddr_cmp(&a->ip_addr.v4, &b->ip_addr.v4);
 | 
			
		||||
	else if (a->ip_addr.v6_present && b->ip_addr.v6_present)
 | 
			
		||||
		rc = osmo_sockaddr_cmp(&a->ip_addr.v6, &b->ip_addr.v6);
 | 
			
		||||
	else
 | 
			
		||||
		rc = (a->ip_addr.v4_present ? -1 : 1);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
	if (a->seid < b->seid)
 | 
			
		||||
		return -1;
 | 
			
		||||
	if (a->seid > b->seid)
 | 
			
		||||
		return 1;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_ip_addrs_set(struct osmo_pfcp_ip_addrs *dst, const struct osmo_sockaddr *addr)
 | 
			
		||||
{
 | 
			
		||||
	switch (addr->u.sas.ss_family) {
 | 
			
		||||
	case AF_INET:
 | 
			
		||||
		dst->v4_present = true;
 | 
			
		||||
		dst->v4 = *addr;
 | 
			
		||||
		return 0;
 | 
			
		||||
	case AF_INET6:
 | 
			
		||||
		dst->v6_present = true;
 | 
			
		||||
		dst->v6 = *addr;
 | 
			
		||||
		return 0;
 | 
			
		||||
	default:
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if 0
 | 
			
		||||
void osmo_pfcp_msg_invalidate_ctx(struct osmo_pfcp_msg *m, struct osmo_fsm_inst *deleted_fi)
 | 
			
		||||
{
 | 
			
		||||
	if (m->ctx.session_fi == deleted_fi) {
 | 
			
		||||
		m->ctx.session_fi = NULL;
 | 
			
		||||
		m->ctx.session_use_count = NULL;
 | 
			
		||||
		m->ctx.session_use_token = NULL;
 | 
			
		||||
	}
 | 
			
		||||
	if (m->ctx.peer_fi == deleted_fi) {
 | 
			
		||||
		m->ctx.peer_fi = NULL;
 | 
			
		||||
		m->ctx.peer_use_count = NULL;
 | 
			
		||||
		m->ctx.peer_use_token = NULL;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
int osmo_pfcp_msg_to_str_buf(char *buf, size_t buflen, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "PFCPv%u %s hdr={seq=%u", m->h.version, osmo_pfcp_message_type_str(m->h.message_type),
 | 
			
		||||
			   m->h.sequence_nr);
 | 
			
		||||
	if (m->h.priority_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " prio=%u", m->h.priority);
 | 
			
		||||
	if (m->h.seid_present)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " SEID=0x%"PRIx64, m->h.seid);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "} ies={");
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_pfcp_ies_encode_to_str, &m->ies, m->h.message_type, osmo_pfcp_iei_strs);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " }");
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *osmo_pfcp_msg_to_str_c(void *ctx, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 256, "ERROR", osmo_pfcp_msg_to_str_buf, m)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										492
									
								
								src/libosmo-pfcp/pfcp_strs.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										492
									
								
								src/libosmo-pfcp/pfcp_strs.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,492 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_strs.h>
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_message_type_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_MSGT_HEARTBEAT_REQ, "HEARTBEAT_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_HEARTBEAT_RESP, "HEARTBEAT_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_PFD_MGMT_REQ, "PFD_MGMT_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_PFD_MGMT_RESP, "PFD_MGMT_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_ASSOC_SETUP_REQ, "ASSOC_SETUP_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_ASSOC_SETUP_RESP, "ASSOC_SETUP_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_ASSOC_UPDATE_REQ, "ASSOC_UPDATE_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP, "ASSOC_UPDATE_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_ASSOC_RELEASE_REQ, "ASSOC_RELEASE_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP, "ASSOC_RELEASE_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_VERSION_NOT_SUPP_RESP, "VERSION_NOT_SUPP_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_NODE_REPORT_REQ, "NODE_REPORT_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_NODE_REPORT_RESP, "NODE_REPORT_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_SET_DEL_REQ, "SESSION_SET_DEL_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_SET_DEL_RESP, "SESSION_SET_DEL_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_EST_REQ, "SESSION_EST_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_EST_RESP, "SESSION_EST_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_MOD_REQ, "SESSION_MOD_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_MOD_RESP, "SESSION_MOD_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_DEL_REQ, "SESSION_DEL_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_DEL_RESP, "SESSION_DEL_RESP" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_REP_REQ, "SESSION_REP_REQ" },
 | 
			
		||||
	{ OSMO_PFCP_MSGT_SESSION_REP_RESP, "SESSION_REP_RESP" },
 | 
			
		||||
	{ 0 }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_iei_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_PDR, "Create PDR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PDI, "PDI" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_FAR, "Create FAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FORW_PARAMS, "Forwarding Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DUPL_PARAMS, "Duplicating Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_URR, "Create URR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_QER, "Create QER" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATED_PDR, "Created PDR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_PDR, "Update PDR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_FAR, "Update FAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_FORW_PARAMS, "Update Forwarding Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_BAR_SESS_REP_RESP, "Update BAR (PFCP Session Report Response)" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_URR, "Update URR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_QER, "Update QER" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_PDR, "Remove PDR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_FAR, "Remove FAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_URR, "Remove URR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_QER, "Remove QER" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CAUSE, "Cause" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SOURCE_IFACE, "Source Interface" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_F_TEID, "F-TEID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NETWORK_INST, "Network Instance" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SDF_FILTER, "SDF Filter" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_APPLICATION_ID, "Application ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_GATE_STATUS, "Gate Status" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MBR, "MBR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_GBR, "GBR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QER_CORRELATION_ID, "QER Correlation ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PRECEDENCE, "Precedence" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TRANSPORT_LEVEL_MARKING, "Transport Level Marking" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_VOLUME_THRESH, "Volume Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_THRESH, "Time Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MONITORING_TIME, "Monitoring Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SUBSEQUENT_VOLUME_THRESH, "Subsequent Volume Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SUBSEQUENT_TIME_THRESH, "Subsequent Time Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_INACT_DETECTION_TIME, "Inactivity Detection Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REPORTING_TRIGGERS, "Reporting Triggers" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REDIRECT_INFO, "Redirect Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REP_TYPE, "Report Type" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_OFFENDING_IE, "Offending IE" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FORW_POLICY, "Forwarding Policy" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DESTINATION_IFACE, "Destination Interface" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UP_FUNCTION_FEATURES, "UP Function Features" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_APPLY_ACTION, "Apply Action" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DL_DATA_SERVICE_INFO, "Downlink Data Service Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DL_DATA_NOTIFICATION_DELAY, "Downlink Data Notification Delay" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DL_BUFF_DURATION, "DL Buffering Duration" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DL_BUFF_SUGGESTED_PACKET_COUNT, "DL Buffering Suggested Packet Count" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCPSMREQ_FLAGS, "PFCPSMReq-Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCPSRRSP_FLAGS, "PFCPSRRsp-Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_LOAD_CTRL_INFO, "Load Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SEQUENCE_NUMBER, "Sequence Number" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_METRIC, "Metric" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_OVERLOAD_CTRL_INFO, "Overload Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIMER, "Timer" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PDR_ID, "PDR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_F_SEID, "F-SEID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_APPLICATION_IDS_PFDS, "Application ID's PFDs" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFD_CONTEXT, "PFD context" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NODE_ID, "Node ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFD_CONTENTS, "PFD contents" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MEAS_METHOD, "Measurement Method" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USAGE_REP_TRIGGER, "Usage Report Trigger" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MEAS_PERIOD, "Measurement Period" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FQ_CSID, "FQ-CSID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_VOLUME_MEAS, "Volume Measurement" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DURATION_MEAS, "Duration Measurement" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_APPLICATION_DETECTION_INFO, "Application Detection Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_OF_FIRST_PACKET, "Time of First Packet" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_OF_LAST_PACKET, "Time of Last Packet" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QUOTA_HOLDING_TIME, "Quota Holding Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DROPPED_DL_TRAFFIC_THRESH, "Dropped DL Traffic Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_VOLUME_QUOTA, "Volume Quota" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_QUOTA, "Time Quota" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_START_TIME, "Start Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_END_TIME, "End Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QUERY_URR, "Query URR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USAGE_REP_SESS_MOD_RESP, "Usage Report (Session Modification Response)" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USAGE_REP_SESS_DEL_RESP, "Usage Report (Session Deletion Response)" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USAGE_REP_SESS_REP_REQ, "Usage Report (Session Report Request)" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_URR_ID, "URR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_LINKED_URR_ID, "Linked URR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DL_DATA_REP, "Downlink Data Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_OUTER_HEADER_CREATION, "Outer Header Creation" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_BAR, "Create BAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_BAR_SESS_MOD_REQ, "Update BAR (Session Modification Request)" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_BAR, "Remove BAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_BAR_ID, "BAR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CP_FUNCTION_FEATURES, "CP Function Features" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USAGE_INFO, "Usage Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_APPLICATION_INST_ID, "Application Instance ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FLOW_INFO, "Flow Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UE_IP_ADDRESS, "UE IP Address" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PACKET_RATE, "Packet Rate" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_OUTER_HEADER_REMOVAL, "Outer Header Removal" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_RECOVERY_TIME_STAMP, "Recovery Time Stamp" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DL_FLOW_LEVEL_MARKING, "DL Flow Level Marking" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_HEADER_ENRICHMENT, "Header Enrichment" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ERROR_IND_REP, "Error Indication Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MEAS_INFO, "Measurement Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NODE_REP_TYPE, "Node Report Type" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USER_PLANE_PATH_FAILURE_REP, "User Plane Path Failure Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOTE_GTP_U_PEER, "Remote GTP-U Peer" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UR_SEQN, "UR-SEQN" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_DUPL_PARAMS, "Update Duplicating Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ACTIVATE_PREDEFINED_RULES, "Activate Predefined Rules" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DEACTIVATE_PREDEFINED_RULES, "Deactivate Predefined Rules" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FAR_ID, "FAR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QER_ID, "QER ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_OCI_FLAGS, "OCI Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCP_ASSOC_RELEASE_REQ, "PFCP Association Release Request" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_GRACEFUL_RELEASE_PERIOD, "Graceful Release Period" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PDN_TYPE, "PDN Type" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FAILED_RULE_ID, "Failed Rule ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_QUOTA_MECHANISM, "Time Quota Mechanism" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_RESERVED, "Reserved" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USER_PLANE_INACT_TIMER, "User Plane Inactivity Timer" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_AGGREGATED_URRS, "Aggregated URRs" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MULTIPLIER, "Multiplier" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_AGGREGATED_URR_ID, "Aggregated URR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SUBSEQUENT_VOLUME_QUOTA, "Subsequent Volume Quota" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SUBSEQUENT_TIME_QUOTA, "Subsequent Time Quota" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_RQI, "RQI" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QFI, "QFI" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QUERY_URR_REFERENCE, "Query URR Reference" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ADDITIONAL_USAGE_REPS_INFO, "Additional Usage Reports Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_TRAFFIC_ENDPOINT, "Create Traffic Endpoint" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATED_TRAFFIC_ENDPOINT, "Created Traffic Endpoint" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_TRAFFIC_ENDPOINT, "Update Traffic Endpoint" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_TRAFFIC_ENDPOINT, "Remove Traffic Endpoint" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TRAFFIC_ENDPOINT_ID, "Traffic Endpoint ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERNET_PACKET_FILTER, "Ethernet Packet Filter" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MAC_ADDRESS, "MAC address" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_C_TAG, "C-TAG" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_S_TAG, "S-TAG" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERTYPE, "Ethertype" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PROXYING, "Proxying" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERNET_FILTER_ID, "Ethernet Filter ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERNET_FILTER_PROPERTIES, "Ethernet Filter Properties" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SUGGESTED_BUFF_PACKETS_COUNT, "Suggested Buffering Packets Count" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USER_ID, "User ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERNET_PDU_SESS_INFO, "Ethernet PDU Session Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERNET_TRAFFIC_INFO, "Ethernet Traffic Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MAC_ADDRS_DETECTED, "MAC Addresses Detected" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MAC_ADDRS_REMOVED, "MAC Addresses Removed" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERNET_INACT_TIMER, "Ethernet Inactivity Timer" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ADDITIONAL_MONITORING_TIME, "Additional Monitoring Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_EVENT_QUOTA, "Event Quota" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_EVENT_THRESH, "Event Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SUBSEQUENT_EVENT_QUOTA, "Subsequent Event Quota" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SUBSEQUENT_EVENT_THRESH, "Subsequent Event Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TRACE_INFO, "Trace Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FRAMED_ROUTE, "Framed-Route" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FRAMED_ROUTING, "Framed-Routing" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_FRAMED_IPV6_ROUTE, "Framed-IPv6-Route" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_STAMP, "Time Stamp" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_AVERAGING_WINDOW, "Averaging Window" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PAGING_POLICY_INDICATOR, "Paging Policy Indicator" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_APN_DNN, "APN/DNN" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_3GPP_IFACE_TYPE, "3GPP Interface Type" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCPSRREQ_FLAGS, "PFCPSRReq-Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCPAUREQ_FLAGS, "PFCPAUReq-Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ACTIVATION_TIME, "Activation Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DEACTIVATION_TIME, "Deactivation Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_MAR, "Create MAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_3GPP_ACCESS_FORW_ACTION_INFO, "3GPP Access Forwarding Action Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NON_3GPP_ACCESS_FORW_ACTION_INFO, "Non-3GPP Access Forwarding Action Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_MAR, "Remove MAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_MAR, "Update MAR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MAR_ID, "MAR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_STEERING_FUNCTIONALITY, "Steering Functionality" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_STEERING_MODE, "Steering Mode" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_WEIGHT, "Weight" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PRIORITY, "Priority" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_3GPP_ACCESS_FORW_ACTION_INFO, "Update 3GPP Access Forwarding Action Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_NON_3GPP_ACCESS_FORW_ACTION_INFO, "Update Non 3GPP Access Forwarding Action Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UE_IP_ADDRESS_POOL_IDENTITY, "UE IP address Pool Identity" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ALTERNATIVE_SMF_IP_ADDRESS, "Alternative SMF IP Address" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PACKET_REPLICATION_AND_DETECTION_CARRY_ON_INFO, "Packet Replication and Detection Carry-On Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SMF_SET_ID, "SMF Set ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QUOTA_VALIDITY_TIME, "Quota Validity Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NUMBER_OF_REPS, "Number of Reports" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCP_SESS_RETENTION_INFO_IN_ASSOC_SETUP_REQ, "PFCP Session Retention Information (within PFCP Association Setup Request)" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCPASRSP_FLAGS, "PFCPASRsp-Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CP_ENTITY_IP_ADDRESS, "CP PFCP Entity IP Address" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCPSEREQ_FLAGS, "PFCPSEReq-Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_USER_PLANE_PATH_RECOVERY_REP, "User Plane Path Recovery Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_IP_MULTICAST_ADDR_INFO_IN_SESS_EST_REQ, "IP Multicast Addressing Info within PFCP Session Establishment Request" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_JOIN_IP_MULTICAST_INFO_IE_IN_USAGE_REP, "Join IP Multicast Information IE within Usage Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_LEAVE_IP_MULTICAST_INFO_IE_IN_USAGE_REP, "Leave IP Multicast Information IE within Usage Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_IP_MULTICAST_ADDRESS, "IP Multicast Address" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SOURCE_IP_ADDRESS, "Source IP Address" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PACKET_RATE_STATUS, "Packet Rate Status" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_BRIDGE_INFO_FOR_TSC, "Create Bridge Info for TSC" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATED_BRIDGE_INFO_FOR_TSC, "Created Bridge Info for TSC" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DS_TT_PORT_NUMBER, "DS-TT Port Number" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NW_TT_PORT_NUMBER, "NW-TT Port Number" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TSN_BRIDGE_ID, "TSN Bridge ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_MOD_REQ, "TSC Management Information IE within PFCP Session Modification Request" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_MOD_RESP, "TSC Management Information IE within PFCP Session Modification Response" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_REP_REQ, "TSC Management Information IE within PFCP Session Report Request" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PORT_MGMT_INFO_CONTAINER, "Port Management Information Container" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CLOCK_DRIFT_CTRL_INFO, "Clock Drift Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REQUESTED_CLOCK_DRIFT_INFO, "Requested Clock Drift Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CLOCK_DRIFT_REP, "Clock Drift Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TSN_TIME_DOMAIN_NUMBER, "TSN Time Domain Number" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_OFFSET_THRESH, "Time Offset Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CUMULATIVE_RATERATIO_THRESH, "Cumulative rateRatio Threshold" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TIME_OFFSET_MEAS, "Time Offset Measurement" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CUMULATIVE_RATERATIO_MEAS, "Cumulative rateRatio Measurement" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REMOVE_SRR, "Remove SRR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_CREATE_SRR, "Create SRR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPD_SRR, "Update SRR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SESS_REP, "Session Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_SRR_ID, "SRR ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ACCESS_AVAIL_CTRL_INFO, "Access Availability Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REQUESTED_ACCESS_AVAIL_INFO, "Requested Access Availability Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ACCESS_AVAIL_REP, "Access Availability Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ACCESS_AVAIL_INFO, "Access Availability Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PROVIDE_ATSSS_CTRL_INFO, "Provide ATSSS Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ATSSS_CTRL_PARAMS, "ATSSS Control Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MPTCP_CTRL_INFO, "MPTCP Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ATSSS_LL_CTRL_INFO, "ATSSS-LL Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PMF_CTRL_INFO, "PMF Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MPTCP_PARAMS, "MPTCP Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ATSSS_LL_PARAMS, "ATSSS-LL Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PMF_PARAMS, "PMF Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MPTCP_ADDRESS_INFO, "MPTCP Address Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UE_LINK_SPECIFIC_IP_ADDRESS, "UE Link-Specific IP Address" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PMF_ADDRESS_INFO, "PMF Address Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ATSSS_LL_INFO, "ATSSS-LL Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DATA_NETWORK_ACCESS_IDENTIFIER, "Data Network Access Identifier" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UE_IP_ADDRESS_POOL_INFO, "UE IP address Pool Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_AVERAGE_PACKET_DELAY, "Average Packet Delay" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MIN_PACKET_DELAY, "Minimum Packet Delay" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MAX_PACKET_DELAY, "Maximum Packet Delay" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QOS_REP_TRIGGER, "QoS Report Trigger" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_GTP_U_PATH_QOS_CTRL_INFO, "GTP-U Path QoS Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_GTP_U_PATH_QOS_REP_NODE_REP_REQ, "GTP-U Path QoS Report (PFCP Node Report Request)" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QOS_INFO_IN_GTP_U_PATH_QOS_REP, "QoS Information in GTP-U Path QoS Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_GTP_U_PATH_IFACE_TYPE, "GTP-U Path Interface Type" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QOS_MONITORING_PER_QOS_FLOW_CTRL_INFO, "QoS Monitoring per QoS flow Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REQUESTED_QOS_MONITORING, "Requested QoS Monitoring" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REPORTING_FREQUENCY, "Reporting Frequency" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PACKET_DELAY_THRESHOLDS, "Packet Delay Thresholds" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MIN_WAIT_TIME, "Minimum Wait Time" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QOS_MONITORING_REP, "QoS Monitoring Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QOS_MONITORING_MEAS, "QoS Monitoring Measurement" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MT_EDT_CTRL_INFO, "MT-EDT Control Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DL_DATA_PACKETS_SIZE, "DL Data Packets Size" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QER_CTRL_INDICATIONS, "QER Control Indications" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PACKET_RATE_STATUS_REP, "Packet Rate Status Report" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NF_INST_ID, "NF Instance ID" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_ETHERNET_CONTEXT_INFO, "Ethernet Context Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REDUNDANT_TRANSMISSION_PARAMS, "Redundant Transmission Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UPDATED_PDR, "Updated PDR" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_S_NSSAI, "S-NSSAI" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_IP_VERSION, "IP version" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PFCPASREQ_FLAGS, "PFCPASReq-Flags" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_DATA_STATUS, "Data Status" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PROVIDE_RDS_CONF_INFO, "Provide RDS configuration information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_RDS_CONF_INFO, "RDS configuration information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_QUERY_PACKET_RATE_STATUS_IE_IN_SESS_MOD_REQ, "Query Packet Rate Status IE within PFCP Session Modification Request" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_PACKET_RATE_STATUS_REP_IE_IN_SESS_MOD_RESP, "Packet Rate Status Report IE within PFCP Session Modification Response" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_MPTCP_APPLICABLE_IND, "MPTCP Applicable Indication" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_BRIDGE_MGMT_INFO_CONTAINER, "Bridge Management Information Container" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_UE_IP_ADDRESS_USAGE_INFO, "UE IP Address Usage Information" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_NUMBER_OF_UE_IP_ADDRS, "Number of UE IP Addresses" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_VALIDITY_TIMER, "Validity Timer" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_REDUNDANT_TRANSMISSION_FORW_PARAMS, "Redundant Transmission Forwarding Parameters" },
 | 
			
		||||
	{ OSMO_PFCP_IEI_TRANSPORT_DELAY_REPORTING, "Transport Delay Reporting" },
 | 
			
		||||
	{ 0 }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_cause_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_RESERVED, "0" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_REQUEST_ACCEPTED, "Request accepted (success)" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_MORE_USAGE_REPORT_TO_SEND, "More Usage Report to send" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_REQUEST_REJECTED, "Request rejected (reason not specified)" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND, "Session context not found" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_MANDATORY_IE_MISSING, "Mandatory IE missing" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_CONDITIONAL_IE_MISSING, "Conditional IE missing" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_INVALID_LENGTH, "Invalid length" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_MANDATORY_IE_INCORRECT, "Mandatory IE incorrect" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_INVALID_FORW_POLICY, "Invalid Forwarding Policy" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_INVALID_F_TEID_ALLOC_OPTION, "Invalid F-TEID allocation option" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC, "No established PFCP Association" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_RULE_CREATION_MOD_FAILURE, "Rule creation/modification Failure" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION, "PFCP entity in congestion" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_NO_RESOURCES_AVAILABLE, "No resources available" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED, "Service not supported" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_SYSTEM_FAILURE, "System failure" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_REDIRECTION_REQUESTED, "Redirection Requested" },
 | 
			
		||||
	{ OSMO_PFCP_CAUSE_ALL_DYNAMIC_ADDRESSES_ARE_OCCUPIED, "All dynamic addresses are occupied" },
 | 
			
		||||
	{ 0 }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_up_feature_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_BUCP, "BUCP" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_DDND, "DDND" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_DLBD, "DLBD" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_TRST, "TRST" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_FTUP, "FTUP" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_PFDM, "PFDM" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_HEEU, "HEEU" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_TREU, "TREU" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_EMPU, "EMPU" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_PDIU, "PDIU" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_UDBC, "UDBC" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_QUOAC, "QUOAC" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_TRACE, "TRACE" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_FRRT, "FRRT" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_PFDE, "PFDE" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_EPFAR, "EPFAR" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_DPDRA, "DPDRA" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_ADPDP, "ADPDP" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_UEIP, "UEIP" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_SSET, "SSET" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_MNOP, "MNOP" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_MTE, "MTE" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_BUNDL, "BUNDL" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_GCOM, "GCOM" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_MPAS, "MPAS" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_RTTL, "RTTL" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_VTIME, "VTIME" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_NORP, "NORP" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_IP6PL, "IP6PL" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_TSCU, "TSCU" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_MPTCP, "MPTCP" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_ATSSSLL, "ATSSSLL" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_QFQM, "QFQM" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_GPQM, "GPQM" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_MTEDT, "MTEDT" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_CIOT, "CIOT" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_ETHAR, "ETHAR" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_DDDS, "DDDS" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_RDS, "RDS" },
 | 
			
		||||
	{ OSMO_PFCP_UP_FEAT_RTTWP, "RTTWP" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_cp_feature_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_LOAD, "LOAD" },
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_OVRL, "OVRL" },
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_EPFAR, "EPFAR" },
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_SSET, "SSET" },
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_BUNDL, "BUNDL" },
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_MPAS, "MPAS" },
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_ARDR, "ARDR" },
 | 
			
		||||
	{ OSMO_PFCP_CP_FEAT_UIAUR, "UIAUR" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_apply_action_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_DROP, "DROP" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_FORW, "FORW" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_BUFF, "BUFF" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_NOCP, "NOCP" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_DUPL, "DUPL" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_IPMA, "IPMA" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_IPMD, "IPMD" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_DFRT, "DFRT" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_EDRT, "EDRT" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_BDPN, "BDPN" },
 | 
			
		||||
	{ OSMO_PFCP_APPLY_ACTION_DDPN, "DDPN" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_outer_header_creation_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, "GTP_U_UDP_IPV4" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV6, "GTP_U_UDP_IPV6" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV4, "UDP_IPV4" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV6, "UDP_IPV6" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_IPV4, "IPV4" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_IPV6, "IPV6" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_C_TAG, "C_TAG" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_S_TAG, "S_TAG" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_N19_INDICATION, "N19_INDICATION" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_CREATION_N6_INDICATION, "N6_INDICATION" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_outer_header_removal_desc_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4, "GTP_U_UDP_IPV4" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV6, "GTP_U_UDP_IPV6" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_UDP_IPV4, "UDP_IPV4" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_UDP_IPV6, "UDP_IPV6" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_IPV4, "IPV4" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_IPV6, "IPV6" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IP, "GTP_U_UDP_IP" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_VLAN_S_TAG, "VLAN_S_TAG" },
 | 
			
		||||
	{ OSMO_PFCP_OUTER_HEADER_REMOVAL_S_TAG_AND_C_TAG, "S_TAG_AND_C_TAG" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_source_iface_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_SOURCE_IFACE_ACCESS, "Access" },
 | 
			
		||||
	{ OSMO_PFCP_SOURCE_IFACE_CORE, "Core" },
 | 
			
		||||
	{ OSMO_PFCP_SOURCE_IFACE_SGI_LAN_N6_LAN, "SGi-LAN/N6-LAN" },
 | 
			
		||||
	{ OSMO_PFCP_SOURCE_IFACE_CP_FUNCTION, "CP-function" },
 | 
			
		||||
	{ OSMO_PFCP_SOURCE_IFACE_5G_VN_INTERNAL, "5G-VN-Internal" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_dest_iface_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_DEST_IFACE_ACCESS, "Access" },
 | 
			
		||||
	{ OSMO_PFCP_DEST_IFACE_CORE, "Core" },
 | 
			
		||||
	{ OSMO_PFCP_DEST_IFACE_SGI_LAN_N6_LAN, "SGi-LAN/N6-LAN" },
 | 
			
		||||
	{ OSMO_PFCP_DEST_IFACE_CP_FUNCTION, "CP-function" },
 | 
			
		||||
	{ OSMO_PFCP_DEST_IFACE_LI_FUNCTION, "LI-function" },
 | 
			
		||||
	{ OSMO_PFCP_DEST_IFACE_5G_VN_INTERNAL, "5G-VN-Internal" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string osmo_pfcp_3gpp_iface_type_strs[] = {
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S1_U, "S1_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S5_S8_U, "S5_S8_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S4_U, "S4_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S11_U, "S11_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S12_U, "S12_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_GN_GP_U, "GN_GP_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S2A_U, "S2A_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S2B_U, "S2B_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_ENODEB_GTP_U_INTERFACE_FOR_DL_DATA_FORWARDING, "ENODEB_GTP_U_INTERFACE_FOR_DL_DATA_FORWARDING" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_ENODEB_GTP_U_INTERFACE_FOR_UL_DATA_FORWARDING, "ENODEB_GTP_U_INTERFACE_FOR_UL_DATA_FORWARDING" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_SGW_UPF_GTP_U_INTERFACE_FOR_DL_DATA_FORWARDING, "SGW_UPF_GTP_U_INTERFACE_FOR_DL_DATA_FORWARDING" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_N3_3GPP_ACCESS, "N3_3GPP_ACCESS" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_N3_TRUSTED_NON_3GPP_ACCESS, "N3_TRUSTED_NON_3GPP_ACCESS" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_N3_UNTRUSTED_NON_3GPP_ACCESS, "N3_UNTRUSTED_NON_3GPP_ACCESS" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_N3_FOR_DATA_FORWARDING, "N3_FOR_DATA_FORWARDING" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_N9, "N9" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_SGI, "SGI" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_N6, "N6" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_N19, "N19" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_S8_U, "S8_U" },
 | 
			
		||||
	{ OSMO_PFCP_3GPP_IFACE_TYPE_GP_U, "GP_U" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
@@ -10,15 +10,10 @@ AM_CFLAGS = \
 | 
			
		||||
	$(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOVTY_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOCTRL_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOPFCP_CFLAGS) \
 | 
			
		||||
	$(COVERAGE_CFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_LDFLAGS = \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(LIBOSMOVTY_LIBS) \
 | 
			
		||||
	$(LIBOSMOCTRL_LIBS) \
 | 
			
		||||
	$(LIBOSMOPFCP_LIBS) \
 | 
			
		||||
	$(COVERAGE_LDFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
@@ -35,3 +30,12 @@ osmo_pfcp_tool_SOURCES = \
 | 
			
		||||
	pfcp_tool.c \
 | 
			
		||||
	pfcp_tool_vty.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
osmo_pfcp_tool_LDADD = \
 | 
			
		||||
	$(top_builddir)/src/libosmo-pfcp/libosmo-pfcp.a \
 | 
			
		||||
	$(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(LIBOSMOVTY_LIBS) \
 | 
			
		||||
	$(LIBOSMOCTRL_LIBS) \
 | 
			
		||||
	$(COVERAGE_LDFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 
 | 
			
		||||
@@ -206,16 +206,17 @@ static void signal_handler(int signum)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct vty_app_info pfcp_tool_vty_app_info = {
 | 
			
		||||
	.name = "osmo-pfcp-tool",
 | 
			
		||||
	.version = PACKAGE_VERSION,
 | 
			
		||||
	.copyright =
 | 
			
		||||
static const char * const osmo_pfcp_tool_copyright =
 | 
			
		||||
	"OsmoPFCPTool - Osmocom Packet Forwarding Control Protocol tool for testing\r\n"
 | 
			
		||||
	"Copyright (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>\r\n"
 | 
			
		||||
	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
 | 
			
		||||
	"This is free software: you are free to change and redistribute it.\r\n"
 | 
			
		||||
	"There is NO WARRANTY, to the extent permitted by law.\r\n",
 | 
			
		||||
	"There is NO WARRANTY, to the extent permitted by law.\r\n";
 | 
			
		||||
 | 
			
		||||
static struct vty_app_info pfcp_tool_vty_app_info = {
 | 
			
		||||
	.name = "osmo-pfcp-tool",
 | 
			
		||||
	.version = PACKAGE_VERSION,
 | 
			
		||||
	.copyright = osmo_pfcp_tool_copyright,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct log_info_cat pfcp_tool_default_categories[] = {
 | 
			
		||||
@@ -299,13 +300,13 @@ int main(int argc, char **argv)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* start telnet VTY */
 | 
			
		||||
	rc = telnet_init_default(tall_pfcp_tool_ctx, &g_pfcp_tool, OSMO_VTY_PORT_PFCP_TOOL);
 | 
			
		||||
	/* start telnet, after reading config for vty_get_bind_addr() */
 | 
			
		||||
	rc = telnet_init_dynif(tall_pfcp_tool_ctx, &g_pfcp_tool, vty_get_bind_addr(), 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);
 | 
			
		||||
	g_pfcp_tool->ctrl = ctrl_interface_setup_dynip(g_pfcp_tool, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_PFCP_TOOL, NULL);
 | 
			
		||||
	if (!g_pfcp_tool->ctrl) {
 | 
			
		||||
		fprintf(stderr, "Failed to initialize control interface. Exiting.\n");
 | 
			
		||||
		return -1;
 | 
			
		||||
@@ -343,7 +344,7 @@ int main(int argc, char **argv)
 | 
			
		||||
		do {
 | 
			
		||||
			if (pfcp_tool_mainloop())
 | 
			
		||||
				break;
 | 
			
		||||
		} while (osmo_pfcp_endpoint_retrans_queue_is_busy(g_pfcp_tool->ep));
 | 
			
		||||
		} while (!llist_empty(&g_pfcp_tool->ep->retrans_queue));
 | 
			
		||||
		printf("Done\n");
 | 
			
		||||
	} else {
 | 
			
		||||
		printf("Listening for commands on VTY...\n");
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ struct pfcp_tool_session *pfcp_tool_session_find(struct pfcp_tool_peer *peer, ui
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
							   enum up_gtp_action_kind gtp_action)
 | 
			
		||||
{
 | 
			
		||||
	struct pfcp_tool_session *session = pfcp_tool_session_find(peer, cp_seid);
 | 
			
		||||
	if (session)
 | 
			
		||||
@@ -95,7 +95,7 @@ struct pfcp_tool_session *pfcp_tool_session_find_or_create(struct pfcp_tool_peer
 | 
			
		||||
	*session = (struct pfcp_tool_session){
 | 
			
		||||
		.peer = peer,
 | 
			
		||||
		.cp_seid = cp_seid,
 | 
			
		||||
		.kind = kind,
 | 
			
		||||
		.gtp_action = gtp_action,
 | 
			
		||||
	};
 | 
			
		||||
	llist_add(&session->entry, &peer->sessions);
 | 
			
		||||
	return session;
 | 
			
		||||
@@ -146,7 +146,7 @@ static void rx_session_est_resp(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_
 | 
			
		||||
	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)
 | 
			
		||||
void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	switch (m->h.message_type) {
 | 
			
		||||
	case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP:
 | 
			
		||||
@@ -159,23 +159,14 @@ void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, st
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 (m->is_response)
 | 
			
		||||
		peer->last_resp = *m;
 | 
			
		||||
	else
 | 
			
		||||
		peer->last_req = *m;
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -51,43 +51,30 @@ struct pfcp_tool_peer {
 | 
			
		||||
	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_teid_pair {
 | 
			
		||||
	uint32_t local;
 | 
			
		||||
	uint32_t remote;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct pfcp_tool_session {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
 | 
			
		||||
	enum up_gtp_action_kind gtp_action;
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
	struct {
 | 
			
		||||
		struct pfcp_tool_teid_pair teid;
 | 
			
		||||
		struct osmo_sockaddr_str gtp_ip;
 | 
			
		||||
	} access;
 | 
			
		||||
 | 
			
		||||
		/* Tunnel-map GTP: translate from one TEID to another and forward */
 | 
			
		||||
		struct pfcp_tool_tunmap tunmap;
 | 
			
		||||
	};
 | 
			
		||||
	struct {
 | 
			
		||||
		struct pfcp_tool_teid_pair teid;
 | 
			
		||||
		struct osmo_sockaddr_str gtp_ip;
 | 
			
		||||
		struct osmo_sockaddr_str ue_addr;
 | 
			
		||||
	} core;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct g_pfcp_tool {
 | 
			
		||||
@@ -113,7 +100,7 @@ int pfcp_tool_mainloop();
 | 
			
		||||
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);
 | 
			
		||||
void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m);
 | 
			
		||||
 | 
			
		||||
int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m);
 | 
			
		||||
uint64_t peer_new_seid(struct pfcp_tool_peer *peer);
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ DEFUN(c_local_addr, c_local_addr_cmd,
 | 
			
		||||
{
 | 
			
		||||
	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)),
 | 
			
		||||
			osmo_sockaddr_to_str_c(OTC_SELECT, &g_pfcp_tool->ep->cfg.local_addr),
 | 
			
		||||
			VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
@@ -63,43 +63,38 @@ DEFUN(c_listen, c_listen_cmd,
 | 
			
		||||
      "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)),
 | 
			
		||||
			osmo_sockaddr_to_str_c(OTC_SELECT, &g_pfcp_tool->ep->cfg.local_addr),
 | 
			
		||||
			VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfg = (struct osmo_pfcp_endpoint_cfg){
 | 
			
		||||
		.rx_msg_cb = pfcp_tool_rx_msg,
 | 
			
		||||
	};
 | 
			
		||||
	g_pfcp_tool->ep = osmo_pfcp_endpoint_create(g_pfcp_tool, g_pfcp_tool);
 | 
			
		||||
	if (!g_pfcp_tool->ep) {
 | 
			
		||||
		vty_out(vty, "Failed to allocate PFCP endpoint.%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
	g_pfcp_tool->ep->rx_msg = pfcp_tool_rx_msg;
 | 
			
		||||
	g_pfcp_tool->ep->seq_nr_state = rand();
 | 
			
		||||
 | 
			
		||||
	/* 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);
 | 
			
		||||
	osmo_sockaddr_str_to_sockaddr(&local_addr, &g_pfcp_tool->ep->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());
 | 
			
		||||
	/* Store this address as the local PFCP Node Id */
 | 
			
		||||
	osmo_pfcp_ie_node_id_from_osmo_sockaddr(&g_pfcp_tool->ep->cfg.local_node_id, &g_pfcp_tool->ep->cfg.local_addr);
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
		vty_out(vty, "Failed to bind PFCP endpoint on %s: %s%s\n",
 | 
			
		||||
			osmo_sockaddr_to_str_c(OTC_SELECT, &g_pfcp_tool->ep->cfg.local_addr), strerror(rc),
 | 
			
		||||
			VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
@@ -108,7 +103,7 @@ DEFUN(c_listen, c_listen_cmd,
 | 
			
		||||
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")
 | 
			
		||||
	"Seconds to wait\n")
 | 
			
		||||
{
 | 
			
		||||
	int secs = atoi(argv[0]);
 | 
			
		||||
	int msecs = 0;
 | 
			
		||||
@@ -150,7 +145,7 @@ DEFUN(peer, peer_cmd,
 | 
			
		||||
	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);
 | 
			
		||||
	osmo_sockaddr_str_to_sockaddr(&remote_addr_str, (struct sockaddr_storage*)&remote_addr);
 | 
			
		||||
 | 
			
		||||
	peer = pfcp_tool_peer_find_or_create(&remote_addr);
 | 
			
		||||
 | 
			
		||||
@@ -198,8 +193,9 @@ DEFUN(peer_tx_assoc_setup_req, peer_tx_assoc_setup_req_cmd,
 | 
			
		||||
		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 = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL,
 | 
			
		||||
				   OSMO_PFCP_MSGT_ASSOC_SETUP_REQ);
 | 
			
		||||
	m->ies.assoc_setup_req.recovery_time_stamp = g_pfcp_tool->ep->recovery_time_stamp;
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
@@ -226,15 +222,13 @@ DEFUN(peer_retrans_req, peer_retrans_req_cmd,
 | 
			
		||||
		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);
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL, 0);
 | 
			
		||||
	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]);
 | 
			
		||||
	OSMO_LOG_PFCP_MSG(m, LOGL_DEBUG, "retrans %s\n", argv[0]);
 | 
			
		||||
 | 
			
		||||
	rc = osmo_pfcp_endpoint_tx_data(g_pfcp_tool->ep, m);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
@@ -250,26 +244,24 @@ static struct cmd_node session_node = {
 | 
			
		||||
	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)
 | 
			
		||||
      "session [(endecaps|tunmap)] [<0-18446744073709551615>]",
 | 
			
		||||
      "Enter the 'session' node for the given SEID\n"
 | 
			
		||||
      "Set up GTP tunnel encapsulation/decapsulation (default)\n"
 | 
			
		||||
      "Set up GTP tunnel mapping\n"
 | 
			
		||||
      "local Session Endpoint ID\n")
 | 
			
		||||
{
 | 
			
		||||
	struct pfcp_tool_peer *peer = vty->index;
 | 
			
		||||
	struct pfcp_tool_session *session;
 | 
			
		||||
	enum up_gtp_action_kind kind = UP_GTP_U_TUNEND;
 | 
			
		||||
	enum up_gtp_action_kind gtp_action = UP_GTP_U_ENDECAPS;
 | 
			
		||||
 | 
			
		||||
	if (argc > 0 && !strcmp(argv[0], "tunmap"))
 | 
			
		||||
		kind = UP_GTP_U_TUNMAP;
 | 
			
		||||
		gtp_action = UP_GTP_U_TUNMAP;
 | 
			
		||||
 | 
			
		||||
	if (argc > 1)
 | 
			
		||||
		session = pfcp_tool_session_find_or_create(peer, atoll(argv[1]), kind);
 | 
			
		||||
		session = pfcp_tool_session_find_or_create(peer, atoll(argv[1]), gtp_action);
 | 
			
		||||
	else
 | 
			
		||||
		session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), kind);
 | 
			
		||||
		session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer), gtp_action);
 | 
			
		||||
 | 
			
		||||
	vty->index = session;
 | 
			
		||||
	vty->node = SESSION_NODE;
 | 
			
		||||
@@ -277,132 +269,64 @@ DEFUN(session, session_cmd,
 | 
			
		||||
	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])) {
 | 
			
		||||
	if (osmo_sockaddr_str_from_str2(&session->core.ue_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")
 | 
			
		||||
DEFUN(s_teid, s_teid_cmd,
 | 
			
		||||
      "gtp (access|core) teid local <0-4294967295> remote <0-4294967295>",
 | 
			
		||||
      "Setup TEID used in GTP\n"
 | 
			
		||||
      "Set the TEIDs towards the ACCESS network (towards the radio network and the actual UE)\n"
 | 
			
		||||
      "Set the TEIDs towards the CORE network (towards the internet)\n"
 | 
			
		||||
      "Local TEID, which the UPF expects to see in incoming GTP packets\n"
 | 
			
		||||
      "Local TEID, when 0 tell the UPF to choose (PFCP: FAR F-TEID: CHOOSE=1)\n"
 | 
			
		||||
      "Remote TEID, which the UPF sends out in GTP packets\n"
 | 
			
		||||
      "Remote TEID, which the GTP peer has assigned for itself\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;
 | 
			
		||||
	struct pfcp_tool_teid_pair *dst;
 | 
			
		||||
	if (!strcmp(argv[0], "access"))
 | 
			
		||||
		dst = &session->access.teid;
 | 
			
		||||
	else
 | 
			
		||||
		dst = &session->core.teid;
 | 
			
		||||
	*dst = (struct pfcp_tool_teid_pair){
 | 
			
		||||
		.local = atoi(argv[1]),
 | 
			
		||||
		.remote = atoi(argv[2]),
 | 
			
		||||
	};
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
DEFUN(s_gtp, s_gtp_cmd,
 | 
			
		||||
      "gtp (access|core) ip A.B.C.D",
 | 
			
		||||
      "Setup GTP peer\n"
 | 
			
		||||
      "Set the GTP peer towards the ACCESS network (towards the radio network and the actual UE)\n"
 | 
			
		||||
      "Set the GTP peer towards the CORE network (towards the internet)\n"
 | 
			
		||||
      "Set the GTP peer IP address, where to send GTP packets to / receive GTP packets from\n"
 | 
			
		||||
      "GTP peer IP address\n")
 | 
			
		||||
{
 | 
			
		||||
	struct pfcp_tool_session *session = vty->index;
 | 
			
		||||
	struct osmo_sockaddr_str *dst;
 | 
			
		||||
	if (!strcmp(argv[0], "access"))
 | 
			
		||||
		dst = &session->access.gtp_ip;
 | 
			
		||||
	else
 | 
			
		||||
		dst = &session->core.gtp_ip;
 | 
			
		||||
	if (osmo_sockaddr_str_from_str2(dst, argv[1])) {
 | 
			
		||||
		vty_out(vty, "Error setting GTP IP address%s", 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,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
int session_endecaps_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
{
 | 
			
		||||
	struct pfcp_tool_session *session = vty->index;
 | 
			
		||||
	struct pfcp_tool_peer *peer = session->peer;
 | 
			
		||||
@@ -414,8 +338,6 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
	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) {
 | 
			
		||||
		vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
@@ -426,17 +348,12 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
	else
 | 
			
		||||
		osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true);
 | 
			
		||||
 | 
			
		||||
#define STR_TO_ADDR(DST, SRC) do { \
 | 
			
		||||
			if (osmo_sockaddr_str_to_sockaddr(&SRC, &DST.u.sas)) { \
 | 
			
		||||
				vty_out(vty, "Error in " #SRC ": " OSMO_SOCKADDR_STR_FMT "%s", \
 | 
			
		||||
					OSMO_SOCKADDR_STR_FMT_ARGS(&SRC), VTY_NEWLINE); \
 | 
			
		||||
				return CMD_WARNING; \
 | 
			
		||||
			} \
 | 
			
		||||
		} while (0)
 | 
			
		||||
	if (osmo_sockaddr_str_to_sockaddr(&session->core.ue_addr, &ue_addr.u.sas)) {
 | 
			
		||||
		vty_out(vty, "Error in UE IP%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	STR_TO_ADDR(ue_addr, session->tunend.core.ue_local_addr);
 | 
			
		||||
 | 
			
		||||
	if (session->tunend.access.local.teid == 0) {
 | 
			
		||||
	if (session->access.teid.local == 0) {
 | 
			
		||||
		f_teid_access_local = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
			.choose_flag = true,
 | 
			
		||||
			.choose = {
 | 
			
		||||
@@ -446,29 +363,37 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
	} else {
 | 
			
		||||
		f_teid_access_local = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
			.fixed = {
 | 
			
		||||
				.teid = session->tunend.access.local.teid,
 | 
			
		||||
				.teid = session->access.teid.local,
 | 
			
		||||
				.ip_addr = {
 | 
			
		||||
					.v4_present = true,
 | 
			
		||||
					.v4 = g_pfcp_tool->ep->cfg.local_addr,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
		STR_TO_ADDR(f_teid_access_local.fixed.ip_addr.v4, session->tunend.access.local.addr);
 | 
			
		||||
		if (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &f_teid_access_local.fixed.ip_addr.v4.u.sas)) {
 | 
			
		||||
			vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ohc_access = (struct osmo_pfcp_ie_outer_header_creation){
 | 
			
		||||
		.teid_present = true,
 | 
			
		||||
		.teid = session->tunend.access.remote.teid,
 | 
			
		||||
		.teid = session->access.teid.remote,
 | 
			
		||||
		.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);
 | 
			
		||||
	if (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &ohc_access.ip_addr.v4.u.sas)) {
 | 
			
		||||
		vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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));
 | 
			
		||||
	osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &g_pfcp_tool->ep->cfg.local_addr);
 | 
			
		||||
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_EST_REQ);
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL,
 | 
			
		||||
				   OSMO_PFCP_MSGT_SESSION_EST_REQ);
 | 
			
		||||
	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;
 | 
			
		||||
@@ -480,7 +405,7 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
		.create_pdr_count = 2,
 | 
			
		||||
		.create_pdr = {
 | 
			
		||||
			{
 | 
			
		||||
				.pdr_id = PDR_ID_CORE,
 | 
			
		||||
				.pdr_id = 1,
 | 
			
		||||
				.precedence = 255,
 | 
			
		||||
				.pdi = {
 | 
			
		||||
					.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
 | 
			
		||||
@@ -497,7 +422,7 @@ int session_tunend_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
				.far_id = 1,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				.pdr_id = PDR_ID_ACCESS,
 | 
			
		||||
				.pdr_id = 2,
 | 
			
		||||
				.precedence = 255,
 | 
			
		||||
				.pdi = {
 | 
			
		||||
					.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
 | 
			
		||||
@@ -570,7 +495,7 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
	else
 | 
			
		||||
		osmo_pfcp_bits_set(aa.bits, OSMO_PFCP_APPLY_ACTION_FORW, true);
 | 
			
		||||
 | 
			
		||||
	if (session->tunmap.access.local.teid == 0) {
 | 
			
		||||
	if (session->access.teid.local == 0) {
 | 
			
		||||
		f_teid_access_local = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
			.choose_flag = true,
 | 
			
		||||
			.choose = {
 | 
			
		||||
@@ -580,25 +505,31 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
	} else {
 | 
			
		||||
		f_teid_access_local = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
			.fixed = {
 | 
			
		||||
				.teid = session->tunmap.access.local.teid,
 | 
			
		||||
				.teid = session->access.teid.local,
 | 
			
		||||
				.ip_addr = {
 | 
			
		||||
					.v4_present = true,
 | 
			
		||||
					.v4 = osmo_pfcp_endpoint_get_cfg(g_pfcp_tool->ep)->local_addr,
 | 
			
		||||
					.v4 = g_pfcp_tool->ep->cfg.local_addr,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
		STR_TO_ADDR(f_teid_access_local.fixed.ip_addr.v4, session->tunmap.access.local.addr);
 | 
			
		||||
		if (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &f_teid_access_local.fixed.ip_addr.v4.u.sas)) {
 | 
			
		||||
			vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ohc_access = (struct osmo_pfcp_ie_outer_header_creation){
 | 
			
		||||
		.teid_present = true,
 | 
			
		||||
		.teid = session->tunmap.access.remote.teid,
 | 
			
		||||
		.teid = session->access.teid.remote,
 | 
			
		||||
		.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 (osmo_sockaddr_str_to_sockaddr(&session->access.gtp_ip, &ohc_access.ip_addr.v4.u.sas)) {
 | 
			
		||||
		vty_out(vty, "Error in GTP IP towards Access%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (session->tunmap.core.local.teid == 0) {
 | 
			
		||||
	if (session->core.teid.local == 0) {
 | 
			
		||||
		f_teid_core_local = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
			.choose_flag = true,
 | 
			
		||||
			.choose = {
 | 
			
		||||
@@ -608,28 +539,36 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
	} else {
 | 
			
		||||
		f_teid_core_local = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
			.fixed = {
 | 
			
		||||
				.teid = session->tunmap.core.local.teid,
 | 
			
		||||
				.teid = session->core.teid.local,
 | 
			
		||||
				.ip_addr = {
 | 
			
		||||
					.v4_present = true,
 | 
			
		||||
					.v4 = g_pfcp_tool->ep->cfg.local_addr,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
		STR_TO_ADDR(f_teid_core_local.fixed.ip_addr.v4, session->tunmap.core.local.addr);
 | 
			
		||||
		if (osmo_sockaddr_str_to_sockaddr(&session->core.gtp_ip, &f_teid_core_local.fixed.ip_addr.v4.u.sas)) {
 | 
			
		||||
			vty_out(vty, "Error in GTP IP towards Core%s", VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ohc_core = (struct osmo_pfcp_ie_outer_header_creation){
 | 
			
		||||
		.teid_present = true,
 | 
			
		||||
		.teid = session->tunmap.core.remote.teid,
 | 
			
		||||
		.teid = session->core.teid.remote,
 | 
			
		||||
		.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);
 | 
			
		||||
	if (osmo_sockaddr_str_to_sockaddr(&session->core.gtp_ip, &ohc_core.ip_addr.v4.u.sas)) {
 | 
			
		||||
		vty_out(vty, "Error in GTP IP towards Core%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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));
 | 
			
		||||
	osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &g_pfcp_tool->ep->cfg.local_addr);
 | 
			
		||||
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_EST_REQ);
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL,
 | 
			
		||||
				   OSMO_PFCP_MSGT_SESSION_EST_REQ);
 | 
			
		||||
	m->h.seid_present = true;
 | 
			
		||||
	m->h.seid = 0;
 | 
			
		||||
	/* GTP tunmap: remove header from both directions, and add header in both directions */
 | 
			
		||||
@@ -640,7 +579,7 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
		.create_pdr_count = 2,
 | 
			
		||||
		.create_pdr = {
 | 
			
		||||
			{
 | 
			
		||||
				.pdr_id = PDR_ID_CORE,
 | 
			
		||||
				.pdr_id = 1,
 | 
			
		||||
				.precedence = 255,
 | 
			
		||||
				.pdi = {
 | 
			
		||||
					.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
 | 
			
		||||
@@ -655,7 +594,7 @@ int session_tunmap_tx_est_req(struct vty *vty, const char **argv, int argc)
 | 
			
		||||
				.far_id = 1,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				.pdr_id = PDR_ID_ACCESS,
 | 
			
		||||
				.pdr_id = 2,
 | 
			
		||||
				.precedence = 255,
 | 
			
		||||
				.pdi = {
 | 
			
		||||
					.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
 | 
			
		||||
@@ -710,9 +649,9 @@ DEFUN(session_tx_est_req, session_tx_est_req_cmd,
 | 
			
		||||
      "Set FAR to DROP = 1\n")
 | 
			
		||||
{
 | 
			
		||||
	struct pfcp_tool_session *session = vty->index;
 | 
			
		||||
	switch (session->kind) {
 | 
			
		||||
	case UP_GTP_U_TUNEND:
 | 
			
		||||
		return session_tunend_tx_est_req(vty, argv, argc);
 | 
			
		||||
	switch (session->gtp_action) {
 | 
			
		||||
	case UP_GTP_U_ENDECAPS:
 | 
			
		||||
		return session_endecaps_tx_est_req(vty, argv, argc);
 | 
			
		||||
	case UP_GTP_U_TUNMAP:
 | 
			
		||||
		return session_tunmap_tx_est_req(vty, argv, argc);
 | 
			
		||||
	default:
 | 
			
		||||
@@ -747,9 +686,10 @@ DEFUN(session_tx_mod_req, session_tx_mod_req_cmd,
 | 
			
		||||
	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));
 | 
			
		||||
	osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &g_pfcp_tool->ep->cfg.local_addr);
 | 
			
		||||
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_MOD_REQ);
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL,
 | 
			
		||||
				   OSMO_PFCP_MSGT_SESSION_MOD_REQ);
 | 
			
		||||
	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){
 | 
			
		||||
@@ -792,7 +732,8 @@ DEFUN(session_tx_del_req, session_tx_del_req_cmd,
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, OSMO_PFCP_MSGT_SESSION_DEL_REQ);
 | 
			
		||||
	m = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr, &g_pfcp_tool->ep->cfg.local_node_id, NULL,
 | 
			
		||||
				   OSMO_PFCP_MSGT_SESSION_DEL_REQ);
 | 
			
		||||
	m->h.seid_present = true;
 | 
			
		||||
	m->h.seid = session->up_f_seid.seid;
 | 
			
		||||
 | 
			
		||||
@@ -833,13 +774,12 @@ void pfcp_tool_vty_init_cmds()
 | 
			
		||||
	install_element(PEER_NODE, &peer_retrans_req_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, &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_element(SESSION_NODE, &s_gtp_cmd);
 | 
			
		||||
	install_element(SESSION_NODE, &s_teid_cmd);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ AM_CFLAGS = \
 | 
			
		||||
	$(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOVTY_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOCTRL_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOPFCP_CFLAGS) \
 | 
			
		||||
	$(LIBGTPNL_CFLAGS) \
 | 
			
		||||
	$(LIBNFTNL_CFLAGS) \
 | 
			
		||||
	$(LIBNFTABLES_CFLAGS) \
 | 
			
		||||
@@ -18,15 +17,18 @@ AM_CFLAGS = \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_LDFLAGS = \
 | 
			
		||||
	$(LIBGTPNL_LDFLAGS) \
 | 
			
		||||
	$(LIBNFTNL_LDFLAGS) \
 | 
			
		||||
	$(LIBNFTABLES_LDFLAGS) \
 | 
			
		||||
	$(COVERAGE_LDFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
noinst_LTLIBRARIES = \
 | 
			
		||||
	libupf.la \
 | 
			
		||||
bin_PROGRAMS = \
 | 
			
		||||
	osmo-upf \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
libupf_la_SOURCES = \
 | 
			
		||||
	netinst.c \
 | 
			
		||||
osmo_upf_SOURCES = \
 | 
			
		||||
	osmo_upf_main.c \
 | 
			
		||||
	up_endpoint.c \
 | 
			
		||||
	up_gtp_action.c \
 | 
			
		||||
	up_peer.c \
 | 
			
		||||
@@ -38,24 +40,14 @@ libupf_la_SOURCES = \
 | 
			
		||||
	upf_vty.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
libupf_la_LIBADD = \
 | 
			
		||||
osmo_upf_LDADD = \
 | 
			
		||||
	$(top_builddir)/src/libosmo-pfcp/libosmo-pfcp.a \
 | 
			
		||||
	$(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(LIBOSMOVTY_LIBS) \
 | 
			
		||||
	$(LIBOSMOCTRL_LIBS) \
 | 
			
		||||
	$(LIBOSMOPFCP_LIBS) \
 | 
			
		||||
	$(LIBGTPNL_LIBS) \
 | 
			
		||||
	$(LIBNFTNL_LIBS) \
 | 
			
		||||
	$(LIBNFTABLES_LIBS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS = \
 | 
			
		||||
	osmo-upf \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
osmo_upf_SOURCES = \
 | 
			
		||||
	osmo_upf_main.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
osmo_upf_LDADD = \
 | 
			
		||||
	libupf.la \
 | 
			
		||||
	$(COVERAGE_LDFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,124 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/vty/vty.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/upf/netinst.h>
 | 
			
		||||
 | 
			
		||||
/* Add a new netinst entry to the given list.
 | 
			
		||||
 * \param ctx  talloc allocate new entry from ctx.
 | 
			
		||||
 * \param list  append to this list.
 | 
			
		||||
 * \param name  The Network Instance name as given in PFCP Network Instance IEs.
 | 
			
		||||
 * \param addr  IP address string of local interface to associate with the Network Instance.
 | 
			
		||||
 * \param errmsg  On error, an error description is returned in this out-argument.
 | 
			
		||||
 * \return new network_instance entry, or NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
const struct network_instance *netinst_add(void *ctx, struct llist_head *list, const char *name, const char *addr,
 | 
			
		||||
					   const char **errmsg)
 | 
			
		||||
{
 | 
			
		||||
	struct network_instance *netinst;
 | 
			
		||||
	if (errmsg)
 | 
			
		||||
		*errmsg = NULL;
 | 
			
		||||
 | 
			
		||||
	if (!name || !*name) {
 | 
			
		||||
		if (errmsg)
 | 
			
		||||
			*errmsg = "Network Instance name must not be empty";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (netinst_find(list, name)) {
 | 
			
		||||
		if (errmsg)
 | 
			
		||||
			*errmsg = "Network Instance entry with this name already exists";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	netinst = talloc(ctx, struct network_instance);
 | 
			
		||||
	*netinst = (struct network_instance){
 | 
			
		||||
		.name = talloc_strdup(netinst, name),
 | 
			
		||||
	};
 | 
			
		||||
	if (osmo_sockaddr_str_from_str(&netinst->addr, addr, 0)) {
 | 
			
		||||
		if (errmsg)
 | 
			
		||||
			*errmsg = "Network Instance address is not a valid IP address string";
 | 
			
		||||
		talloc_free(netinst);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	llist_add_tail(&netinst->entry, list);
 | 
			
		||||
 | 
			
		||||
	return netinst;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct network_instance *netinst_find(struct llist_head *list, const char *name)
 | 
			
		||||
{
 | 
			
		||||
	const struct network_instance *netinst;
 | 
			
		||||
 | 
			
		||||
	if (!name)
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(netinst, list, entry)
 | 
			
		||||
		if (!strcmp(netinst->name, name))
 | 
			
		||||
			return netinst;
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct network_instance *netinst_first(struct llist_head *list)
 | 
			
		||||
{
 | 
			
		||||
	return llist_first_entry_or_null(list, struct network_instance, entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Clear the list of Network Instance entries, return the nr of entries that were removed. */
 | 
			
		||||
int netinst_clear(struct llist_head *list)
 | 
			
		||||
{
 | 
			
		||||
	int count = 0;
 | 
			
		||||
	while (1) {
 | 
			
		||||
		struct network_instance *netinst = llist_first_entry_or_null(list, struct network_instance, entry);
 | 
			
		||||
		if (!netinst)
 | 
			
		||||
			break;
 | 
			
		||||
		llist_del(&netinst->entry);
 | 
			
		||||
		talloc_free(netinst);
 | 
			
		||||
		count++;
 | 
			
		||||
	}
 | 
			
		||||
	return count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Write one or all netinst entries to the VTY output.
 | 
			
		||||
 * If name_or_null is NULL, print all entries. Else, print only the entry matching that name.
 | 
			
		||||
 * Return number of printed entries. */
 | 
			
		||||
int netinst_vty_write(struct vty *vty, struct llist_head *list, const char *indent, const char *name_or_null)
 | 
			
		||||
{
 | 
			
		||||
	const struct network_instance *netinst;
 | 
			
		||||
	int count = 0;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(netinst, list, entry) {
 | 
			
		||||
		if (name_or_null && strcmp(netinst->name, name_or_null))
 | 
			
		||||
			continue;
 | 
			
		||||
		vty_out(vty, "%sadd %s %s%s", indent, netinst->name, netinst->addr.ip, VTY_NEWLINE);
 | 
			
		||||
		count++;
 | 
			
		||||
	}
 | 
			
		||||
	return count;
 | 
			
		||||
}
 | 
			
		||||
@@ -185,7 +185,7 @@ static void signal_handler(int signum)
 | 
			
		||||
		 * return, but program wouldn't exit if an external SIGABRT is
 | 
			
		||||
		 * received.
 | 
			
		||||
		 */
 | 
			
		||||
		talloc_report(tall_vty_ctx, stderr);
 | 
			
		||||
		//talloc_report(tall_vty_ctx, stderr);
 | 
			
		||||
		talloc_report_full(tall_upf_ctx, stderr);
 | 
			
		||||
		signal(SIGABRT, SIG_DFL);
 | 
			
		||||
		raise(SIGABRT);
 | 
			
		||||
@@ -304,13 +304,13 @@ int main(int argc, char **argv)
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* start telnet VTY */
 | 
			
		||||
	rc = telnet_init_default(tall_upf_ctx, &g_upf, OSMO_VTY_PORT_UPF);
 | 
			
		||||
	/* start telnet, after reading config for vty_get_bind_addr() */
 | 
			
		||||
	rc = telnet_init_dynif(tall_upf_ctx, &g_upf, vty_get_bind_addr(), OSMO_VTY_PORT_UPF);
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		return 2;
 | 
			
		||||
 | 
			
		||||
	/* start control interface, after reading config for ctrl_vty_get_bind_addr() */
 | 
			
		||||
	g_upf->ctrl = ctrl_interface_setup(g_upf, OSMO_CTRL_PORT_UPF, NULL);
 | 
			
		||||
	g_upf->ctrl = ctrl_interface_setup_dynip(g_upf, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_UPF, NULL);
 | 
			
		||||
	if (!g_upf->ctrl) {
 | 
			
		||||
		fprintf(stderr, "Failed to initialize control interface. Exiting.\n");
 | 
			
		||||
		return -1;
 | 
			
		||||
@@ -331,6 +331,9 @@ int main(int argc, char **argv)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (upf_gtp_genl_open())
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	if (upf_gtp_devs_open())
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
@@ -345,7 +348,7 @@ int main(int argc, char **argv)
 | 
			
		||||
		osmo_select_main_ctx(0);
 | 
			
		||||
 | 
			
		||||
		/* If the user hits Ctrl-C the third time, just terminate immediately. */
 | 
			
		||||
		if (quit >= 3)
 | 
			
		||||
		if (quit >= 1) //3)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		/* Has SIGTERM been received (and not yet been handled)? */
 | 
			
		||||
@@ -374,10 +377,10 @@ int main(int argc, char **argv)
 | 
			
		||||
	talloc_report_full(tall_infra_ctx, stderr);
 | 
			
		||||
	talloc_free(tall_infra_ctx);
 | 
			
		||||
 | 
			
		||||
	talloc_report(tall_vty_ctx, stderr);
 | 
			
		||||
	//talloc_report_full(tall_vty_ctx, stderr);
 | 
			
		||||
	talloc_free(tall_vty_ctx);
 | 
			
		||||
 | 
			
		||||
	talloc_report_full(NULL, stderr);
 | 
			
		||||
	//talloc_report_full(NULL, stderr);
 | 
			
		||||
	talloc_disable_null_tracking();
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,25 +23,17 @@
 | 
			
		||||
 | 
			
		||||
#include <osmocom/pfcp/pfcp_endpoint.h>
 | 
			
		||||
#include <osmocom/pfcp/pfcp_msg.h>
 | 
			
		||||
#include <osmocom/pfcp/pfcp_heartbeat_fsm.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/upf/up_endpoint.h>
 | 
			
		||||
#include <osmocom/upf/up_peer.h>
 | 
			
		||||
#include <osmocom/upf/up_session.h>
 | 
			
		||||
 | 
			
		||||
static void up_endpoint_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
 | 
			
		||||
static void up_endpoint_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct up_endpoint *up_ep = osmo_pfcp_endpoint_get_cfg(ep)->priv;
 | 
			
		||||
	struct up_endpoint *up_ep = ep->priv;
 | 
			
		||||
	struct up_peer *peer;
 | 
			
		||||
 | 
			
		||||
	/* If this is a response to an earlier request, just take the msg context from the request message. */
 | 
			
		||||
	if (req) {
 | 
			
		||||
		if (!m->ctx.peer_fi && req->ctx.peer_fi)
 | 
			
		||||
			up_peer_set_msg_ctx(req->ctx.peer_fi->priv, m);
 | 
			
		||||
		if (!m->ctx.session_fi && req->ctx.session_fi)
 | 
			
		||||
			up_session_set_msg_ctx(req->ctx.session_fi->priv, m);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* From the remote address, find the matching peer instance */
 | 
			
		||||
	if (!m->ctx.peer_fi) {
 | 
			
		||||
		peer = up_peer_find(up_ep, &m->remote_addr);
 | 
			
		||||
		if (peer) {
 | 
			
		||||
@@ -62,21 +54,50 @@ static void up_endpoint_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_not_impl_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m,
 | 
			
		||||
				  enum osmo_pfcp_message_type resp_msgt, enum osmo_pfcp_cause cause)
 | 
			
		||||
				  enum osmo_pfcp_message_type resp_msgt,
 | 
			
		||||
				  const struct osmo_pfcp_ie_node_id *node_id, enum osmo_pfcp_cause cause)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_msg *tx;
 | 
			
		||||
	enum osmo_pfcp_cause *tx_cause;
 | 
			
		||||
	OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "message type not implemented\n");
 | 
			
		||||
	tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, resp_msgt);
 | 
			
		||||
	tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, resp_msgt);
 | 
			
		||||
	tx_cause = osmo_pfcp_msg_cause(tx);
 | 
			
		||||
	if (tx_cause)
 | 
			
		||||
		*tx_cause = cause;
 | 
			
		||||
	osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
 | 
			
		||||
	return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_heartbeat_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct up_peer *peer = m->ctx.peer_fi ? m->ctx.peer_fi->priv : NULL;
 | 
			
		||||
	/* osmo_pfcp_endpoint_handle_rx() has already taken care of the heartbeat response. Just dispatch the event
 | 
			
		||||
	 * here. */
 | 
			
		||||
	/* If the peer is not associated / not known, we don't care that a heartbeat happened. */
 | 
			
		||||
	if (!peer || !peer->heartbeat_fi)
 | 
			
		||||
		return;
 | 
			
		||||
	osmo_fsm_inst_dispatch(peer->heartbeat_fi, OSMO_PFCP_HEARTBEAT_EV_RX_REQ, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_heartbeat_resp(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct up_peer *peer = m->ctx.peer_fi ? m->ctx.peer_fi->priv : NULL;
 | 
			
		||||
	if (!peer) {
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "Heartbeat response from unknown peer %s\n",
 | 
			
		||||
				  osmo_sockaddr_to_str_c(OTC_SELECT, &m->remote_addr));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!peer->heartbeat_fi) {
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "Heartbeat response, but peer is not associated %s\n",
 | 
			
		||||
				  osmo_sockaddr_to_str_c(OTC_SELECT, &m->remote_addr));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fsm_inst_dispatch(peer->heartbeat_fi, OSMO_PFCP_HEARTBEAT_EV_RX_RESP, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_pfd_mgmt_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_PFD_MGMT_RESP, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
	up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_PFD_MGMT_RESP, NULL, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_assoc_setup_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
@@ -86,7 +107,7 @@ static void up_ep_rx_assoc_setup_req(struct up_endpoint *up_ep, const struct osm
 | 
			
		||||
		peer = up_peer_find_or_add(up_ep, &m->remote_addr);
 | 
			
		||||
		OSMO_ASSERT(peer);
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fsm_inst_dispatch(peer->fi, UP_PEER_EV_RX_ASSOC_SETUP_REQ, (void *)m);
 | 
			
		||||
	osmo_fsm_inst_dispatch(peer->fi, UP_PEER_EV_RX_ASSOC_SETUP_REQ, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_assoc_upd_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
@@ -94,11 +115,12 @@ static void up_ep_rx_assoc_upd_req(struct up_endpoint *up_ep, const struct osmo_
 | 
			
		||||
	if (!m->ctx.peer_fi) {
 | 
			
		||||
		struct osmo_pfcp_msg *tx;
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot update association\n");
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP);
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP);
 | 
			
		||||
		/* FIXME set node_id, cause */
 | 
			
		||||
		osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_UPD_REQ, (void *)m);
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_UPD_REQ, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_assoc_rel_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
@@ -106,22 +128,24 @@ static void up_ep_rx_assoc_rel_req(struct up_endpoint *up_ep, const struct osmo_
 | 
			
		||||
	if (!m->ctx.peer_fi) {
 | 
			
		||||
		struct osmo_pfcp_msg *tx;
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated. Sending ACK response anyway\n");
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP);
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP);
 | 
			
		||||
		/* FIXME set node_id, cause */
 | 
			
		||||
		osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_REL_REQ, (void *)m);
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_REL_REQ, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_node_report_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_NODE_REPORT_RESP, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
	up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_NODE_REPORT_RESP, NULL /* FIXME? */,
 | 
			
		||||
			      OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_session_set_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_SESSION_SET_DEL_RESP,
 | 
			
		||||
			      OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
			      &up_ep->pfcp_ep->cfg.local_node_id, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_session_est_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
@@ -129,12 +153,12 @@ static void up_ep_rx_session_est_req(struct up_endpoint *up_ep, const struct osm
 | 
			
		||||
	if (!m->ctx.peer_fi) {
 | 
			
		||||
		struct osmo_pfcp_msg *tx;
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot establish session\n");
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_EST_RESP);
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_SESSION_EST_RESP);
 | 
			
		||||
		tx->ies.session_est_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
 | 
			
		||||
		osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_SESSION_EST_REQ, (void *)m);
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_SESSION_EST_REQ, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_session_mod_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
@@ -142,7 +166,8 @@ static void up_ep_rx_session_mod_req(struct up_endpoint *up_ep, const struct osm
 | 
			
		||||
	if (!m->ctx.session_fi) {
 | 
			
		||||
		/* Session not found. */
 | 
			
		||||
		struct osmo_pfcp_msg *tx;
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_MOD_RESP);
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m,
 | 
			
		||||
					    OSMO_PFCP_MSGT_SESSION_MOD_RESP);
 | 
			
		||||
		if (!m->ctx.peer_fi) {
 | 
			
		||||
			/* Not even the peer is associated. */
 | 
			
		||||
			OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot modify session\n");
 | 
			
		||||
@@ -156,7 +181,7 @@ static void up_ep_rx_session_mod_req(struct up_endpoint *up_ep, const struct osm
 | 
			
		||||
		osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_MOD_REQ, (void *)m);
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_MOD_REQ, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_session_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
@@ -164,7 +189,7 @@ static void up_ep_rx_session_del_req(struct up_endpoint *up_ep, const struct osm
 | 
			
		||||
	if (!m->ctx.session_fi) {
 | 
			
		||||
		/* Session not found. */
 | 
			
		||||
		struct osmo_pfcp_msg *tx;
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_DEL_RESP);
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_SESSION_DEL_RESP);
 | 
			
		||||
		if (!m->ctx.peer_fi) {
 | 
			
		||||
			/* Not even the peer is associated. */
 | 
			
		||||
			OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot delete session\n");
 | 
			
		||||
@@ -178,19 +203,25 @@ static void up_ep_rx_session_del_req(struct up_endpoint *up_ep, const struct osm
 | 
			
		||||
		osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_DEL_REQ, (void *)m);
 | 
			
		||||
	osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_DEL_REQ, (void*)m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_ep_rx_session_rep_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_SESSION_REP_RESP, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
	up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_SESSION_REP_RESP, NULL, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_endpoint_rx_cb(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
 | 
			
		||||
static void up_endpoint_rx_cb(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	struct up_endpoint *up_ep = osmo_pfcp_endpoint_get_priv(ep);
 | 
			
		||||
	struct up_endpoint *up_ep = ep->priv;
 | 
			
		||||
 | 
			
		||||
	switch (m->h.message_type) {
 | 
			
		||||
	case OSMO_PFCP_MSGT_HEARTBEAT_REQ:
 | 
			
		||||
		up_ep_rx_heartbeat_req(up_ep, m);
 | 
			
		||||
		return;
 | 
			
		||||
	case OSMO_PFCP_MSGT_HEARTBEAT_RESP:
 | 
			
		||||
		up_ep_rx_heartbeat_resp(up_ep, m);
 | 
			
		||||
		return;
 | 
			
		||||
	case OSMO_PFCP_MSGT_PFD_MGMT_REQ:
 | 
			
		||||
		up_ep_rx_pfd_mgmt_req(up_ep, m);
 | 
			
		||||
		return;
 | 
			
		||||
@@ -221,46 +252,35 @@ static void up_endpoint_rx_cb(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_ms
 | 
			
		||||
	case OSMO_PFCP_MSGT_SESSION_REP_REQ:
 | 
			
		||||
		up_ep_rx_session_rep_req(up_ep, m);
 | 
			
		||||
		return;
 | 
			
		||||
	case OSMO_PFCP_MSGT_HEARTBEAT_REQ:
 | 
			
		||||
	case OSMO_PFCP_MSGT_HEARTBEAT_RESP:
 | 
			
		||||
		/* Heartbeat is already handled in osmo_pfcp_endpoint_handle_rx() in pfcp_endpoint.c. The heartbeat
 | 
			
		||||
		 * messages are also dispatched here, to the rx_cb, "on informtional basis", nothing needs to happen
 | 
			
		||||
		 * here. */
 | 
			
		||||
		return;
 | 
			
		||||
	default:
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Unknown message type\n");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct up_endpoint *up_endpoint_alloc(void *ctx, const struct osmo_sockaddr *local_addr)
 | 
			
		||||
struct up_endpoint *up_endpoint_init(void *ctx, const struct osmo_sockaddr *local_addr)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_endpoint_cfg cfg;
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct up_endpoint *up_ep;
 | 
			
		||||
	up_ep = talloc_zero(ctx, struct up_endpoint);
 | 
			
		||||
	INIT_LLIST_HEAD(&up_ep->peers);
 | 
			
		||||
 | 
			
		||||
	cfg = (struct osmo_pfcp_endpoint_cfg){
 | 
			
		||||
		.local_addr = *local_addr,
 | 
			
		||||
		.set_msg_ctx_cb = up_endpoint_set_msg_ctx,
 | 
			
		||||
		.rx_msg_cb = up_endpoint_rx_cb,
 | 
			
		||||
		.priv = up_ep,
 | 
			
		||||
	};
 | 
			
		||||
	osmo_pfcp_ie_node_id_from_osmo_sockaddr(&cfg.local_node_id, local_addr);
 | 
			
		||||
	up_ep->pfcp_ep = osmo_pfcp_endpoint_create(up_ep, up_ep);
 | 
			
		||||
	up_ep->pfcp_ep->cfg.local_addr = *local_addr;
 | 
			
		||||
 | 
			
		||||
	up_ep->pfcp_ep = osmo_pfcp_endpoint_create(up_ep, &cfg);
 | 
			
		||||
	OSMO_ASSERT(up_ep->pfcp_ep);
 | 
			
		||||
	up_ep->pfcp_ep->set_msg_ctx = up_endpoint_set_msg_ctx;
 | 
			
		||||
	up_ep->pfcp_ep->rx_msg = up_endpoint_rx_cb;
 | 
			
		||||
 | 
			
		||||
	osmo_pfcp_ie_node_id_from_osmo_sockaddr(&up_ep->pfcp_ep->cfg.local_node_id, local_addr);
 | 
			
		||||
 | 
			
		||||
	rc = osmo_pfcp_endpoint_bind(up_ep->pfcp_ep);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		talloc_free(up_ep);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
	return up_ep;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int up_endpoint_bind(struct up_endpoint *up_ep)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_ASSERT(up_ep);
 | 
			
		||||
	OSMO_ASSERT(up_ep->pfcp_ep);
 | 
			
		||||
	return osmo_pfcp_endpoint_bind(up_ep->pfcp_ep);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct up_session *up_endpoint_find_session(struct up_endpoint *ep, uint64_t up_seid)
 | 
			
		||||
{
 | 
			
		||||
		struct up_peer *peer;
 | 
			
		||||
@@ -272,11 +292,30 @@ static struct up_session *up_endpoint_find_session(struct up_endpoint *ep, uint6
 | 
			
		||||
		return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t up_endpoint_next_up_seid(struct up_endpoint *ep)
 | 
			
		||||
static struct up_session *up_endpoint_find_session_by_local_teid(struct up_endpoint *ep, uint32_t teid)
 | 
			
		||||
{
 | 
			
		||||
		struct up_peer *peer;
 | 
			
		||||
		llist_for_each_entry(peer, &ep->peers, entry) {
 | 
			
		||||
			struct up_session *session = up_session_find_by_local_teid(peer, teid);
 | 
			
		||||
			if (session)
 | 
			
		||||
				return session;
 | 
			
		||||
		}
 | 
			
		||||
		return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint64_t up_endpoint_inc_seid(struct up_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	ep->next_seid_state++;
 | 
			
		||||
	if (!ep->next_seid_state)
 | 
			
		||||
		ep->next_seid_state++;
 | 
			
		||||
	return ep->next_seid_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t up_endpoint_next_seid(struct up_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	uint64_t sanity;
 | 
			
		||||
	for (sanity = 2342; sanity; sanity--) {
 | 
			
		||||
		uint64_t next_seid = osmo_pfcp_next_seid(&ep->next_up_seid_state);
 | 
			
		||||
		uint64_t next_seid = up_endpoint_inc_seid(ep);
 | 
			
		||||
		if (up_endpoint_find_session(ep, next_seid))
 | 
			
		||||
			continue;
 | 
			
		||||
		return next_seid;
 | 
			
		||||
@@ -284,6 +323,26 @@ uint64_t up_endpoint_next_up_seid(struct up_endpoint *ep)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint32_t up_endpoint_inc_teid(struct up_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	ep->next_teid_state++;
 | 
			
		||||
	if (!ep->next_teid_state)
 | 
			
		||||
		ep->next_teid_state++;
 | 
			
		||||
	return ep->next_teid_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t up_endpoint_next_teid(struct up_endpoint *ep)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t sanity;
 | 
			
		||||
	for (sanity = 2342; sanity; sanity--) {
 | 
			
		||||
		uint32_t next_teid = up_endpoint_inc_teid(ep);
 | 
			
		||||
		if (up_endpoint_find_session_by_local_teid(ep, next_teid))
 | 
			
		||||
			continue;
 | 
			
		||||
		return next_teid;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void up_endpoint_free(struct up_endpoint **_ep)
 | 
			
		||||
{
 | 
			
		||||
	struct up_peer *peer;
 | 
			
		||||
 
 | 
			
		||||
@@ -41,34 +41,31 @@ int up_gtp_action_cmp(const struct up_gtp_action *a, const struct up_gtp_action
 | 
			
		||||
	if (!b)
 | 
			
		||||
		return 1;
 | 
			
		||||
 | 
			
		||||
#define CMP_MEMB(MEMB) OSMO_CMP(a->MEMB, b->MEMB)
 | 
			
		||||
#define CMP_RET(MEMB) do { \
 | 
			
		||||
		int _cmp = OSMO_CMP(a->MEMB, b->MEMB); \
 | 
			
		||||
		if (_cmp) \
 | 
			
		||||
			return _cmp; \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
	if ((cmp = CMP_MEMB(kind)))
 | 
			
		||||
		return cmp;
 | 
			
		||||
	CMP_RET(kind);
 | 
			
		||||
 | 
			
		||||
	switch (a->kind) {
 | 
			
		||||
	case UP_GTP_U_TUNEND:
 | 
			
		||||
		if ((cmp = CMP_MEMB(tunend.access.local.teid)))
 | 
			
		||||
			return cmp;
 | 
			
		||||
		if ((cmp = CMP_MEMB(tunend.access.remote.teid)))
 | 
			
		||||
			return cmp;
 | 
			
		||||
		cmp = osmo_sockaddr_cmp(&a->tunend.access.remote.addr, &b->tunend.access.remote.addr);
 | 
			
		||||
	case UP_GTP_U_ENDECAPS:
 | 
			
		||||
		CMP_RET(endecaps.local_teid);
 | 
			
		||||
		CMP_RET(endecaps.remote_teid);
 | 
			
		||||
		cmp = osmo_sockaddr_cmp(&a->endecaps.gtp_remote_addr, &b->endecaps.gtp_remote_addr);
 | 
			
		||||
		if (cmp)
 | 
			
		||||
			return cmp;
 | 
			
		||||
		cmp = osmo_sockaddr_cmp(&a->tunend.core.ue_local_addr, &b->tunend.core.ue_local_addr);
 | 
			
		||||
		cmp = osmo_sockaddr_cmp(&a->endecaps.ue_addr, &b->endecaps.ue_addr);
 | 
			
		||||
		if (cmp)
 | 
			
		||||
			return cmp;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case UP_GTP_U_TUNMAP:
 | 
			
		||||
		if ((cmp = CMP_MEMB(tunmap.access.tun.local.teid)))
 | 
			
		||||
			return cmp;
 | 
			
		||||
		if ((cmp = CMP_MEMB(tunmap.access.tun.remote.teid)))
 | 
			
		||||
			return cmp;
 | 
			
		||||
		if ((cmp = CMP_MEMB(tunmap.core.tun.local.teid)))
 | 
			
		||||
			return cmp;
 | 
			
		||||
		if ((cmp = CMP_MEMB(tunmap.core.tun.remote.teid)))
 | 
			
		||||
			return cmp;
 | 
			
		||||
		CMP_RET(tunmap.access.local_teid);
 | 
			
		||||
		CMP_RET(tunmap.access.remote_teid);
 | 
			
		||||
		CMP_RET(tunmap.core.local_teid);
 | 
			
		||||
		CMP_RET(tunmap.core.remote_teid);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
@@ -79,61 +76,58 @@ int up_gtp_action_cmp(const struct up_gtp_action *a, const struct up_gtp_action
 | 
			
		||||
static int up_gtp_action_enable_disable(struct up_gtp_action *a, bool enable)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_dev *gtp_dev;
 | 
			
		||||
	const struct osmo_sockaddr *gtp_addr;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	switch (a->kind) {
 | 
			
		||||
	case UP_GTP_U_TUNEND:
 | 
			
		||||
		if (g_upf->tunend.mockup) {
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "tunend/mockup active, skipping GTP action %s\n",
 | 
			
		||||
					  enable ? "enable" : "disable");
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Pick GTP device matching the local F-TEID set up for the GTP tunnel (it is on the Access side) */
 | 
			
		||||
		gtp_addr = &a->tunend.access.local.addr;
 | 
			
		||||
		gtp_dev = upf_gtp_dev_find_by_local_addr(gtp_addr);
 | 
			
		||||
	case UP_GTP_U_ENDECAPS:
 | 
			
		||||
		/* use the first available GTP device.
 | 
			
		||||
		 * TODO: select by interface name?
 | 
			
		||||
		 */
 | 
			
		||||
		gtp_dev = upf_gtp_dev_first();
 | 
			
		||||
		if (!gtp_dev) {
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR, "No GTP device open for local address %s, cannot %s"
 | 
			
		||||
					  " -- consider configuring 'tunend' / 'dev (create|use) foo %s'\n",
 | 
			
		||||
					  osmo_sockaddr_to_str_c(OTC_SELECT, gtp_addr),
 | 
			
		||||
					  enable ? "enable" : "disable",
 | 
			
		||||
					  osmo_sockaddr_to_str_c(OTC_SELECT, gtp_addr));
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR, "No GTP device open, cannot %s\n", enable ? "enable" : "disable");
 | 
			
		||||
			return -EIO;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (enable)
 | 
			
		||||
			rc = upf_gtp_dev_tunend_add(gtp_dev, &a->tunend);
 | 
			
		||||
			rc = upf_gtp_dev_tunnel_add(gtp_dev, &a->endecaps);
 | 
			
		||||
		else
 | 
			
		||||
			rc = upf_gtp_dev_tunend_del(gtp_dev, &a->tunend);
 | 
			
		||||
			rc = upf_gtp_dev_tunnel_del(gtp_dev, &a->endecaps);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR, "Failed to %s GTP tunnel (rc=%d)\n",
 | 
			
		||||
					  enable ? "enable" : "disable", rc);
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR, "Failed to %s GTP tunnel: %d %s\n",
 | 
			
		||||
					  enable ? "enable" : "disable", rc, strerror(-rc));
 | 
			
		||||
			return rc;
 | 
			
		||||
		}
 | 
			
		||||
		LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "%s tunend on dev %s\n", enable ? "Enabled" : "Disabled",
 | 
			
		||||
				  gtp_dev->name);
 | 
			
		||||
		LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "%s GTP tunnel\n", enable ? "Enabled" : "Disabled");
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	case UP_GTP_U_TUNMAP:
 | 
			
		||||
		if (g_upf->tunmap.mockup) {
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "tunmap/mockup active, skipping nftables ruleset %s\n",
 | 
			
		||||
					  enable ? "enable" : "disable");
 | 
			
		||||
			return 0;
 | 
			
		||||
		if (enable && a->tunmap.id != 0) {
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR,
 | 
			
		||||
					  "Cannot enable: nft GTP tunnel mapping rule has been enabled before"
 | 
			
		||||
					  " as " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u\n", a->tunmap.id);
 | 
			
		||||
			return -EALREADY;
 | 
			
		||||
		}
 | 
			
		||||
		if (!enable && a->tunmap.id == 0) {
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR,
 | 
			
		||||
					  "Cannot disable: nft GTP tunnel mapping rule has not been enabled"
 | 
			
		||||
					  " (no " NFT_CHAIN_NAME_PREFIX_TUNMAP " id)\n");
 | 
			
		||||
			return -ENOENT;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (enable)
 | 
			
		||||
			rc = upf_nft_tunmap_create(&a->tunmap);
 | 
			
		||||
		else
 | 
			
		||||
			rc = upf_nft_tunmap_delete(&a->tunmap);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR, "Failed to %s nft GTP tunnel mapping (rc=%d)\n",
 | 
			
		||||
					  enable ? "enable" : "disable", rc);
 | 
			
		||||
			LOG_UP_GTP_ACTION(a, LOGL_ERROR,
 | 
			
		||||
					  "Failed to %s nft GTP tunnel mapping " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u:"
 | 
			
		||||
					  " %d %s\n", enable ? "enable" : "disable", a->tunmap.id, rc, strerror(-rc));
 | 
			
		||||
			return rc;
 | 
			
		||||
		}
 | 
			
		||||
		LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "%s tunmap, nft chain IDs: access--%u-> <-%u--core\n",
 | 
			
		||||
				  enable ? "Enabled" : "Disabled",
 | 
			
		||||
				  a->tunmap.access.chain_id, a->tunmap.core.chain_id);
 | 
			
		||||
		LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "%s nft GTP tunnel mapping " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u\n",
 | 
			
		||||
				  enable ? "Enabled" : "Disabled", a->tunmap.id);
 | 
			
		||||
		if (!enable)
 | 
			
		||||
			a->tunmap.id = 0;
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
@@ -156,28 +150,21 @@ int up_gtp_action_to_str_buf(char *buf, size_t buflen, const struct up_gtp_actio
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	switch (a->kind) {
 | 
			
		||||
	case UP_GTP_U_TUNEND:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "GTP:tunend GTP-access-r:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunend.access.remote.addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-access-r:0x%"PRIx32, a->tunend.access.remote.teid);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " GTP-access-l:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunend.access.local.addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-access-l:0x%"PRIx32" IP-core-l:", a->tunend.access.local.teid);
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunend.core.ue_local_addr);
 | 
			
		||||
	case UP_GTP_U_ENDECAPS:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "GTP:endecaps GTP-access:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->endecaps.gtp_remote_addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-r:0x%"PRIx32" TEID-l:0x%"PRIx32" IP-core:",
 | 
			
		||||
				   a->endecaps.remote_teid, a->endecaps.local_teid);
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->endecaps.ue_addr);
 | 
			
		||||
		break;
 | 
			
		||||
	case UP_GTP_U_TUNMAP:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "GTP:tunmap GTP-access-r:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.access.tun.remote.addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-access-r:0x%"PRIx32, a->tunmap.access.tun.remote.teid);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " GTP-access-l:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.access.tun.local.addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-access-l:0x%"PRIx32, a->tunmap.access.tun.local.teid);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " GTP-core-r:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.core.tun.remote.addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-core-r:0x%"PRIx32, a->tunmap.core.tun.remote.teid);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " GTP-core-l:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.core.tun.local.addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-core-l:0x%"PRIx32, a->tunmap.core.tun.local.teid);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "GTP:tunmap GTP-access:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.access.gtp_remote_addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-access-r:0x%"PRIx32" TEID-access-l:0x%"PRIx32" GTP-core:",
 | 
			
		||||
				   a->tunmap.access.remote_teid, a->tunmap.access.local_teid);
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.core.gtp_remote_addr);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " TEID-core-r:0x%"PRIx32" TEID-core-l:0x%"PRIx32,
 | 
			
		||||
				   a->tunmap.core.remote_teid, a->tunmap.core.local_teid);
 | 
			
		||||
		break;
 | 
			
		||||
	case UP_GTP_DROP:
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, "GTP:drop");
 | 
			
		||||
@@ -187,10 +174,9 @@ int up_gtp_action_to_str_buf(char *buf, size_t buflen, const struct up_gtp_actio
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	if (a->session)
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " PFCP-peer:%s SEID-l:0x%"PRIx64,
 | 
			
		||||
				   up_peer_remote_addr_str(a->session->up_peer), a->session->up_seid);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " PDR-access:%d", a->pdr_access);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " PDR-core:%d", a->pdr_core);
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " PFCP-peer:%s SEID-l:0x%"PRIx64" PDR:%d,%d",
 | 
			
		||||
				   up_peer_remote_addr_str(a->session->up_peer),
 | 
			
		||||
				   a->session->up_seid, a->pdr_core, a->pdr_access);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -198,33 +184,3 @@ char *up_gtp_action_to_str_c(void *ctx, const struct up_gtp_action *a)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 128, "ERROR", up_gtp_action_to_str_buf, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct up_gtp_action *up_gtp_action_alloc(void *ctx, struct up_session *session, enum up_gtp_action_kind kind, struct llist_head *dst)
 | 
			
		||||
{
 | 
			
		||||
	struct up_gtp_action *a = talloc_zero(ctx, struct up_gtp_action);
 | 
			
		||||
	OSMO_ASSERT(a);
 | 
			
		||||
	a->session = session;
 | 
			
		||||
	a->kind = kind;
 | 
			
		||||
 | 
			
		||||
	if (kind == UP_GTP_U_TUNMAP) {
 | 
			
		||||
		INIT_HLIST_NODE(&a->tunmap.access.node_by_chain_id);
 | 
			
		||||
		INIT_HLIST_NODE(&a->tunmap.core.node_by_chain_id);
 | 
			
		||||
	}
 | 
			
		||||
	llist_add_tail(&a->entry, dst);
 | 
			
		||||
	return a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void up_gtp_action_free(struct up_gtp_action *a)
 | 
			
		||||
{
 | 
			
		||||
	if (!a)
 | 
			
		||||
		return;
 | 
			
		||||
	up_gtp_action_disable(a);
 | 
			
		||||
	llist_del(&a->entry);
 | 
			
		||||
	if (a->kind == UP_GTP_U_TUNMAP) {
 | 
			
		||||
		if (!hlist_unhashed(&a->tunmap.access.node_by_chain_id))
 | 
			
		||||
			hash_del(&a->tunmap.access.node_by_chain_id);
 | 
			
		||||
		if (!hlist_unhashed(&a->tunmap.core.node_by_chain_id))
 | 
			
		||||
			hash_del(&a->tunmap.core.node_by_chain_id);
 | 
			
		||||
	}
 | 
			
		||||
	talloc_free(a);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,7 @@ static int up_peer_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count,
 | 
			
		||||
 | 
			
		||||
	LOGPFSMSLSRC(peer->fi, DREF, level, file, line,
 | 
			
		||||
		     "%s %s: now used by %s\n",
 | 
			
		||||
		     (e->count - old_use_count) > 0 ? "+" : "-", e->use,
 | 
			
		||||
		     (e->count - old_use_count) > 0? "+" : "-", e->use,
 | 
			
		||||
		     osmo_use_count_to_str_c(OTC_SELECT, &peer->use_count));
 | 
			
		||||
 | 
			
		||||
	if (e->count < 0)
 | 
			
		||||
@@ -101,11 +101,11 @@ static int up_peer_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count,
 | 
			
		||||
char *up_peer_remote_addr_str(struct up_peer *peer)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_sockaddr remote_addr = peer->remote_addr;
 | 
			
		||||
 | 
			
		||||
#if 1
 | 
			
		||||
	/* Zero the port, it is not interesting information. The port for PFCP is defined fixed, and there is no use
 | 
			
		||||
	 * printing it in the logs */
 | 
			
		||||
	osmo_sockaddr_set_port(&remote_addr.u.sa, 0);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
	return osmo_sockaddr_to_str_c(OTC_SELECT, &remote_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -169,6 +169,11 @@ struct up_peer *up_peer_find_or_add(struct up_endpoint *up_endpoint, const struc
 | 
			
		||||
	return up_peer_add(up_endpoint, remote_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int up_peer_tx(struct up_peer *peer, struct osmo_pfcp_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	return osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, m);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int up_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
 | 
			
		||||
{
 | 
			
		||||
	//struct up_peer *peer = fi->priv;
 | 
			
		||||
@@ -183,17 +188,15 @@ void up_peer_set_msg_ctx(struct up_peer *peer, struct osmo_pfcp_msg *m)
 | 
			
		||||
	m->ctx.peer_fi = peer->fi;
 | 
			
		||||
	m->ctx.peer_use_count = &peer->use_count;
 | 
			
		||||
	m->ctx.peer_use_token = (m->rx ? UP_USE_MSG_RX : UP_USE_MSG_TX);
 | 
			
		||||
	OSMO_ASSERT(osmo_use_count_get_put(m->ctx.peer_use_count, m->ctx.peer_use_token, 1) == 0);
 | 
			
		||||
	osmo_use_count_get_put(m->ctx.peer_use_count, m->ctx.peer_use_token, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_pfcp_msg *up_peer_init_tx(struct up_peer *peer, struct osmo_pfcp_msg *in_reply_to,
 | 
			
		||||
				      enum osmo_pfcp_message_type message_type)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_pfcp_msg *tx;
 | 
			
		||||
	if (in_reply_to)
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, in_reply_to, message_type);
 | 
			
		||||
	else
 | 
			
		||||
		tx = osmo_pfcp_msg_alloc_tx_req(OTC_SELECT, &peer->remote_addr, message_type);
 | 
			
		||||
	struct osmo_pfcp_msg *tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr,
 | 
			
		||||
							  &peer->up_endpoint->pfcp_ep->cfg.local_node_id,
 | 
			
		||||
							  in_reply_to, message_type);
 | 
			
		||||
	up_peer_set_msg_ctx(peer, tx);
 | 
			
		||||
	return tx;
 | 
			
		||||
}
 | 
			
		||||
@@ -206,14 +209,14 @@ static int up_peer_tx_assoc_setup_resp(struct up_peer *peer, struct osmo_pfcp_ms
 | 
			
		||||
 | 
			
		||||
	resp->ies.assoc_setup_resp = (struct osmo_pfcp_msg_assoc_setup_resp) {
 | 
			
		||||
		.cause = cause,
 | 
			
		||||
		.recovery_time_stamp = osmo_pfcp_endpoint_get_recovery_timestamp(g_upf->pfcp.ep->pfcp_ep),
 | 
			
		||||
		.recovery_time_stamp = g_upf->pfcp.ep->pfcp_ep->recovery_time_stamp,
 | 
			
		||||
		.up_function_features_present = true,
 | 
			
		||||
		.up_function_features = peer->local_up_features,
 | 
			
		||||
	};
 | 
			
		||||
	resp->ies.assoc_setup_resp.recovery_time_stamp = g_upf->pfcp.ep->pfcp_ep->recovery_time_stamp;
 | 
			
		||||
 | 
			
		||||
	if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp)) {
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Error sending response to this message,"
 | 
			
		||||
				  " cannot associate with peer\n");
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(resp, LOGL_ERROR, "Error sending response, cannot associate with peer\n");
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
@@ -230,7 +233,7 @@ static int up_peer_tx_assoc_rel_resp(struct up_peer *peer, struct osmo_pfcp_msg
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp)) {
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Error sending response to this message\n");
 | 
			
		||||
		OSMO_LOG_PFCP_MSG(resp, LOGL_ERROR, "Error sending response\n");
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
@@ -254,6 +257,9 @@ static void up_peer_rx_assoc_setup_req(struct up_peer *peer, struct osmo_pfcp_ms
 | 
			
		||||
	struct osmo_fsm_inst *fi = peer->fi;
 | 
			
		||||
	enum osmo_pfcp_cause cause = OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
 | 
			
		||||
 | 
			
		||||
	if (m->ies.assoc_setup_req.cp_function_features_present)
 | 
			
		||||
		peer->peer_cp_features = m->ies.assoc_setup_req.cp_function_features;
 | 
			
		||||
 | 
			
		||||
	if (fi->state == UP_PEER_ST_ASSOCIATED) {
 | 
			
		||||
		/* Retransmissions of the ACK response happen in pfcp_endpoint.c. So if we get this, it is a genuine
 | 
			
		||||
		 * duplicate association setup request. We could reject it. But why. Just "replace" with the new
 | 
			
		||||
@@ -267,25 +273,23 @@ static void up_peer_rx_assoc_setup_req(struct up_peer *peer, struct osmo_pfcp_ms
 | 
			
		||||
			LOGPFSML(fi, LOGL_NOTICE, "another Association Setup Request, with same Recovery Timestamp."
 | 
			
		||||
				 " Keeping sessions, sending ACK.\n");
 | 
			
		||||
		}
 | 
			
		||||
	} else if (up_peer_fsm_state_chg(UP_PEER_ST_ASSOCIATED)) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peer->remote_recovery_timestamp = m->ies.assoc_setup_req.recovery_time_stamp;
 | 
			
		||||
 | 
			
		||||
	if (up_peer_fsm_state_chg(UP_PEER_ST_ASSOCIATED)) {
 | 
			
		||||
		/* Not allowed to transition to ST_ASSOCIATED */
 | 
			
		||||
		cause = OSMO_PFCP_CAUSE_REQUEST_REJECTED;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Successfully transitioned to ST_ASSOCIATED */
 | 
			
		||||
		peer->remote_recovery_timestamp = m->ies.assoc_setup_req.recovery_time_stamp;
 | 
			
		||||
		peer->remote_node_id = m->ies.assoc_setup_req.node_id;
 | 
			
		||||
		if (m->ies.assoc_setup_req.cp_function_features_present)
 | 
			
		||||
			peer->peer_cp_features = m->ies.assoc_setup_req.cp_function_features;
 | 
			
		||||
		/* Remember the Node ID that the peer sent */
 | 
			
		||||
		struct osmo_pfcp_ie_node_id *m_node_id = osmo_pfcp_msg_node_id(m);
 | 
			
		||||
		OSMO_ASSERT(m_node_id);
 | 
			
		||||
		peer->remote_node_id = *m_node_id;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (up_peer_tx_assoc_setup_resp(peer, m, cause)
 | 
			
		||||
	    || cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED)
 | 
			
		||||
		up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
 | 
			
		||||
 | 
			
		||||
	LOGPFSML(fi, LOGL_NOTICE, "Peer associated, Node-Id=%s. Local UP features: [%s]; Peer CP features: [%s]\n",
 | 
			
		||||
		 osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, &peer->remote_node_id),
 | 
			
		||||
		 osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->local_up_features.bits, osmo_pfcp_up_feature_strs),
 | 
			
		||||
		 osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->peer_cp_features.bits, osmo_pfcp_cp_feature_strs));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_peer_rx_assoc_rel_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
 | 
			
		||||
@@ -299,7 +303,7 @@ static void up_peer_rx_session_est_req(struct up_peer *peer, struct osmo_pfcp_ms
 | 
			
		||||
{
 | 
			
		||||
	enum osmo_pfcp_cause cause = OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
 | 
			
		||||
	struct osmo_pfcp_msg *resp;
 | 
			
		||||
	struct up_session *session = up_session_find_or_add(peer, &m->ies.session_est_req.cp_f_seid);
 | 
			
		||||
	struct up_session *session = up_session_find_or_add(peer, &m->ies.session_est_req.cp_f_seid, NULL);
 | 
			
		||||
 | 
			
		||||
	if (!session) {
 | 
			
		||||
		cause = OSMO_PFCP_CAUSE_NO_RESOURCES_AVAILABLE;
 | 
			
		||||
@@ -322,8 +326,6 @@ nack_response:
 | 
			
		||||
		.cause = cause,
 | 
			
		||||
	};
 | 
			
		||||
	osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp);
 | 
			
		||||
	if (session)
 | 
			
		||||
		up_session_discard(session);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 | 
			
		||||
@@ -346,6 +348,14 @@ static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t eve
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_peer_associated_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
 | 
			
		||||
{
 | 
			
		||||
	struct up_peer *peer = fi->priv;
 | 
			
		||||
	LOGPFSML(fi, LOGL_NOTICE, "Peer associated. Local UP features: [%s]; Peer CP features: [%s]\n",
 | 
			
		||||
		 osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->local_up_features.bits, osmo_pfcp_up_feature_strs),
 | 
			
		||||
		 osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->peer_cp_features.bits, osmo_pfcp_cp_feature_strs));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct up_peer *peer = fi->priv;
 | 
			
		||||
@@ -379,10 +389,8 @@ static void up_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event,
 | 
			
		||||
 | 
			
		||||
static void up_peer_associated_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
 | 
			
		||||
{
 | 
			
		||||
	struct up_peer *peer = fi->priv;
 | 
			
		||||
	if (next_state != UP_PEER_ST_ASSOCIATED)
 | 
			
		||||
		LOGPFSML(fi, LOGL_NOTICE, "Peer %s released\n",
 | 
			
		||||
			 osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, &peer->remote_node_id));
 | 
			
		||||
		LOGPFSML(fi, LOGL_NOTICE, "Peer released\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void up_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
 | 
			
		||||
@@ -468,6 +476,7 @@ static const struct osmo_fsm_state up_peer_fsm_states[] = {
 | 
			
		||||
			| S(UP_PEER_ST_GRACEFUL_RELEASE)
 | 
			
		||||
			| S(UP_PEER_ST_WAIT_USE_COUNT)
 | 
			
		||||
			,
 | 
			
		||||
		.onenter = up_peer_associated_onenter,
 | 
			
		||||
		.action = up_peer_associated_action,
 | 
			
		||||
		.onleave = up_peer_associated_onleave,
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,6 @@
 | 
			
		||||
#include <osmocom/upf/up_peer.h>
 | 
			
		||||
#include <osmocom/upf/up_session.h>
 | 
			
		||||
#include <osmocom/upf/up_gtp_action.h>
 | 
			
		||||
#include <osmocom/upf/netinst.h>
 | 
			
		||||
 | 
			
		||||
static enum osmo_pfcp_cause up_session_setup_gtp(struct up_session *session);
 | 
			
		||||
 | 
			
		||||
@@ -48,7 +47,7 @@ void up_session_set_msg_ctx(struct up_session *session, struct osmo_pfcp_msg *m)
 | 
			
		||||
	m->ctx.session_fi = session->fi;
 | 
			
		||||
	m->ctx.session_use_count = &session->use_count;
 | 
			
		||||
	m->ctx.session_use_token = (m->rx ? UP_USE_MSG_RX : UP_USE_MSG_TX);
 | 
			
		||||
	OSMO_ASSERT(osmo_use_count_get_put(m->ctx.session_use_count, m->ctx.session_use_token, 1) == 0);
 | 
			
		||||
	osmo_use_count_get_put(m->ctx.session_use_count, m->ctx.session_use_token, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum up_session_fsm_state {
 | 
			
		||||
@@ -85,6 +84,7 @@ static const struct osmo_tdef_state_timeout up_session_fsm_timeouts[32] = {
 | 
			
		||||
 | 
			
		||||
static int up_session_fsm_timer_cb(struct osmo_fsm_inst *fi)
 | 
			
		||||
{
 | 
			
		||||
	//struct up_session *up_session = fi->priv;
 | 
			
		||||
	/* Return 1 to terminate FSM instance, 0 to keep running */
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
@@ -115,116 +115,46 @@ struct chosen_f_teid *chosen_f_teid_find(struct llist_head *list, uint8_t choose
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Find local interface's IP address by Network Instance name. Return 0 on success, or an OSMO_PFCP_CAUSE_* value on
 | 
			
		||||
 * failure. */
 | 
			
		||||
static int up_session_choose_local_ip(struct up_session *session, struct osmo_pfcp_ip_addrs *local_addr,
 | 
			
		||||
				      const char *netinst_name)
 | 
			
		||||
{
 | 
			
		||||
	const struct network_instance *netinst;
 | 
			
		||||
	struct osmo_sockaddr osa = {};
 | 
			
		||||
 | 
			
		||||
	if (llist_empty(&g_upf->netinst)) {
 | 
			
		||||
		/* No network instances are configured in osmo-upf.cfg. Instead use the local address configured for
 | 
			
		||||
		 * PFCP, assuming that in a simplistic setup the host has only one interface. It is unlikely to be
 | 
			
		||||
		 * useful for a production environment where the entire point is to hand packet data from one interface
 | 
			
		||||
		 * to another, and where PFCP most probably happens on an entirely different interface, but may make
 | 
			
		||||
		 * things simpler for lab testing. */
 | 
			
		||||
		if (osmo_pfcp_ip_addrs_set(local_addr,
 | 
			
		||||
					   osmo_pfcp_endpoint_get_local_addr(session->up_peer->up_endpoint->pfcp_ep))) {
 | 
			
		||||
			LOGPFSML(session->fi, LOGL_ERROR, "Invalid local address in pfcp_endpoint cfg\n");
 | 
			
		||||
			return OSMO_PFCP_CAUSE_SYSTEM_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		LOGPFSML(session->fi, LOGL_NOTICE,
 | 
			
		||||
			 "Cannot look up Network Instance %s: No 'netinst' is configured, setting up GTP on same local"
 | 
			
		||||
			 " interface as PFCP: %s (makes sense only for lab testing)\n",
 | 
			
		||||
			 osmo_quote_str_c(OTC_SELECT, netinst_name, -1),
 | 
			
		||||
			 osmo_pfcp_ip_addrs_to_str_c(OTC_SELECT, local_addr));
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!netinst_name || !*netinst_name) {
 | 
			
		||||
		/* Empty or no Network Instance IE in incoming PFCP request. Pick the first network instance; makes
 | 
			
		||||
		 * sense only in a simplistic lab setup where packet data is forwarded to the same interface that it is
 | 
			
		||||
		 * received on, and where no Network Instance is indicated by the CPF. Warn if more than one network
 | 
			
		||||
		 * instance is configured to choose from. */
 | 
			
		||||
		if (llist_count(&g_upf->netinst) > 1)
 | 
			
		||||
			LOGPFSML(session->fi, LOGL_NOTICE,
 | 
			
		||||
				 "Missing Network Instance in incoming request, using the first 'netinst' from cfg\n");
 | 
			
		||||
		netinst = netinst_first(&g_upf->netinst);
 | 
			
		||||
		/* there has to be a first entry, because we handled the empty list above. */
 | 
			
		||||
		OSMO_ASSERT(netinst);
 | 
			
		||||
	} else {
 | 
			
		||||
		netinst = netinst_find(&g_upf->netinst, netinst_name);
 | 
			
		||||
		if (!netinst) {
 | 
			
		||||
			LOGPFSML(session->fi, LOGL_ERROR, "Network Instance from PFCP request not found: %s"
 | 
			
		||||
				 " -- ensure there is a 'netinst' / 'add %s <ip-addr>' entry in your config\n",
 | 
			
		||||
				 osmo_quote_str_c(OTC_SELECT, netinst_name, -1),
 | 
			
		||||
				 osmo_escape_str_c(OTC_SELECT, netinst_name, -1));
 | 
			
		||||
			return OSMO_PFCP_CAUSE_RULE_CREATION_MOD_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Convert netinst IP address string first to osmo_sockaddr and then to osmo_pfcp_ip_addrs. */
 | 
			
		||||
	if (osmo_sockaddr_str_to_sockaddr(&netinst->addr, &osa.u.sas)
 | 
			
		||||
	    || osmo_pfcp_ip_addrs_set(local_addr, &osa)) {
 | 
			
		||||
		LOGPFSML(session->fi, LOGL_ERROR,
 | 
			
		||||
			 "Network Instance %s from PFCP request yields no valid IP address: "
 | 
			
		||||
				OSMO_SOCKADDR_STR_FMT "\n",
 | 
			
		||||
			 osmo_quote_str_c(OTC_SELECT, netinst_name, -1),
 | 
			
		||||
			 OSMO_SOCKADDR_STR_FMT_ARGS(&netinst->addr));
 | 
			
		||||
		return OSMO_PFCP_CAUSE_RULE_CREATION_MOD_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Choose an F-TEID (when the peer has sent CHOOSE = 1).
 | 
			
		||||
 * If the peer also sent a CHOOSE_ID, then remember this F-TEID choice under the given ID, and re-use that choice when
 | 
			
		||||
 * the same ID re-appears. The chosen IDs are saved in session->chosen_f_teids.
 | 
			
		||||
 * Return 0 on success, or an OSMO_PFCP_CAUSE_* value on failure. */
 | 
			
		||||
 * the same ID re-appears. The chosen IDs are saved in session->chosen_f_teids. */
 | 
			
		||||
static enum osmo_pfcp_cause up_session_choose_f_teid(struct up_session *session, struct osmo_pfcp_ie_f_teid *dst,
 | 
			
		||||
						     bool choose_id_present, uint8_t choose_id,
 | 
			
		||||
						     const char *netinst_name)
 | 
			
		||||
						     bool choose_id_present, uint8_t choose_id)
 | 
			
		||||
{
 | 
			
		||||
	struct chosen_f_teid *chosen;
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct up_endpoint *up_ep = session->up_peer->up_endpoint;
 | 
			
		||||
	struct chosen_f_teid *chosen = NULL;
 | 
			
		||||
 | 
			
		||||
	if (choose_id_present) {
 | 
			
		||||
	if (choose_id_present)
 | 
			
		||||
		chosen = chosen_f_teid_find(&session->chosen_f_teids, choose_id);
 | 
			
		||||
		if (chosen) {
 | 
			
		||||
			/* Re-use a previous F-TEID */
 | 
			
		||||
			*dst = chosen->f_teid;
 | 
			
		||||
			return OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
 | 
			
		||||
		}
 | 
			
		||||
		/* No previous F-TEID found, allocate a new one below */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*dst = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
		.choose_flag = false,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* Determine local IP address from Network Instance value received in PFCP request */
 | 
			
		||||
	rc = up_session_choose_local_ip(session, &dst->fixed.ip_addr, netinst_name);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* Choose a new TEID */
 | 
			
		||||
	dst->fixed.teid = upf_next_local_teid();
 | 
			
		||||
	if (dst->fixed.teid == 0) {
 | 
			
		||||
		LOGPFSML(session->fi, LOGL_ERROR, "Failed to allocate an unused TEID\n");
 | 
			
		||||
		return OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGPFSML(session->fi, LOGL_INFO, "Allocated new local F-TEID %s\n",
 | 
			
		||||
		 osmo_pfcp_ie_f_teid_to_str_c(OTC_SELECT, dst));
 | 
			
		||||
 | 
			
		||||
	/* Save this choice */
 | 
			
		||||
	if (choose_id_present) {
 | 
			
		||||
		chosen = talloc(session, struct chosen_f_teid);
 | 
			
		||||
		*chosen = (struct chosen_f_teid){
 | 
			
		||||
			.f_teid = *dst,
 | 
			
		||||
			.choose_id = choose_id,
 | 
			
		||||
	if (chosen) {
 | 
			
		||||
		/* Re-use a previous F-TEID */
 | 
			
		||||
		*dst = chosen->f_teid;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Choose a new F-TEID */
 | 
			
		||||
		*dst = (struct osmo_pfcp_ie_f_teid){
 | 
			
		||||
			.fixed = {
 | 
			
		||||
				.teid = up_endpoint_next_teid(up_ep),
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
		llist_add_tail(&chosen->entry, &session->chosen_f_teids);
 | 
			
		||||
		if (dst->fixed.teid == 0) {
 | 
			
		||||
			LOGPFSML(session->fi, LOGL_ERROR, "Failed to allocate an unused TEID\n");
 | 
			
		||||
			return OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION;
 | 
			
		||||
		}
 | 
			
		||||
		LOGPFSML(session->fi, LOGL_INFO, "Allocated new local TEID 0x%x\n", dst->fixed.teid);
 | 
			
		||||
 | 
			
		||||
		if (osmo_pfcp_ip_addrs_set(&dst->fixed.ip_addr, &up_ep->pfcp_ep->cfg.local_addr)) {
 | 
			
		||||
			LOGPFSML(session->fi, LOGL_ERROR, "Invalid local address in pfcp_endpoint cfg\n");
 | 
			
		||||
			return OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION;
 | 
			
		||||
		}
 | 
			
		||||
		/* Save this choice */
 | 
			
		||||
		if (choose_id_present) {
 | 
			
		||||
			chosen = talloc(session, struct chosen_f_teid);
 | 
			
		||||
			*chosen = (struct chosen_f_teid){
 | 
			
		||||
				.f_teid = *dst,
 | 
			
		||||
				.choose_id = choose_id,
 | 
			
		||||
			};
 | 
			
		||||
			llist_add_tail(&chosen->entry, &session->chosen_f_teids);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
 | 
			
		||||
}
 | 
			
		||||
@@ -259,23 +189,22 @@ static void far_upd(struct far *far, const struct osmo_pfcp_ie_upd_far *upd)
 | 
			
		||||
	if (upd->upd_forw_params_present) {
 | 
			
		||||
		const struct osmo_pfcp_ie_upd_forw_params *u = &upd->upd_forw_params;
 | 
			
		||||
		struct osmo_pfcp_ie_forw_params *p = &far->desc.forw_params;
 | 
			
		||||
		far->desc.forw_params_present = true;
 | 
			
		||||
		if (u->destination_iface_present)
 | 
			
		||||
			p->destination_iface = u->destination_iface;
 | 
			
		||||
		if (u->network_inst_present) {
 | 
			
		||||
			p->network_inst = u->network_inst;
 | 
			
		||||
			p->network_inst = p->network_inst;
 | 
			
		||||
			p->network_inst_present = true;
 | 
			
		||||
		}
 | 
			
		||||
		if (u->outer_header_creation_present) {
 | 
			
		||||
			p->outer_header_creation = u->outer_header_creation;
 | 
			
		||||
			p->outer_header_creation = p->outer_header_creation;
 | 
			
		||||
			p->outer_header_creation_present = true;
 | 
			
		||||
		}
 | 
			
		||||
		if (u->linked_te_id_present) {
 | 
			
		||||
			p->linked_te_id = u->linked_te_id;
 | 
			
		||||
			p->linked_te_id = p->linked_te_id;
 | 
			
		||||
			p->linked_te_id_present = true;
 | 
			
		||||
		}
 | 
			
		||||
		if (u->destination_iface_type_present) {
 | 
			
		||||
			p->destination_iface_type = u->destination_iface_type;
 | 
			
		||||
			p->destination_iface_type = p->destination_iface_type;
 | 
			
		||||
			p->destination_iface_type_present = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -308,9 +237,12 @@ static int far_to_str_buf(char *buf, size_t len, const struct far *far)
 | 
			
		||||
	if (f->forw_params_present) {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " dst:%s", osmo_pfcp_dest_iface_str(f->forw_params.destination_iface));
 | 
			
		||||
		if (f->forw_params.outer_header_creation_present) {
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, ",");
 | 
			
		||||
			OSMO_STRBUF_APPEND(sb, osmo_pfcp_ie_outer_header_creation_to_str_buf,
 | 
			
		||||
					   &f->forw_params.outer_header_creation);
 | 
			
		||||
			OSMO_STRBUF_PRINTF(sb, " encaps-");
 | 
			
		||||
			OSMO_STRBUF_APPEND(sb, osmo_pfcp_bits_to_str_buf,
 | 
			
		||||
					   f->forw_params.outer_header_creation.desc_bits,
 | 
			
		||||
					   osmo_pfcp_outer_header_creation_strs);
 | 
			
		||||
			if (f->forw_params.outer_header_creation.teid_present)
 | 
			
		||||
				OSMO_STRBUF_PRINTF(sb, " TEID-0x%x", f->forw_params.outer_header_creation.teid);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "}");
 | 
			
		||||
@@ -341,10 +273,6 @@ int pdr_to_str_buf(char *buf, size_t buflen, const struct pdr *pdr)
 | 
			
		||||
			OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &pdr->desc.pdi.ue_ip_address.ip_addr.v6);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (pdr->desc.pdi.network_inst_present) {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " netinst:");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_quote_str_buf3, pdr->desc.pdi.network_inst.str, -1);
 | 
			
		||||
	}
 | 
			
		||||
	if (pdr->local_f_teid) {
 | 
			
		||||
		OSMO_STRBUF_PRINTF(sb, " ");
 | 
			
		||||
		OSMO_STRBUF_APPEND(sb, osmo_pfcp_ie_f_teid_to_str_buf, pdr->local_f_teid);
 | 
			
		||||
@@ -379,8 +307,6 @@ static struct pdr *pdr_find(struct up_session *session, uint16_t pdr_id)
 | 
			
		||||
 | 
			
		||||
static void pdr_del(struct pdr *pdr)
 | 
			
		||||
{
 | 
			
		||||
	if (!hlist_unhashed(&pdr->node_by_local_f_teid))
 | 
			
		||||
		hash_del(&pdr->node_by_local_f_teid);
 | 
			
		||||
	llist_del(&pdr->entry);
 | 
			
		||||
	talloc_free(pdr);
 | 
			
		||||
}
 | 
			
		||||
@@ -390,7 +316,6 @@ static void pdr_set_far(struct pdr *pdr, struct far *far)
 | 
			
		||||
	pdr->far = far;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Set up a new Packet Detection Rule, append the response to the end of the created_pdr/created_pdr_count array. */
 | 
			
		||||
static struct pdr *pdr_create(struct up_session *session,
 | 
			
		||||
			      const struct osmo_pfcp_ie_create_pdr *create_pdr,
 | 
			
		||||
			      enum osmo_pfcp_cause *cause,
 | 
			
		||||
@@ -413,7 +338,6 @@ static struct pdr *pdr_create(struct up_session *session,
 | 
			
		||||
		.session = session,
 | 
			
		||||
		.desc = *create_pdr,
 | 
			
		||||
	};
 | 
			
		||||
	INIT_HLIST_NODE(&pdr->node_by_local_f_teid);
 | 
			
		||||
	llist_add_tail(&pdr->entry, &session->pdrs);
 | 
			
		||||
 | 
			
		||||
	if (pdr->desc.far_id_present) {
 | 
			
		||||
@@ -448,13 +372,9 @@ static struct pdr *pdr_create(struct up_session *session,
 | 
			
		||||
		if (pdr->desc.pdi.local_f_teid.choose_flag) {
 | 
			
		||||
			/* CHOOSE = 1: we need to pick our own local F-TEID */
 | 
			
		||||
			struct osmo_pfcp_ie_f_teid local_f_teid;
 | 
			
		||||
			const char *netinst_name = NULL;
 | 
			
		||||
			if (pdr->desc.pdi.network_inst_present)
 | 
			
		||||
				netinst_name = pdr->desc.pdi.network_inst.str;
 | 
			
		||||
			*cause = up_session_choose_f_teid(session, &local_f_teid,
 | 
			
		||||
							  pdr->desc.pdi.local_f_teid.choose.choose_id_present,
 | 
			
		||||
							  pdr->desc.pdi.local_f_teid.choose.choose_id,
 | 
			
		||||
							  netinst_name);
 | 
			
		||||
							  pdr->desc.pdi.local_f_teid.choose.choose_id);
 | 
			
		||||
			if (*cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) {
 | 
			
		||||
				*offending_ie = OSMO_PFCP_IEI_F_TEID;
 | 
			
		||||
				*offending_ie_present = true;
 | 
			
		||||
@@ -485,7 +405,6 @@ static struct pdr *pdr_create(struct up_session *session,
 | 
			
		||||
			.local_f_teid_present = true,
 | 
			
		||||
			.local_f_teid = *pdr->local_f_teid,
 | 
			
		||||
		};
 | 
			
		||||
		hash_add(g_upf->gtp.pdrs_by_local_f_teid, &pdr->node_by_local_f_teid, pdr->local_f_teid->fixed.teid);
 | 
			
		||||
	} else {
 | 
			
		||||
		created_pdr[*created_pdr_count] = (struct osmo_pfcp_ie_created_pdr){
 | 
			
		||||
			.pdr_id = pdr->desc.pdr_id,
 | 
			
		||||
@@ -563,7 +482,8 @@ static struct pdr *pdr_upd(struct pdr *pdr,
 | 
			
		||||
	return pdr;
 | 
			
		||||
 | 
			
		||||
nack_resp:
 | 
			
		||||
	pdr_del(pdr);
 | 
			
		||||
	if (pdr)
 | 
			
		||||
		pdr_del(pdr);
 | 
			
		||||
	if (!*offending_ie_present) {
 | 
			
		||||
		*offending_ie = OSMO_PFCP_IEI_UPD_PDR;
 | 
			
		||||
		*offending_ie_present = true;
 | 
			
		||||
@@ -631,25 +551,18 @@ static void up_session_est(struct up_session *session, struct osmo_pfcp_msg *m)
 | 
			
		||||
		goto nack_response;
 | 
			
		||||
 | 
			
		||||
	/* Success, send ACK */
 | 
			
		||||
	osmo_pfcp_ie_f_seid_set(&resp->up_f_seid, session->up_seid,
 | 
			
		||||
				osmo_pfcp_endpoint_get_local_addr(peer->up_endpoint->pfcp_ep));
 | 
			
		||||
	osmo_pfcp_ie_f_seid_set(&resp->up_f_seid, session->up_seid, &peer->up_endpoint->pfcp_ep->cfg.local_addr);
 | 
			
		||||
	resp->up_f_seid_present = true;
 | 
			
		||||
 | 
			
		||||
	rc = osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, tx);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		/* sending ACK failed, discard session. It might seem like a good idea to keep the session around,
 | 
			
		||||
		 * because the creation succeeded, only the ACK failed. But in the greater scheme of things, if we
 | 
			
		||||
		 * cannot ACK to the PFCP peer, all is lost. Rather not keep stale sessions around. */
 | 
			
		||||
	if (rc)
 | 
			
		||||
		up_session_fsm_state_chg(UP_SESSION_ST_WAIT_USE_COUNT);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	up_session_fsm_state_chg(UP_SESSION_ST_ESTABLISHED);
 | 
			
		||||
	return;
 | 
			
		||||
 | 
			
		||||
nack_response:
 | 
			
		||||
	resp->created_pdr_count = 0;
 | 
			
		||||
	osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, tx);
 | 
			
		||||
	/* No matter if sending the NACK succeeded or not, discard the session. */
 | 
			
		||||
	up_session_fsm_state_chg(UP_SESSION_ST_WAIT_USE_COUNT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -730,13 +643,8 @@ static void up_session_mod(struct up_session *session, struct osmo_pfcp_msg *m)
 | 
			
		||||
		goto nack_response;
 | 
			
		||||
 | 
			
		||||
	/* Success, send ACK */
 | 
			
		||||
	if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, tx)) {
 | 
			
		||||
		/* sending ACK failed, discard session. It might seem like a good idea to keep the session around,
 | 
			
		||||
		 * because the modification succeeded, only the ACK failed. But in the greater scheme of things, if we
 | 
			
		||||
		 * cannot ACK to the PFCP peer, all is lost. Rather not keep stale sessions around. */
 | 
			
		||||
	if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, tx))
 | 
			
		||||
		up_session_fsm_state_chg(UP_SESSION_ST_WAIT_USE_COUNT);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGPFSML(fi, LOGL_NOTICE, "Session modified: %s\n", up_session_gtp_status(session));
 | 
			
		||||
	return;
 | 
			
		||||
@@ -744,7 +652,6 @@ static void up_session_mod(struct up_session *session, struct osmo_pfcp_msg *m)
 | 
			
		||||
nack_response:
 | 
			
		||||
	resp->created_pdr_count = 0;
 | 
			
		||||
	osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, tx);
 | 
			
		||||
	/* No matter if sending the NACK succeeded or not, discard the session. */
 | 
			
		||||
	up_session_fsm_state_chg(UP_SESSION_ST_WAIT_USE_COUNT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -759,7 +666,6 @@ static void up_session_del(struct up_session *session, struct osmo_pfcp_msg *m)
 | 
			
		||||
		.cause = OSMO_PFCP_CAUSE_REQUEST_ACCEPTED
 | 
			
		||||
	};
 | 
			
		||||
	osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, tx);
 | 
			
		||||
	/* No matter if sending the deletion ACK succeeded or not, discard the session. */
 | 
			
		||||
	up_session_fsm_state_chg(UP_SESSION_ST_WAIT_USE_COUNT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -824,7 +730,9 @@ static void up_session_established_onleave(struct osmo_fsm_inst *fi, uint32_t ne
 | 
			
		||||
 | 
			
		||||
	/* Shut down all active GTP rules */
 | 
			
		||||
	while ((a = llist_first_entry_or_null(&session->active_gtp_actions, struct up_gtp_action, entry))) {
 | 
			
		||||
		up_gtp_action_free(a);
 | 
			
		||||
		up_gtp_action_disable(a);
 | 
			
		||||
		llist_del(&a->entry);
 | 
			
		||||
		talloc_free(a);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -924,6 +832,8 @@ static const struct osmo_fsm_state up_session_fsm_states[] = {
 | 
			
		||||
		.in_event_mask = 0
 | 
			
		||||
			| S(UP_SESSION_EV_USE_COUNT_ZERO)
 | 
			
		||||
			,
 | 
			
		||||
		.out_state_mask = 0
 | 
			
		||||
			,
 | 
			
		||||
		.onenter = up_session_wait_use_count_onenter,
 | 
			
		||||
		.action = up_session_wait_use_count_action,
 | 
			
		||||
	},
 | 
			
		||||
@@ -963,7 +873,7 @@ static int up_session_use_cb(struct osmo_use_count_entry *e, int32_t old_use_cou
 | 
			
		||||
 | 
			
		||||
	LOGPFSMSLSRC(session->fi, DREF, level, file, line,
 | 
			
		||||
		     "%s %s: now used by %s\n",
 | 
			
		||||
		     (e->count - old_use_count) > 0 ? "+" : "-", e->use,
 | 
			
		||||
		     (e->count - old_use_count) > 0? "+" : "-", e->use,
 | 
			
		||||
		     osmo_use_count_to_str_c(OTC_SELECT, &session->use_count));
 | 
			
		||||
 | 
			
		||||
	if (e->count < 0)
 | 
			
		||||
@@ -990,7 +900,7 @@ static inline uint64_t up_session_key(uint64_t cp_seid, uint64_t up_seid)
 | 
			
		||||
static struct up_session *up_session_add(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid)
 | 
			
		||||
{
 | 
			
		||||
	struct up_session *session;
 | 
			
		||||
	uint64_t up_seid = up_endpoint_next_up_seid(peer->up_endpoint);
 | 
			
		||||
	uint64_t up_seid = up_endpoint_next_seid(peer->up_endpoint);
 | 
			
		||||
 | 
			
		||||
	if (!up_seid)
 | 
			
		||||
		return NULL;
 | 
			
		||||
@@ -1025,11 +935,16 @@ static struct up_session *up_session_add(struct up_peer *peer, const struct osmo
 | 
			
		||||
	return session;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct up_session *up_session_find_or_add(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid)
 | 
			
		||||
struct up_session *up_session_find_or_add(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid,
 | 
			
		||||
					  const struct osmo_pfcp_ie_f_seid *up_f_seid)
 | 
			
		||||
{
 | 
			
		||||
	struct up_session *session;
 | 
			
		||||
	OSMO_ASSERT(cp_f_seid);
 | 
			
		||||
	session = up_session_find_by_cp_f_seid(peer, cp_f_seid);
 | 
			
		||||
	if (cp_f_seid)
 | 
			
		||||
		session = up_session_find_by_cp_f_seid(peer, cp_f_seid);
 | 
			
		||||
	else if (up_f_seid)
 | 
			
		||||
		session = up_session_find_by_up_seid(peer, up_f_seid->seid);
 | 
			
		||||
	else
 | 
			
		||||
		return NULL;
 | 
			
		||||
	if (session)
 | 
			
		||||
		return session;
 | 
			
		||||
 | 
			
		||||
@@ -1056,6 +971,22 @@ struct up_session *up_session_find_by_cp_f_seid(struct up_peer *peer, const stru
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct up_session *up_session_find_by_local_teid(struct up_peer *peer, uint32_t teid)
 | 
			
		||||
{
 | 
			
		||||
	struct up_session *session;
 | 
			
		||||
	int bkt;
 | 
			
		||||
	hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
 | 
			
		||||
		struct pdr *pdr;
 | 
			
		||||
		llist_for_each_entry(pdr, &session->pdrs, entry) {
 | 
			
		||||
			if (!pdr->local_f_teid)
 | 
			
		||||
				continue;
 | 
			
		||||
			if (pdr->local_f_teid->fixed.teid == teid)
 | 
			
		||||
				return session;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool action_is_forw(const struct osmo_pfcp_ie_apply_action *aa)
 | 
			
		||||
{
 | 
			
		||||
	return osmo_pfcp_bits_get(aa->bits, OSMO_PFCP_APPLY_ACTION_FORW)
 | 
			
		||||
@@ -1066,8 +997,8 @@ static void pdr_classify(struct pdr *pdr)
 | 
			
		||||
{
 | 
			
		||||
	pdr->rx_decaps = false;
 | 
			
		||||
	pdr->forw_encaps = false;
 | 
			
		||||
	pdr->access_to_core = false;
 | 
			
		||||
	pdr->core_to_access = false;
 | 
			
		||||
	pdr->forw_to_core = false;
 | 
			
		||||
	pdr->forw_from_core = false;
 | 
			
		||||
	if (!pdr->far)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
@@ -1079,10 +1010,10 @@ static void pdr_classify(struct pdr *pdr)
 | 
			
		||||
	if (!action_is_forw(&pdr->far->desc.apply_action))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	pdr->access_to_core = (pdr->desc.pdi.source_iface == OSMO_PFCP_SOURCE_IFACE_ACCESS
 | 
			
		||||
			       && pdr->far->desc.forw_params.destination_iface == OSMO_PFCP_DEST_IFACE_CORE);
 | 
			
		||||
	pdr->forw_to_core = (pdr->desc.pdi.source_iface == OSMO_PFCP_SOURCE_IFACE_ACCESS
 | 
			
		||||
			     && pdr->far->desc.forw_params.destination_iface == OSMO_PFCP_DEST_IFACE_CORE);
 | 
			
		||||
 | 
			
		||||
	pdr->core_to_access = (pdr->desc.pdi.source_iface == OSMO_PFCP_SOURCE_IFACE_CORE
 | 
			
		||||
	pdr->forw_from_core = (pdr->desc.pdi.source_iface == OSMO_PFCP_SOURCE_IFACE_CORE
 | 
			
		||||
			       && pdr->far->desc.forw_params.destination_iface == OSMO_PFCP_DEST_IFACE_ACCESS);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1102,11 +1033,6 @@ void pdr_reverse_unset(struct pdr *pdr)
 | 
			
		||||
	pdr->reverse_pdr = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Log that a PDR (and its reverse-PDR) is inactive.
 | 
			
		||||
 * \param pdr  The Access-to-Core PDR.
 | 
			
		||||
 * \param desc  Why it is inactive.
 | 
			
		||||
 * \param pdr_to_str  The PDR that desc describes, can be pdr or the reverse Core-to-Access PDR.
 | 
			
		||||
 */
 | 
			
		||||
static void log_inactive_pdr_set(struct pdr *pdr, const char *desc, const struct pdr *pdr_to_str)
 | 
			
		||||
{
 | 
			
		||||
	struct pdr *rpdr = pdr->reverse_pdr;
 | 
			
		||||
@@ -1130,9 +1056,8 @@ static void log_inactive_pdr_set(struct pdr *pdr, const char *desc, const struct
 | 
			
		||||
 * The given PDR must have an outer-header-removal and a local F-TEID.
 | 
			
		||||
 * Its reverse-PDR must have a UE address flagged as "Destination" IP addr.
 | 
			
		||||
 * Its reverse-PDR's FAR must have an outer-header creation with a remote TEID.
 | 
			
		||||
 * \param pdr  A rule detecting packets on Access, where pdr->reverse_pdr detects packets on Core.
 | 
			
		||||
 */
 | 
			
		||||
static void add_gtp_action_tunend(void *ctx, struct llist_head *dst, struct pdr *pdr)
 | 
			
		||||
static void add_gtp_action_endecaps(void *ctx, struct llist_head *dst, struct pdr *pdr)
 | 
			
		||||
{
 | 
			
		||||
	struct up_session *session = pdr->session;
 | 
			
		||||
	struct up_gtp_action *a;
 | 
			
		||||
@@ -1143,20 +1068,18 @@ static void add_gtp_action_tunend(void *ctx, struct llist_head *dst, struct pdr
 | 
			
		||||
	OSMO_ASSERT(pdr->far);
 | 
			
		||||
	OSMO_ASSERT(pdr->reverse_pdr);
 | 
			
		||||
	OSMO_ASSERT(pdr->reverse_pdr->far);
 | 
			
		||||
	rpdr = pdr->reverse_pdr;
 | 
			
		||||
	rfar = rpdr->far;
 | 
			
		||||
	rfar_forw = &rfar->desc.forw_params;
 | 
			
		||||
 | 
			
		||||
	OSMO_ASSERT(pdr->access_to_core);
 | 
			
		||||
	OSMO_ASSERT(rpdr->core_to_access);
 | 
			
		||||
 | 
			
		||||
	/* To decaps incoming on Access, we need to have a local F-TEID assigned for which to receive GTP packets. */
 | 
			
		||||
	/* To decaps, we need to have a local TEID assigned for which to receive GTP packets. */
 | 
			
		||||
	if (!pdr->local_f_teid || pdr->local_f_teid->choose_flag) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing local F-TEID", pdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing local TEID", pdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* To encaps outgoing on Access, we need to have a remote F-TEID assigned to send out in GTP packets */
 | 
			
		||||
	/* To encaps, we need to have a remote TEID assigned to send out in GTP packets, and we need to know where to
 | 
			
		||||
	 * send GTP to. */
 | 
			
		||||
	rpdr = pdr->reverse_pdr;
 | 
			
		||||
	rfar = rpdr->far;
 | 
			
		||||
	rfar_forw = &rfar->desc.forw_params;
 | 
			
		||||
	if (!rfar->desc.forw_params_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Forwarding Parameters", rpdr);
 | 
			
		||||
		return;
 | 
			
		||||
@@ -1174,7 +1097,8 @@ static void add_gtp_action_tunend(void *ctx, struct llist_head *dst, struct pdr
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* To receive IP packets incoming on Core, we need to know the assigned IP address for the UE */
 | 
			
		||||
	/* To receive packets to be encapsulated, we need to know the assigned IP address for the UE, which receives the
 | 
			
		||||
	 * IP packets that should be placed into GTP. */
 | 
			
		||||
	if (!rpdr->desc.pdi.ue_ip_address_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing UE IP Address in PDI", rpdr);
 | 
			
		||||
		return;
 | 
			
		||||
@@ -1200,22 +1124,25 @@ static void add_gtp_action_tunend(void *ctx, struct llist_head *dst, struct pdr
 | 
			
		||||
	talloc_free(rpdr->inactive_reason);
 | 
			
		||||
	rpdr->inactive_reason = NULL;
 | 
			
		||||
 | 
			
		||||
	a = up_gtp_action_alloc(ctx, session, UP_GTP_U_TUNEND, dst);
 | 
			
		||||
	a->pdr_access = pdr->desc.pdr_id;
 | 
			
		||||
	a->pdr_core = rpdr->desc.pdr_id;
 | 
			
		||||
	a->tunend.access.local.addr = pdr->local_f_teid->fixed.ip_addr.v4;
 | 
			
		||||
	a->tunend.access.local.teid = pdr->local_f_teid->fixed.teid;
 | 
			
		||||
	a->tunend.access.remote.addr = rfar_forw->outer_header_creation.ip_addr.v4;
 | 
			
		||||
	a->tunend.access.remote.teid = rfar_forw->outer_header_creation.teid;
 | 
			
		||||
	a->tunend.core.ue_local_addr = rpdr->desc.pdi.ue_ip_address.ip_addr.v4;
 | 
			
		||||
	a = talloc(ctx, struct up_gtp_action);
 | 
			
		||||
	OSMO_ASSERT(a);
 | 
			
		||||
	*a = (struct up_gtp_action){
 | 
			
		||||
		.session = session,
 | 
			
		||||
		.pdr_core = pdr->desc.pdr_id,
 | 
			
		||||
		.pdr_access = rpdr->desc.pdr_id,
 | 
			
		||||
		.kind = UP_GTP_U_ENDECAPS,
 | 
			
		||||
		.endecaps = {
 | 
			
		||||
			.local_teid = pdr->local_f_teid->fixed.teid,
 | 
			
		||||
			.remote_teid = rfar_forw->outer_header_creation.teid,
 | 
			
		||||
			.gtp_remote_addr = rfar_forw->outer_header_creation.ip_addr.v4,
 | 
			
		||||
			.ue_addr = rpdr->desc.pdi.ue_ip_address.ip_addr.v4,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	llist_add_tail(&a->entry, dst);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* A GTP tunnel on Access side, mapping to another GTP tunnel on Core side and vice versa.
 | 
			
		||||
 * The PDR and its reverse PDR must both have an outer-header-removal and a local F-TEID.
 | 
			
		||||
 * Both FARs must have an outer-header creation with a remote F-TEID.
 | 
			
		||||
 * \param pdr  A rule detecting packets on Access, where pdr->reverse_pdr detects packets on Core.
 | 
			
		||||
 */
 | 
			
		||||
static void add_gtp_action_tunmap(void *ctx, struct llist_head *dst, struct pdr *pdr)
 | 
			
		||||
static void add_gtp_action_forw(void *ctx, struct llist_head *dst, struct pdr *pdr)
 | 
			
		||||
{
 | 
			
		||||
	struct up_session *session = pdr->session;
 | 
			
		||||
	struct up_gtp_action *a;
 | 
			
		||||
@@ -1235,53 +1162,52 @@ static void add_gtp_action_tunmap(void *ctx, struct llist_head *dst, struct pdr
 | 
			
		||||
	rfar = rpdr->far;
 | 
			
		||||
	rfar_forw = &rfar->desc.forw_params;
 | 
			
		||||
 | 
			
		||||
	OSMO_ASSERT(pdr->access_to_core);
 | 
			
		||||
	OSMO_ASSERT(rpdr->core_to_access);
 | 
			
		||||
 | 
			
		||||
	/* To decaps incoming on Access, we need to have a local F-TEID assigned for which to receive GTP packets. */
 | 
			
		||||
	/* To decaps, we need to have a local TEID assigned for which to receive GTP packets. */
 | 
			
		||||
	/* decaps from CORE */
 | 
			
		||||
	if (!pdr->local_f_teid || pdr->local_f_teid->choose_flag) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing local F-TEID (Access side)", pdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing local TEID (CORE side)", pdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	/* To decaps incoming on Core, we need to have a local F-TEID assigned for which to receive GTP packets. */
 | 
			
		||||
	/* decaps from ACCESS */
 | 
			
		||||
	if (!rpdr->local_f_teid || rpdr->local_f_teid->choose_flag) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing local F-TEID (Core side)", pdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing local TEID (ACCESS side)", pdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* To encaps outgoing on Core, we need to have a remote F-TEID assigned to send out in GTP packets */
 | 
			
		||||
	/* To encaps, we need to have a remote TEID assigned to send out in GTP packets, and we need to know where to
 | 
			
		||||
	 * send GTP to. */
 | 
			
		||||
	/* encaps towards ACCESS */
 | 
			
		||||
	if (!far->desc.forw_params_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Forwarding Parameters (Access side)", pdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Forwarding Parameters", pdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!far_forw->outer_header_creation_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Outer Header Creation (Access side)", pdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Outer Header Creation", pdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!far_forw->outer_header_creation.teid_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing TEID in FAR Outer Header Creation (Access side)", pdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing TEID in FAR Outer Header Creation", pdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!far_forw->outer_header_creation.ip_addr.v4_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing IPv4 in FAR Outer Header Creation (Access side)", pdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing IPv4 in FAR Outer Header Creation", pdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* To encaps outgoing on Access, we need to have a remote F-TEID assigned to send out in GTP packets */
 | 
			
		||||
	/* encaps towards CORE */
 | 
			
		||||
	if (!rfar->desc.forw_params_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Forwarding Parameters (Access side)", rpdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Forwarding Parameters", rpdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!rfar_forw->outer_header_creation_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Outer Header Creation (Access side)", rpdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing FAR Outer Header Creation", rpdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!rfar_forw->outer_header_creation.teid_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing TEID in FAR Outer Header Creation (Access side)", rpdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing TEID in FAR Outer Header Creation", rpdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (!rfar_forw->outer_header_creation.ip_addr.v4_present) {
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing IPv4 in FAR Outer Header Creation (Access side)", rpdr);
 | 
			
		||||
		log_inactive_pdr_set(pdr, "missing IPv4 in FAR Outer Header Creation", rpdr);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1297,17 +1223,28 @@ static void add_gtp_action_tunmap(void *ctx, struct llist_head *dst, struct pdr
 | 
			
		||||
	talloc_free(rpdr->inactive_reason);
 | 
			
		||||
	rpdr->inactive_reason = NULL;
 | 
			
		||||
 | 
			
		||||
	a = up_gtp_action_alloc(ctx, session, UP_GTP_U_TUNMAP, dst);
 | 
			
		||||
	a->pdr_access = pdr->desc.pdr_id;
 | 
			
		||||
	a->pdr_core = rpdr->desc.pdr_id;
 | 
			
		||||
	a->tunmap.access.tun.local.addr = pdr->local_f_teid->fixed.ip_addr.v4;
 | 
			
		||||
	a->tunmap.access.tun.local.teid = pdr->local_f_teid->fixed.teid;
 | 
			
		||||
	a->tunmap.access.tun.remote.addr = rfar_forw->outer_header_creation.ip_addr.v4;
 | 
			
		||||
	a->tunmap.access.tun.remote.teid = rfar_forw->outer_header_creation.teid;
 | 
			
		||||
	a->tunmap.core.tun.local.addr = rpdr->local_f_teid->fixed.ip_addr.v4;
 | 
			
		||||
	a->tunmap.core.tun.local.teid = rpdr->local_f_teid->fixed.teid;
 | 
			
		||||
	a->tunmap.core.tun.remote.addr = far_forw->outer_header_creation.ip_addr.v4;
 | 
			
		||||
	a->tunmap.core.tun.remote.teid = far_forw->outer_header_creation.teid;
 | 
			
		||||
	a = talloc(ctx, struct up_gtp_action);
 | 
			
		||||
	OSMO_ASSERT(a);
 | 
			
		||||
	*a = (struct up_gtp_action){
 | 
			
		||||
		.session = session,
 | 
			
		||||
		.pdr_core = pdr->desc.pdr_id,
 | 
			
		||||
		.pdr_access = rpdr->desc.pdr_id,
 | 
			
		||||
		.kind = UP_GTP_U_TUNMAP,
 | 
			
		||||
		.tunmap = {
 | 
			
		||||
			.core = {
 | 
			
		||||
				.local_teid = pdr->local_f_teid->fixed.teid,
 | 
			
		||||
				.remote_teid = rfar_forw->outer_header_creation.teid,
 | 
			
		||||
				.gtp_remote_addr = rfar_forw->outer_header_creation.ip_addr.v4,
 | 
			
		||||
			},
 | 
			
		||||
			.access = {
 | 
			
		||||
				.local_teid = rpdr->local_f_teid->fixed.teid,
 | 
			
		||||
				.remote_teid = far_forw->outer_header_creation.teid,
 | 
			
		||||
				.gtp_remote_addr = far_forw->outer_header_creation.ip_addr.v4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	llist_add_tail(&a->entry, dst);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Analyse all PDRs and FARs and find configurations that match either a GTP encaps/decaps or a GTP forward rule. Add to
 | 
			
		||||
@@ -1334,13 +1271,12 @@ static enum osmo_pfcp_cause find_gtp_actions(void *ctx, struct llist_head *dst,
 | 
			
		||||
		if (pdr->reverse_pdr)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* In this outer loop, only follow the access_to_core directed PDRs, in the inner loop find the matching
 | 
			
		||||
		 * core_to_access PDR. i.e. we are looking only at PDRs detecting packets on the Access side, pairing up
 | 
			
		||||
		 * with "reverse PDRs" detecting packets on the Core side. */
 | 
			
		||||
		if (!pdr->access_to_core)
 | 
			
		||||
		/* In this outer loop, only follow the forw_to_core directed PDRs, in the inner loop find the matching
 | 
			
		||||
		 * forw_from_core PDR. */
 | 
			
		||||
		if (!pdr->forw_to_core)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* If a required local addr + TEID is not known, we cannot pair this PDR up */
 | 
			
		||||
		/* If a required TEID is not known, we cannot pair this PDR up */
 | 
			
		||||
		if (pdr->rx_decaps && !pdr->local_f_teid)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
@@ -1351,7 +1287,7 @@ static enum osmo_pfcp_cause find_gtp_actions(void *ctx, struct llist_head *dst,
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			/* Looking for a PDR facing the other way */
 | 
			
		||||
			if (!other->core_to_access)
 | 
			
		||||
			if (!other->forw_from_core)
 | 
			
		||||
				continue;
 | 
			
		||||
			/* GTP header-ness must match, in reverse. */
 | 
			
		||||
			if (pdr->rx_decaps != other->forw_encaps
 | 
			
		||||
@@ -1374,14 +1310,14 @@ static enum osmo_pfcp_cause find_gtp_actions(void *ctx, struct llist_head *dst,
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Iterate in direction Access-to-Core, where pdr->reverse_pdr will be the Core-to-Access counterpart. */
 | 
			
		||||
		if (!pdr->access_to_core)
 | 
			
		||||
		/* Iterate in direction to-Core, where pdr->reverse_pdr will be the from-Core counterpart. */
 | 
			
		||||
		if (!pdr->forw_to_core)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (pdr->rx_decaps && !pdr->forw_encaps)
 | 
			
		||||
			add_gtp_action_tunend(ctx, dst, pdr);
 | 
			
		||||
			add_gtp_action_endecaps(ctx, dst, pdr);
 | 
			
		||||
		else if (pdr->rx_decaps && pdr->forw_encaps)
 | 
			
		||||
			add_gtp_action_tunmap(ctx, dst, pdr);
 | 
			
		||||
			add_gtp_action_forw(ctx, dst, pdr);
 | 
			
		||||
		else {
 | 
			
		||||
			/* log the details of both PDRs in two separate log lines */
 | 
			
		||||
			log_inactive_pdr_set(pdr, "not implemented", pdr);
 | 
			
		||||
@@ -1432,7 +1368,9 @@ static enum osmo_pfcp_cause setup_gtp_actions(struct up_session *session, struct
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		LOGPFSML(session->fi, LOGL_DEBUG, "disabling: %s\n", up_gtp_action_to_str_c(OTC_SELECT, a));
 | 
			
		||||
		up_gtp_action_free(a);
 | 
			
		||||
		up_gtp_action_disable(a);
 | 
			
		||||
		llist_del(&a->entry);
 | 
			
		||||
		talloc_free(a);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Set up all GTP tunnels requested in the session setup, but not active yet */
 | 
			
		||||
@@ -1477,16 +1415,11 @@ static enum osmo_pfcp_cause up_session_setup_gtp(struct up_session *session)
 | 
			
		||||
	return cause;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Return true when the session is in Established state and has active GTP actions. */
 | 
			
		||||
bool up_session_is_active(struct up_session *session)
 | 
			
		||||
{
 | 
			
		||||
	return session && (session->fi->state == UP_SESSION_ST_ESTABLISHED) && !llist_empty(&session->active_gtp_actions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Return true when up_session_is_active() == true *and* it has only active PDR/FAR pairs.
 | 
			
		||||
 * A PDR/FAR is inactive when it is not part of an active GTP action. Reasons may be that it has no PDR-to-FAR relation,
 | 
			
		||||
 * there is no matching reverse PDR/FAR, that a FAR is not set to FORW, an ignored Source/Destination Interface, ...
 | 
			
		||||
 */
 | 
			
		||||
bool up_session_is_fully_active(struct up_session *session, int *active_p, int *inactive_p)
 | 
			
		||||
{
 | 
			
		||||
	struct pdr *pdr;
 | 
			
		||||
 
 | 
			
		||||
@@ -29,27 +29,12 @@
 | 
			
		||||
 | 
			
		||||
#include <osmocom/upf/upf.h>
 | 
			
		||||
#include <osmocom/upf/up_endpoint.h>
 | 
			
		||||
#include <osmocom/upf/up_peer.h>
 | 
			
		||||
#include <osmocom/upf/up_session.h>
 | 
			
		||||
#include <osmocom/upf/up_gtp_action.h>
 | 
			
		||||
#include <osmocom/upf/upf_gtp.h>
 | 
			
		||||
 | 
			
		||||
struct g_upf *g_upf = NULL;
 | 
			
		||||
 | 
			
		||||
struct osmo_tdef g_upf_nft_tdefs[] = {
 | 
			
		||||
	{ .T = -32, .default_val = 1000, .unit = OSMO_TDEF_MS,
 | 
			
		||||
	  .desc = "How long to wait for more nft rulesets before flushing in batch",
 | 
			
		||||
	},
 | 
			
		||||
	{ .T = -33, .default_val = 1, .unit = OSMO_TDEF_CUSTOM,
 | 
			
		||||
	  .desc = "When reaching this nr of queued nft rulesets, flush the queue",
 | 
			
		||||
	  .max_val = 128,
 | 
			
		||||
	},
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_tdef_group g_upf_tdef_groups[] = {
 | 
			
		||||
	{ "pfcp", "PFCP endpoint timers", osmo_pfcp_tdefs, },
 | 
			
		||||
	{ "nft", "netfilter timers", g_upf_nft_tdefs, },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -65,24 +50,20 @@ void g_upf_alloc(void *ctx)
 | 
			
		||||
				.local_port = OSMO_PFCP_PORT,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		.tunmap = {
 | 
			
		||||
			.priority_pre = -300,
 | 
			
		||||
			.priority_post = 400,
 | 
			
		||||
		.nft = {
 | 
			
		||||
			.priority = -300,
 | 
			
		||||
		},
 | 
			
		||||
		.tunend = {
 | 
			
		||||
		.gtp = {
 | 
			
		||||
			/* TODO: recovery count state file; use lower byte of current time, poor person's random. */
 | 
			
		||||
			.recovery_count = time(NULL),
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	INIT_LLIST_HEAD(&g_upf->tunend.vty_cfg.devs);
 | 
			
		||||
	INIT_LLIST_HEAD(&g_upf->tunend.devs);
 | 
			
		||||
	INIT_LLIST_HEAD(&g_upf->netinst);
 | 
			
		||||
	hash_init(g_upf->tunmap.nft_tun_by_chain_id);
 | 
			
		||||
	hash_init(g_upf->gtp.pdrs_by_local_f_teid);
 | 
			
		||||
	INIT_LLIST_HEAD(&g_upf->gtp.vty_cfg.devs);
 | 
			
		||||
	INIT_LLIST_HEAD(&g_upf->gtp.devs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_pfcp_init(void)
 | 
			
		||||
int upf_pfcp_listen()
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_sockaddr_str local_addr_str;
 | 
			
		||||
	struct osmo_sockaddr local_addr;
 | 
			
		||||
@@ -94,8 +75,9 @@ int upf_pfcp_init(void)
 | 
			
		||||
	 * osmo_sockaddr. */
 | 
			
		||||
	osmo_sockaddr_str_from_str(&local_addr_str, g_upf->pfcp.vty_cfg.local_addr, g_upf->pfcp.vty_cfg.local_port);
 | 
			
		||||
	osmo_sockaddr_str_to_sockaddr(&local_addr_str, &local_addr.u.sas);
 | 
			
		||||
	LOGP(DLPFCP, LOGL_NOTICE, "PFCP: Listening on %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, &local_addr));
 | 
			
		||||
 | 
			
		||||
	g_upf->pfcp.ep = up_endpoint_alloc(g_upf, &local_addr);
 | 
			
		||||
	g_upf->pfcp.ep = up_endpoint_init(g_upf, &local_addr);;
 | 
			
		||||
	if (!g_upf->pfcp.ep) {
 | 
			
		||||
		fprintf(stderr, "Failed to allocate PFCP endpoint.\n");
 | 
			
		||||
		return -1;
 | 
			
		||||
@@ -103,30 +85,10 @@ int upf_pfcp_init(void)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_pfcp_listen(void)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	if (!g_upf->pfcp.ep) {
 | 
			
		||||
		rc = upf_pfcp_init();
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc = up_endpoint_bind(g_upf->pfcp.ep);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DLPFCP, LOGL_ERROR, "PFCP: failed to listen on %s\n",
 | 
			
		||||
		     osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_upf->pfcp.ep->pfcp_ep)));
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
	LOGP(DLPFCP, LOGL_NOTICE, "PFCP: Listening on %s\n",
 | 
			
		||||
	     osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_upf->pfcp.ep->pfcp_ep)));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtp_devs_open()
 | 
			
		||||
{
 | 
			
		||||
	struct tunend_vty_cfg *c = &g_upf->tunend.vty_cfg;
 | 
			
		||||
	struct tunend_vty_cfg_dev *d;
 | 
			
		||||
	struct gtp_vty_cfg *c = &g_upf->gtp.vty_cfg;
 | 
			
		||||
	struct gtp_vty_cfg_dev *d;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(d, &c->devs, entry) {
 | 
			
		||||
		if (upf_gtp_dev_open(d->dev_name, d->create, d->local_addr, false, false))
 | 
			
		||||
@@ -134,76 +96,3 @@ int upf_gtp_devs_open()
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool upf_is_local_teid_in_use(uint32_t teid)
 | 
			
		||||
{
 | 
			
		||||
	struct pdr *pdr;
 | 
			
		||||
	hash_for_each_possible(g_upf->gtp.pdrs_by_local_f_teid, pdr, node_by_local_f_teid, teid) {
 | 
			
		||||
		if (!pdr->local_f_teid)
 | 
			
		||||
			continue;
 | 
			
		||||
		if (pdr->local_f_teid->fixed.teid != teid)
 | 
			
		||||
			continue;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint32_t upf_next_local_teid_inc(void)
 | 
			
		||||
{
 | 
			
		||||
	g_upf->gtp.next_local_teid_state++;
 | 
			
		||||
	if (!g_upf->gtp.next_local_teid_state)
 | 
			
		||||
		g_upf->gtp.next_local_teid_state++;
 | 
			
		||||
	return g_upf->gtp.next_local_teid_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t upf_next_local_teid(void)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t sanity;
 | 
			
		||||
	for (sanity = 2342; sanity; sanity--) {
 | 
			
		||||
		uint32_t next_teid = upf_next_local_teid_inc();
 | 
			
		||||
		if (upf_is_local_teid_in_use(next_teid))
 | 
			
		||||
			continue;
 | 
			
		||||
		return next_teid;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint32_t upf_next_chain_id_inc(void)
 | 
			
		||||
{
 | 
			
		||||
	g_upf->tunmap.next_chain_id_state++;
 | 
			
		||||
	if (!g_upf->tunmap.next_chain_id_state)
 | 
			
		||||
		g_upf->tunmap.next_chain_id_state++;
 | 
			
		||||
	return g_upf->tunmap.next_chain_id_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool upf_is_chain_id_in_use(uint32_t chain_id)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_nft_tun *nft_tun;
 | 
			
		||||
	hash_for_each_possible(g_upf->tunmap.nft_tun_by_chain_id, nft_tun, node_by_chain_id, chain_id) {
 | 
			
		||||
		if (nft_tun->chain_id != chain_id)
 | 
			
		||||
			continue;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Return an unused chain_id, or 0 if none is found with sane effort. */
 | 
			
		||||
uint32_t upf_next_chain_id(void)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t sanity;
 | 
			
		||||
 | 
			
		||||
	/* Make sure the new chain_id is not used anywhere */
 | 
			
		||||
	for (sanity = 2342; sanity; sanity--) {
 | 
			
		||||
		uint32_t chain_id = upf_next_chain_id_inc();
 | 
			
		||||
 | 
			
		||||
		if (!g_upf->pfcp.ep)
 | 
			
		||||
			return chain_id;
 | 
			
		||||
 | 
			
		||||
		if (upf_is_chain_id_in_use(chain_id))
 | 
			
		||||
			continue;
 | 
			
		||||
		return chain_id;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* finding a chain_id became insane, return invalid = 0 */
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
#include <osmocom/upf/upf_gtpu_echo.h>
 | 
			
		||||
 | 
			
		||||
#define LOG_GTP_TUN(TUN, LEVEL, FMT, ARGS...) \
 | 
			
		||||
	LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_tunend_to_str_c(OTC_SELECT, (TUN)), ##ARGS)
 | 
			
		||||
	LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_tun_to_str_c(OTC_SELECT, (TUN)), ##ARGS)
 | 
			
		||||
 | 
			
		||||
int upf_gtp_dev_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
@@ -66,36 +66,16 @@ char *upf_gtp_dev_to_str_c(void *ctx, const struct upf_gtp_dev *dev)
 | 
			
		||||
struct upf_gtp_dev *upf_gtp_dev_find_by_name(const char *name)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_dev *dev;
 | 
			
		||||
	llist_for_each_entry(dev, &g_upf->tunend.devs, entry) {
 | 
			
		||||
	llist_for_each_entry(dev, &g_upf->gtp.devs, entry) {
 | 
			
		||||
		if (!strcmp(name, dev->name))
 | 
			
		||||
			return dev;
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct upf_gtp_dev *upf_gtp_dev_find_by_local_addr(const struct osmo_sockaddr *local_addr)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_dev *dev;
 | 
			
		||||
	struct upf_gtp_dev *dev_any = NULL;
 | 
			
		||||
	struct osmo_sockaddr needle = *local_addr;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(dev, &g_upf->tunend.devs, entry) {
 | 
			
		||||
		/* To leave the port number out of the cmp, set the needle's port to match */
 | 
			
		||||
		osmo_sockaddr_set_port(&needle.u.sa, osmo_sockaddr_port(&dev->gtpv1.local_addr.u.sa));
 | 
			
		||||
 | 
			
		||||
		if (!osmo_sockaddr_cmp(&needle, &dev->gtpv1.local_addr))
 | 
			
		||||
			return dev;
 | 
			
		||||
		if (osmo_sockaddr_is_any(&dev->gtpv1.local_addr) == 1)
 | 
			
		||||
			dev_any = dev;
 | 
			
		||||
	}
 | 
			
		||||
	/* No 1:1 match found, but there is a dev listening on ANY? Return that.
 | 
			
		||||
	 * If there is no such dev, return NULL. */
 | 
			
		||||
	return dev_any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct upf_gtp_dev *upf_gtp_dev_first()
 | 
			
		||||
{
 | 
			
		||||
	return llist_first_entry_or_null(&g_upf->tunend.devs, struct upf_gtp_dev, entry);
 | 
			
		||||
	return llist_first_entry_or_null(&g_upf->gtp.devs, struct upf_gtp_dev, entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Tell the kernel to remove the GTP device. Called implicitly by talloc_free() (see upf_gtp_dev_destruct()). */
 | 
			
		||||
@@ -116,8 +96,6 @@ static int upf_gtp_dev_delete(struct upf_gtp_dev *dev)
 | 
			
		||||
 | 
			
		||||
static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev);
 | 
			
		||||
 | 
			
		||||
/* Allocate state for one GTP device, add to g_upf->tunend.devs and return the created device. If state for the device of
 | 
			
		||||
 * that name already exists, do nothing and return NULL. */
 | 
			
		||||
static struct upf_gtp_dev *upf_gtp_dev_alloc(const char *name, const char *local_addr)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_dev *dev = upf_gtp_dev_find_by_name(name);
 | 
			
		||||
@@ -134,7 +112,6 @@ static struct upf_gtp_dev *upf_gtp_dev_alloc(const char *name, const char *local
 | 
			
		||||
		.gtpv1.ofd.fd = -1,
 | 
			
		||||
	};
 | 
			
		||||
	INIT_LLIST_HEAD(&dev->tunnels);
 | 
			
		||||
	hash_init(dev->tunnels_by_local_f_teid);
 | 
			
		||||
 | 
			
		||||
	osmo_sockaddr_str_from_str(&addr_conv, local_addr, PORT_GTP0_U);
 | 
			
		||||
 | 
			
		||||
@@ -145,7 +122,7 @@ static struct upf_gtp_dev *upf_gtp_dev_alloc(const char *name, const char *local
 | 
			
		||||
 | 
			
		||||
	/* Need to add to list before setting up the destructor. A talloc_free() does automagically remove from the
 | 
			
		||||
	 * list. */
 | 
			
		||||
	llist_add(&dev->entry, &g_upf->tunend.devs);
 | 
			
		||||
	llist_add(&dev->entry, &g_upf->gtp.devs);
 | 
			
		||||
 | 
			
		||||
	talloc_set_destructor(dev, upf_gtp_dev_destruct);
 | 
			
		||||
 | 
			
		||||
@@ -164,7 +141,7 @@ static int dev_resolve_ifidx(struct upf_gtp_dev *dev)
 | 
			
		||||
	}
 | 
			
		||||
	/* Let's try something to see if talking to the device works. */
 | 
			
		||||
	errno = 0;
 | 
			
		||||
	rc = gtp_list_tunnel(g_upf->tunend.genl_id, g_upf->tunend.nl);
 | 
			
		||||
	rc = gtp_list_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl);
 | 
			
		||||
	if (errno)
 | 
			
		||||
		rc = -errno;
 | 
			
		||||
	else if (rc)
 | 
			
		||||
@@ -179,16 +156,9 @@ static int dev_resolve_ifidx(struct upf_gtp_dev *dev)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int upf_gtp_dev_create(struct upf_gtp_dev *dev, int gtp0_fd, int gtp1_fd)
 | 
			
		||||
{
 | 
			
		||||
	if (dev->sgsn_mode)
 | 
			
		||||
		return gtp_dev_create_sgsn(-1, dev->name, gtp0_fd, gtp1_fd);
 | 
			
		||||
	return gtp_dev_create(-1, dev->name, gtp0_fd, gtp1_fd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_addr, bool listen_for_gtpv0, bool sgsn_mode)
 | 
			
		||||
{
 | 
			
		||||
	const struct osmo_sockaddr any = {
 | 
			
		||||
	struct osmo_sockaddr any = {
 | 
			
		||||
		.u.sin = {
 | 
			
		||||
			.sin_family = AF_INET,
 | 
			
		||||
			.sin_port = 0,
 | 
			
		||||
@@ -198,25 +168,12 @@ int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_ad
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct upf_gtp_dev *dev;
 | 
			
		||||
 | 
			
		||||
	if (g_upf->tunend.mockup) {
 | 
			
		||||
		LOGP(DGTP, LOGL_NOTICE, "tunend/mockup active: not opening GTP device '%s'\n", name);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dev = upf_gtp_dev_alloc(name, local_addr);
 | 
			
		||||
	struct upf_gtp_dev *dev = upf_gtp_dev_alloc(name, local_addr);
 | 
			
		||||
	if (!dev)
 | 
			
		||||
		return -EIO;
 | 
			
		||||
 | 
			
		||||
	dev->sgsn_mode = sgsn_mode;
 | 
			
		||||
 | 
			
		||||
	rc = upf_gtp_genl_ensure_open();
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot set up GTP device, failed to open mnl_socket\n");
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (listen_for_gtpv0) {
 | 
			
		||||
		rc = osmo_sock_init_osa_ofd(&dev->gtpv0.ofd, SOCK_DGRAM, 0, &dev->gtpv0.local_addr, &any,
 | 
			
		||||
					    OSMO_SOCK_F_BIND);
 | 
			
		||||
@@ -243,13 +200,10 @@ int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_ad
 | 
			
		||||
	if (create_gtp_dev) {
 | 
			
		||||
		int gtp0_fd = listen_for_gtpv0 ? dev->gtpv0.ofd.fd : -1;
 | 
			
		||||
		int gtp1_fd = dev->gtpv1.ofd.fd;
 | 
			
		||||
 | 
			
		||||
		rc = upf_gtp_dev_create(dev, gtp0_fd, gtp1_fd);
 | 
			
		||||
		if (rc == -EEXIST && gtp_dev_destroy(dev->name) == 0) {
 | 
			
		||||
			LOG_GTP_DEV(dev, LOGL_ERROR, "deleted GTP device from unclean shutdown\n");
 | 
			
		||||
			rc = upf_gtp_dev_create(dev, gtp0_fd, gtp1_fd);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (dev->sgsn_mode)
 | 
			
		||||
			rc = gtp_dev_create_sgsn(-1, dev->name, gtp0_fd, gtp1_fd);
 | 
			
		||||
		else
 | 
			
		||||
			rc = gtp_dev_create(-1, dev->name, gtp0_fd, gtp1_fd);
 | 
			
		||||
		if (rc < 0) {
 | 
			
		||||
			LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot create GTP device: rc=%d\n", rc);
 | 
			
		||||
			/* name = NULL: signal to the destructor that it does not need to delete the device */
 | 
			
		||||
@@ -276,40 +230,38 @@ int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_ad
 | 
			
		||||
void upf_gtp_devs_close()
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_dev *dev;
 | 
			
		||||
	while ((dev = llist_first_entry_or_null(&g_upf->tunend.devs, struct upf_gtp_dev, entry)))
 | 
			
		||||
	while ((dev = llist_first_entry_or_null(&g_upf->gtp.devs, struct upf_gtp_dev, entry)))
 | 
			
		||||
		talloc_free(dev);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upf_gtp_genl_close()
 | 
			
		||||
{
 | 
			
		||||
	if (!g_upf->tunend.nl)
 | 
			
		||||
	if (!g_upf->gtp.nl)
 | 
			
		||||
		return;
 | 
			
		||||
	genl_socket_close(g_upf->tunend.nl);
 | 
			
		||||
	g_upf->tunend.nl = NULL;
 | 
			
		||||
	g_upf->tunend.genl_id = -1;
 | 
			
		||||
	genl_socket_close(g_upf->gtp.nl);
 | 
			
		||||
	g_upf->gtp.nl = NULL;
 | 
			
		||||
	g_upf->gtp.genl_id = -1;
 | 
			
		||||
 | 
			
		||||
	LOGP(DGTP, LOGL_NOTICE, "Closed mnl_socket\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Open an MNL socket which allows to create and remove GTP devices (requires CAP_NET_ADMIN). */
 | 
			
		||||
int upf_gtp_genl_ensure_open()
 | 
			
		||||
int upf_gtp_genl_open()
 | 
			
		||||
{
 | 
			
		||||
	/* Already open? */
 | 
			
		||||
	if (g_upf->tunend.nl && g_upf->tunend.genl_id >= 0)
 | 
			
		||||
	if (g_upf->gtp.nl && g_upf->gtp.genl_id >= 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* sanity / paranoia: if re-opening, make sure the previous socket is closed */
 | 
			
		||||
	if (g_upf->tunend.nl)
 | 
			
		||||
	if (g_upf->gtp.nl)
 | 
			
		||||
		upf_gtp_genl_close();
 | 
			
		||||
 | 
			
		||||
	g_upf->tunend.nl = genl_socket_open();
 | 
			
		||||
	if (!g_upf->tunend.nl) {
 | 
			
		||||
	g_upf->gtp.nl = genl_socket_open();
 | 
			
		||||
	if (!g_upf->gtp.nl) {
 | 
			
		||||
		LOGP(DGTP, LOGL_ERROR, "Cannot open mnl_socket: %s\n", strerror(errno));
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_upf->tunend.genl_id = genl_lookup_family(g_upf->tunend.nl, "gtp");
 | 
			
		||||
	if (g_upf->tunend.genl_id < 0) {
 | 
			
		||||
	g_upf->gtp.genl_id = genl_lookup_family(g_upf->gtp.nl, "gtp");
 | 
			
		||||
	if (g_upf->gtp.genl_id < 0) {
 | 
			
		||||
		LOGP(DGTP, LOGL_ERROR, "genl family 'gtp' not found\n");
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
@@ -318,72 +270,59 @@ int upf_gtp_genl_ensure_open()
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct upf_gtp_tunend {
 | 
			
		||||
	struct llist_head entry; /* item in (struct upf_gtp_dev)->tunnels */
 | 
			
		||||
	struct hlist_node node_by_local_f_teid; /* item in g_upf->gtp.pdrs_by_local_f_teid */
 | 
			
		||||
struct upf_gtp_tun {
 | 
			
		||||
	struct llist_head entry;
 | 
			
		||||
 | 
			
		||||
	struct upf_gtp_dev *dev;
 | 
			
		||||
	struct upf_tunend desc;
 | 
			
		||||
	struct upf_gtp_tun_desc desc;
 | 
			
		||||
	bool active;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int upf_gtp_tunend_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_tunend *tun)
 | 
			
		||||
static int upf_gtp_tun_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_tun *tun)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	/* "tunend{dev=apn0 access(GTP-r=1.2.3.4 TEID:l=0x1234,r=0x5678) core(UE-l=10.9.8.7)}" */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "tunend{dev=%s access(GTP-r=", tun->dev->name);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.access.remote.addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " TEID:l=0x%x,r=0x%x) core(UE-l=",
 | 
			
		||||
			   tun->desc.access.local.teid, tun->desc.access.remote.teid);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.core.ue_local_addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, ")}");
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "%s:tun{TEID=l:0x%x,r:0x%x UE=", tun->dev->name, tun->desc.local_teid,
 | 
			
		||||
			   tun->desc.remote_teid);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.ue_addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " GTP-dst=");
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.gtp_remote_addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "}");
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *upf_gtp_tunend_to_str_c(void *ctx, const struct upf_gtp_tunend *tun)
 | 
			
		||||
static char *upf_gtp_tun_to_str_c(void *ctx, const struct upf_gtp_tun *tun)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_tunend_to_str_buf, tun)
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_tun_to_str_buf, tun)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int upf_gtp_tunend_deactivate(struct upf_gtp_tunend *tun);
 | 
			
		||||
static int upf_gtp_tun_deactivate(struct upf_gtp_tun *tun);
 | 
			
		||||
 | 
			
		||||
static int upf_gtp_tunend_destruct(struct upf_gtp_tunend *tun)
 | 
			
		||||
static int upf_gtp_tun_destruct(struct upf_gtp_tun *tun)
 | 
			
		||||
{
 | 
			
		||||
	if (tun->active)
 | 
			
		||||
		upf_gtp_tunend_deactivate(tun);
 | 
			
		||||
	hash_del(&tun->node_by_local_f_teid);
 | 
			
		||||
		upf_gtp_tun_deactivate(tun);
 | 
			
		||||
	llist_del(&tun->entry);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define tunend_validate(TUNEND) \
 | 
			
		||||
	do { \
 | 
			
		||||
		OSMO_ASSERT(osmo_sockaddr_port(&(TUNEND)->access.local.addr.u.sa) == 0); \
 | 
			
		||||
		OSMO_ASSERT(osmo_sockaddr_port(&(TUNEND)->access.remote.addr.u.sa) == 0); \
 | 
			
		||||
		OSMO_ASSERT(osmo_sockaddr_port(&(TUNEND)->core.ue_local_addr.u.sa) == 0); \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
static struct upf_gtp_tunend *upf_gtp_tunend_alloc(struct upf_gtp_dev *dev, const struct upf_tunend *desc)
 | 
			
		||||
static struct upf_gtp_tun *upf_gtp_tun_alloc(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *desc)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_tunend *tun = talloc(dev, struct upf_gtp_tunend);
 | 
			
		||||
	struct upf_gtp_tun *tun = talloc(dev, struct upf_gtp_tun);
 | 
			
		||||
	OSMO_ASSERT(tun);
 | 
			
		||||
	tunend_validate(desc);
 | 
			
		||||
	*tun = (struct upf_gtp_tunend){
 | 
			
		||||
	*tun = (struct upf_gtp_tun){
 | 
			
		||||
		.dev = dev,
 | 
			
		||||
		.desc = *desc,
 | 
			
		||||
	};
 | 
			
		||||
	hash_add(dev->tunnels_by_local_f_teid, &tun->node_by_local_f_teid, tun->desc.access.local.teid);
 | 
			
		||||
	llist_add(&tun->entry, &dev->tunnels);
 | 
			
		||||
	talloc_set_destructor(tun, upf_gtp_tunend_destruct);
 | 
			
		||||
	talloc_set_destructor(tun, upf_gtp_tun_destruct);
 | 
			
		||||
	return tun;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct gtp_tunnel *upf_gtp_tunend_to_gtp_tunnel(struct upf_gtp_tunend *tun)
 | 
			
		||||
static struct gtp_tunnel *upf_gtp_tun_to_gtp_tunnel(struct upf_gtp_tun *tun)
 | 
			
		||||
{
 | 
			
		||||
	struct gtp_tunnel *t;
 | 
			
		||||
 | 
			
		||||
	if (tun->desc.core.ue_local_addr.u.sas.ss_family != AF_INET
 | 
			
		||||
	    || tun->desc.access.remote.addr.u.sas.ss_family != AF_INET) {
 | 
			
		||||
	if (tun->desc.ue_addr.u.sas.ss_family != AF_INET || tun->desc.gtp_remote_addr.u.sas.ss_family != AF_INET) {
 | 
			
		||||
		LOG_GTP_TUN(tun, LOGL_ERROR, "Only capabale of IPv4\n");
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
@@ -392,14 +331,14 @@ static struct gtp_tunnel *upf_gtp_tunend_to_gtp_tunnel(struct upf_gtp_tunend *tu
 | 
			
		||||
	OSMO_ASSERT(t);
 | 
			
		||||
	gtp_tunnel_set_ifidx(t, tun->dev->ifidx);
 | 
			
		||||
	gtp_tunnel_set_version(t, GTP_V1);
 | 
			
		||||
	gtp_tunnel_set_i_tei(t, tun->desc.access.local.teid);
 | 
			
		||||
	gtp_tunnel_set_o_tei(t, tun->desc.access.remote.teid);
 | 
			
		||||
	gtp_tunnel_set_sgsn_ip4(t, &tun->desc.access.remote.addr.u.sin.sin_addr);
 | 
			
		||||
	gtp_tunnel_set_ms_ip4(t, &tun->desc.core.ue_local_addr.u.sin.sin_addr);
 | 
			
		||||
	gtp_tunnel_set_i_tei(t, tun->desc.local_teid);
 | 
			
		||||
	gtp_tunnel_set_o_tei(t, tun->desc.remote_teid);
 | 
			
		||||
	gtp_tunnel_set_ms_ip4(t, &tun->desc.ue_addr.u.sin.sin_addr);
 | 
			
		||||
	gtp_tunnel_set_sgsn_ip4(t, &tun->desc.gtp_remote_addr.u.sin.sin_addr);
 | 
			
		||||
	return t;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtp_tunend_activate(struct upf_gtp_tunend *tun)
 | 
			
		||||
int upf_gtp_tun_activate(struct upf_gtp_tun *tun)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct gtp_tunnel *t;
 | 
			
		||||
@@ -407,12 +346,12 @@ int upf_gtp_tunend_activate(struct upf_gtp_tunend *tun)
 | 
			
		||||
	if (tun->active)
 | 
			
		||||
		return -EALREADY;
 | 
			
		||||
 | 
			
		||||
	t = upf_gtp_tunend_to_gtp_tunnel(tun);
 | 
			
		||||
	t = upf_gtp_tun_to_gtp_tunnel(tun);
 | 
			
		||||
	if (!t)
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
 | 
			
		||||
	errno = 0;
 | 
			
		||||
	rc = gtp_add_tunnel(g_upf->tunend.genl_id, g_upf->tunend.nl, t);
 | 
			
		||||
	rc = gtp_add_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl, t);
 | 
			
		||||
	if (errno) {
 | 
			
		||||
		rc = -errno;
 | 
			
		||||
	} else if (rc) {
 | 
			
		||||
@@ -425,41 +364,37 @@ int upf_gtp_tunend_activate(struct upf_gtp_tunend *tun)
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct upf_gtp_tunend *upf_gtp_dev_tunend_find(struct upf_gtp_dev *dev, const struct upf_tunend *tunend)
 | 
			
		||||
static struct upf_gtp_tun *upf_gtp_dev_tunnel_find(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_tunend *tun;
 | 
			
		||||
	tunend_validate(tunend);
 | 
			
		||||
 | 
			
		||||
	hash_for_each_possible(dev->tunnels_by_local_f_teid, tun, node_by_local_f_teid, tunend->access.local.teid) {
 | 
			
		||||
		if (upf_gtp_tunend_cmp(tunend, &tun->desc))
 | 
			
		||||
	struct upf_gtp_tun *tun;
 | 
			
		||||
	llist_for_each_entry(tun, &dev->tunnels, entry) {
 | 
			
		||||
		if (upf_gtp_tun_desc_cmp(tun_desc, &tun->desc))
 | 
			
		||||
			continue;
 | 
			
		||||
		return tun;
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtp_dev_tunend_add(struct upf_gtp_dev *dev, const struct upf_tunend *tunend)
 | 
			
		||||
int upf_gtp_dev_tunnel_add(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_tunend *tun;
 | 
			
		||||
	tunend_validate(tunend);
 | 
			
		||||
	tun = upf_gtp_dev_tunend_find(dev, tunend);
 | 
			
		||||
	struct upf_gtp_tun *tun;
 | 
			
		||||
	tun = upf_gtp_dev_tunnel_find(dev, tun_desc);
 | 
			
		||||
	if (!tun)
 | 
			
		||||
		tun = upf_gtp_tunend_alloc(dev, tunend);
 | 
			
		||||
		tun = upf_gtp_tun_alloc(dev, tun_desc);
 | 
			
		||||
	if (tun->active)
 | 
			
		||||
		return 0;
 | 
			
		||||
	return upf_gtp_tunend_activate(tun);
 | 
			
		||||
	return upf_gtp_tun_activate(tun);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtp_dev_tunend_del(struct upf_gtp_dev *dev, const struct upf_tunend *tunend)
 | 
			
		||||
int upf_gtp_dev_tunnel_del(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_tunend *tun;
 | 
			
		||||
	struct upf_gtp_tun *tun;
 | 
			
		||||
	int rc;
 | 
			
		||||
	tunend_validate(tunend);
 | 
			
		||||
	tun = upf_gtp_dev_tunend_find(dev, tunend);
 | 
			
		||||
	tun = upf_gtp_dev_tunnel_find(dev, tun_desc);
 | 
			
		||||
	if (!tun)
 | 
			
		||||
		return 0;
 | 
			
		||||
	if (tun->active) {
 | 
			
		||||
		rc = upf_gtp_tunend_deactivate(tun);
 | 
			
		||||
		rc = upf_gtp_tun_deactivate(tun);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
	}
 | 
			
		||||
@@ -467,7 +402,7 @@ int upf_gtp_dev_tunend_del(struct upf_gtp_dev *dev, const struct upf_tunend *tun
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int upf_gtp_tunend_deactivate(struct upf_gtp_tunend *tun)
 | 
			
		||||
static int upf_gtp_tun_deactivate(struct upf_gtp_tun *tun)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct gtp_tunnel *t;
 | 
			
		||||
@@ -477,13 +412,13 @@ static int upf_gtp_tunend_deactivate(struct upf_gtp_tunend *tun)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t = upf_gtp_tunend_to_gtp_tunnel(tun);
 | 
			
		||||
	t = upf_gtp_tun_to_gtp_tunnel(tun);
 | 
			
		||||
	if (!t)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	rc = gtp_del_tunnel(g_upf->tunend.genl_id, g_upf->tunend.nl, t);
 | 
			
		||||
	rc = gtp_del_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl, t);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		LOG_GTP_TUN(tun, LOGL_ERROR, "Failed to delete tunnel\n");
 | 
			
		||||
		LOG_GTP_TUN(tun, LOGL_ERROR, "Failed to delete tunnel: %d %s\n", rc, strerror(rc));
 | 
			
		||||
	else
 | 
			
		||||
		tun->active = false;
 | 
			
		||||
 | 
			
		||||
@@ -493,12 +428,11 @@ static int upf_gtp_tunend_deactivate(struct upf_gtp_tunend *tun)
 | 
			
		||||
 | 
			
		||||
static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_tunend *t;
 | 
			
		||||
	struct upf_gtp_tun *t;
 | 
			
		||||
	/* Destruct and clean up all active tunnels before deleting the device */
 | 
			
		||||
	while ((t = llist_first_entry_or_null(&dev->tunnels, struct upf_gtp_tunend, entry)))
 | 
			
		||||
	while ((t = llist_first_entry_or_null(&dev->tunnels, struct upf_gtp_tun, entry)))
 | 
			
		||||
		talloc_free(t);
 | 
			
		||||
	llist_del(&dev->entry);
 | 
			
		||||
	/* osmo_fd_close() is a noop if ofd.fd == -1 */
 | 
			
		||||
	osmo_fd_close(&dev->gtpv0.ofd);
 | 
			
		||||
	osmo_fd_close(&dev->gtpv1.ofd);
 | 
			
		||||
	if (dev->created)
 | 
			
		||||
@@ -506,10 +440,8 @@ static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtp_tunend_cmp(const struct upf_tunend *a, const struct upf_tunend *b)
 | 
			
		||||
int upf_gtp_tun_desc_cmp(const struct upf_gtp_tun_desc *a, const struct upf_gtp_tun_desc *b)
 | 
			
		||||
{
 | 
			
		||||
	int r;
 | 
			
		||||
 | 
			
		||||
	if (a == b)
 | 
			
		||||
		return 0;
 | 
			
		||||
	if (!a)
 | 
			
		||||
@@ -517,10 +449,13 @@ int upf_gtp_tunend_cmp(const struct upf_tunend *a, const struct upf_tunend *b)
 | 
			
		||||
	if (!b)
 | 
			
		||||
		return 1;
 | 
			
		||||
 | 
			
		||||
#define CMP_MEMB(MEMB) OSMO_CMP(a->MEMB, b->MEMB)
 | 
			
		||||
	if ((r = CMP_MEMB(access.local.teid)))
 | 
			
		||||
		return r;
 | 
			
		||||
	if ((r = CMP_MEMB(access.remote.teid)))
 | 
			
		||||
		return r;
 | 
			
		||||
	return osmo_sockaddr_cmp(&a->access.remote.addr, &b->access.remote.addr);
 | 
			
		||||
#define CMP_RET(MEMB) do { \
 | 
			
		||||
		int _cmp = OSMO_CMP(a->MEMB, b->MEMB); \
 | 
			
		||||
		if (_cmp) \
 | 
			
		||||
			return _cmp; \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
	CMP_RET(local_teid);
 | 
			
		||||
	CMP_RET(remote_teid);
 | 
			
		||||
	return osmo_sockaddr_cmp(&a->gtp_remote_addr, &b->gtp_remote_addr);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,12 +13,8 @@
 | 
			
		||||
#define GTP1U_PORT	2152
 | 
			
		||||
 | 
			
		||||
enum gtp1u_msgt {
 | 
			
		||||
	GTP1U_MSGTYPE_ECHO_REQ			= 1,
 | 
			
		||||
	GTP1U_MSGTYPE_ECHO_RSP			= 2,
 | 
			
		||||
	GTP1U_MSGTYPE_ERRR_IND			= 26,
 | 
			
		||||
	GTP1U_MSGTYPE_SUPP_EXT_HDR_NOTIF	= 31,
 | 
			
		||||
	GTP1U_MSGTYPE_END_MARKER		= 254,
 | 
			
		||||
	GTP1U_MSGTYPE_GPDU			= 255,
 | 
			
		||||
	GTP1U_MSGTYPE_ECHO_REQ = 1,
 | 
			
		||||
	GTP1U_MSGTYPE_ECHO_RSP = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum gtp1u_iei {
 | 
			
		||||
@@ -35,7 +31,6 @@ struct gtp1u_hdr {
 | 
			
		||||
		pt:1, /*< Protocol Type: GTP=1, GTP'=0 */
 | 
			
		||||
		version:3; /*< Version: 1 */
 | 
			
		||||
#elif OSMO_IS_BIG_ENDIAN
 | 
			
		||||
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
 | 
			
		||||
	uint8_t version:3, pt:1, spare:1, e:1, s:1, pn:1;
 | 
			
		||||
#endif
 | 
			
		||||
	uint8_t msg_type;
 | 
			
		||||
@@ -52,46 +47,19 @@ struct gtp1u_hdr {
 | 
			
		||||
	uint8_t data2[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
static int tx_echo_resp(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, uint16_t seq_nr);
 | 
			
		||||
 | 
			
		||||
static int rx_echo_req(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, const struct gtp1u_hdr *rx_h,
 | 
			
		||||
		       size_t msg_len)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t seq_nr = 0;
 | 
			
		||||
	uint8_t recovery_count = 0;
 | 
			
		||||
	if (msg_len >= (sizeof(*rx_h) + 2) && rx_h->data2[0] == GTP1U_IEI_RECOVERY)
 | 
			
		||||
		recovery_count = rx_h->data2[1];
 | 
			
		||||
 | 
			
		||||
	seq_nr = rx_h->s;
 | 
			
		||||
	LOG_GTP_DEV(dev, LOGL_INFO, "<- %s: rx GTPv1-U Echo Request: seq_nr=%u recovery_count=%u\n",
 | 
			
		||||
		    osmo_sockaddr_to_str(remote), seq_nr, recovery_count);
 | 
			
		||||
 | 
			
		||||
	return tx_echo_resp(dev, remote, rx_h->ext.seq_nr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void rx_echo_resp(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, const struct gtp1u_hdr *rx_h,
 | 
			
		||||
			 size_t msg_len)
 | 
			
		||||
{
 | 
			
		||||
	if (msg_len < (sizeof(*rx_h) + 2)) {
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR,
 | 
			
		||||
			    "<- %s: rx GTPv1-U Echo Response, but message is too short (%zu < %zu)\n",
 | 
			
		||||
			    osmo_sockaddr_to_str_c(OTC_SELECT, remote), msg_len, (sizeof(*rx_h) + 2));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint8_t recovery_count = rx_h->data2[1];
 | 
			
		||||
	LOG_GTP_DEV(dev, LOGL_INFO, "<- %s: rx GTPv1-U Echo Response: seq_nr=%u recovery_count=%u\n",
 | 
			
		||||
		    osmo_sockaddr_to_str(remote), rx_h->ext.seq_nr, recovery_count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int tx_echo_resp(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, uint16_t seq_nr)
 | 
			
		||||
static int rx_echo_req(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, const struct gtp1u_hdr *rx_h)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct gtp1u_hdr *tx_h;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	msg = msgb_alloc_headroom(1024, 128, "GTPv1-U-echo-resp");
 | 
			
		||||
	tx_h = (void *)msgb_put(msg, sizeof(*tx_h));
 | 
			
		||||
	if (!rx_h->s) {
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTPv1-U ECHO REQ without sequence nr\n");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg = msgb_alloc_headroom(1024, 128, "GTP-echo-resp");
 | 
			
		||||
	tx_h = (void*)msgb_put(msg, sizeof(*tx_h));
 | 
			
		||||
 | 
			
		||||
	*tx_h = (struct gtp1u_hdr){
 | 
			
		||||
		/* 3GPP TS 29.281 5.1 defines that the ECHO REQ & RESP shall contain a sequence nr */
 | 
			
		||||
@@ -100,7 +68,7 @@ static int tx_echo_resp(struct upf_gtp_dev *dev, const struct osmo_sockaddr *rem
 | 
			
		||||
		.version = 1,
 | 
			
		||||
		.msg_type = GTP1U_MSGTYPE_ECHO_RSP,
 | 
			
		||||
		.ext = {
 | 
			
		||||
			.seq_nr = seq_nr,
 | 
			
		||||
			.seq_nr = rx_h->ext.seq_nr,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
@@ -108,62 +76,20 @@ static int tx_echo_resp(struct upf_gtp_dev *dev, const struct osmo_sockaddr *rem
 | 
			
		||||
 | 
			
		||||
	/* ECHO RESPONSE shall contain a recovery counter */
 | 
			
		||||
	msgb_put_u8(msg, GTP1U_IEI_RECOVERY);
 | 
			
		||||
	msgb_put_u8(msg, g_upf->tunend.recovery_count);
 | 
			
		||||
	msgb_put_u8(msg, g_upf->gtp.recovery_count);
 | 
			
		||||
 | 
			
		||||
	osmo_store16be(msg->tail - tx_h->data1, &tx_h->length);
 | 
			
		||||
 | 
			
		||||
	rc = sendto(dev->gtpv1.ofd.fd, msgb_data(msg), msgb_length(msg), 0, &remote->u.sa, sizeof(*remote));
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		rc = -errno;
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "-> %s: tx GTPv1-U Echo Response: sendto(len=%d): %s\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(remote), msgb_length(msg), strerror(-rc));
 | 
			
		||||
	} else {
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_INFO, "-> %s: tx GTPv1-U Echo Response: seq_nr=%u recovery_count=%u\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(remote), seq_nr, g_upf->tunend.recovery_count);
 | 
			
		||||
		rc = 0;
 | 
			
		||||
		int err = errno;
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "GTP1-U sendto(len=%d, to=%s): %s\n", msgb_length(msg),
 | 
			
		||||
			    osmo_sockaddr_to_str(remote), strerror(err));
 | 
			
		||||
	}
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtpu_echo_req_tx(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, uint16_t seq_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct gtp1u_hdr *tx_h;
 | 
			
		||||
	int rc;
 | 
			
		||||
	uint8_t msgbuf[sizeof(struct gtp1u_hdr) + 2];
 | 
			
		||||
 | 
			
		||||
	tx_h = (void *)msgbuf;
 | 
			
		||||
	*tx_h = (struct gtp1u_hdr){
 | 
			
		||||
		/* 3GPP TS 29.281 5.1 defines that the ECHO REQ & RESP shall contain a sequence nr */
 | 
			
		||||
		.s = 1,
 | 
			
		||||
		.pt = 1,
 | 
			
		||||
		.version = 1,
 | 
			
		||||
		.msg_type = GTP1U_MSGTYPE_ECHO_REQ,
 | 
			
		||||
		.ext = {
 | 
			
		||||
			.seq_nr = seq_nr,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* ECHO REQUEST shall contain a recovery counter */
 | 
			
		||||
	tx_h->data2[0] = GTP1U_IEI_RECOVERY;
 | 
			
		||||
	tx_h->data2[1] = g_upf->tunend.recovery_count;
 | 
			
		||||
 | 
			
		||||
	osmo_store16be(sizeof(msgbuf) - offsetof(struct gtp1u_hdr, data1), &tx_h->length);
 | 
			
		||||
 | 
			
		||||
	rc = sendto(dev->gtpv1.ofd.fd, msgbuf, sizeof(msgbuf), 0, &remote->u.sa, sizeof(*remote));
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		rc = -errno;
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "GTP1-U sendto(len=%zu, to=%s): %s\n", sizeof(msgbuf),
 | 
			
		||||
			    osmo_sockaddr_to_str(remote), strerror(-rc));
 | 
			
		||||
	} else {
 | 
			
		||||
		rc = 0;
 | 
			
		||||
	}
 | 
			
		||||
	LOG_GTP_DEV(dev, LOGL_INFO, "<- %s: tx GTP1-U Echo Request: seq_nr=%u recovery_count=%u\n",
 | 
			
		||||
		    osmo_sockaddr_to_str(remote), seq_nr, g_upf->tunend.recovery_count);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtpu_read_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
int upf_gtpu_echo_read_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_gtp_dev *dev = ofd->data;
 | 
			
		||||
 | 
			
		||||
@@ -186,61 +112,43 @@ int upf_gtpu_read_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
	/* A GTPv1-U header of size 8 is valid, but this code expects to handle only ECHO REQUEST messages. These are
 | 
			
		||||
	 * required to have a sequence number, hence this check here consciously uses the full sizeof(*h) == 12. */
 | 
			
		||||
	if (sz < sizeof(*h)) {
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR,
 | 
			
		||||
			    "<- %s: rx GTPv1-U packet smaller than the GTPv1-U header + sequence nr: %zd < %zu\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(&remote), sz, sizeof(*h));
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTP packet smaller than the GTPv1-U header + sequence nr: %zd < %zu\n",
 | 
			
		||||
			    sz, sizeof(*h));
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	h = (const struct gtp1u_hdr *)buf;
 | 
			
		||||
	if (h->version != 1) {
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "<- %s: rx GTPv1-U v%u: only GTP version 1 supported\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(&remote), h->version);
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTP v%u: only GTP version 1 supported\n", h->version);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	h_length = osmo_load16be(&h->length);
 | 
			
		||||
	if (offsetof(struct gtp1u_hdr, data1) + h_length > sz) {
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "<- %s: rx GTPv1-U: header + h.length = %zu > received bytes = %zd\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(&remote), offsetof(struct gtp1u_hdr, data1) + h_length, sz);
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTP: header + h.length = %zu > received bytes = %zd\n",
 | 
			
		||||
			    offsetof(struct gtp1u_hdr, data1) + h_length, sz);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (h->msg_type) {
 | 
			
		||||
	case GTP1U_MSGTYPE_ECHO_REQ:
 | 
			
		||||
		return rx_echo_req(dev, &remote, h, sz);
 | 
			
		||||
	case GTP1U_MSGTYPE_ECHO_RSP:
 | 
			
		||||
		rx_echo_resp(dev, &remote, h, sz);
 | 
			
		||||
		return 0;
 | 
			
		||||
	case GTP1U_MSGTYPE_ERRR_IND:
 | 
			
		||||
		/* 3GPP TS 29.281 7.3.1: Log "Tunnel Endpoint Identifier Data I" and "GTP-U Peer Address" */
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_NOTICE, "%s rx: GTPv1-U Error Indication not supported\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(&remote));
 | 
			
		||||
		return 0;
 | 
			
		||||
	case GTP1U_MSGTYPE_GPDU:
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_NOTICE, "%s rx: GTPv1-U PDU TEID=0x%08x over slow path not supported\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(&remote), osmo_load32be(&h->tei));
 | 
			
		||||
		return 0;
 | 
			
		||||
		return rx_echo_req(dev, &remote, h);
 | 
			
		||||
	default:
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "%s rx: GTPv1-U message type %u not supported\n",
 | 
			
		||||
			    osmo_sockaddr_to_str(&remote), h->msg_type);
 | 
			
		||||
		LOG_GTP_DEV(dev, LOGL_ERROR, "rx: GTPv1-U message type %u not supported\n", h->msg_type);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_gtpu_echo_setup(struct upf_gtp_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	if (dev->gtpv1.ofd.fd == -1) {
 | 
			
		||||
		LOGP(DGTP, LOGL_ERROR, "Cannot setup GTPv1-U ECHO: socket not initialized\n");
 | 
			
		||||
		LOGP(DGTP, LOGL_ERROR, "Cannot setup GTP-U ECHO: GTP-v1 socket not initialized\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* the caller should already have osmo_fd_register()ed when setting up the socket. */
 | 
			
		||||
	OSMO_ASSERT(osmo_fd_is_registered(&dev->gtpv1.ofd));
 | 
			
		||||
	/* make sure there is no cb yet that this would be replacing. */
 | 
			
		||||
	OSMO_ASSERT(dev->gtpv1.ofd.cb == NULL);
 | 
			
		||||
 | 
			
		||||
	dev->gtpv1.ofd.cb = upf_gtpu_read_cb;
 | 
			
		||||
	dev->gtpv1.ofd.cb = upf_gtpu_echo_read_cb;
 | 
			
		||||
	dev->gtpv1.ofd.data = dev;
 | 
			
		||||
	return 0;
 | 
			
		||||
	return osmo_fd_register(&dev->gtpv1.ofd);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,351 +26,110 @@
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/upf/upf.h>
 | 
			
		||||
#include <osmocom/upf/upf_nft.h>
 | 
			
		||||
 | 
			
		||||
static char *upf_nft_ruleset_table_create(void *ctx, const char *table_name)
 | 
			
		||||
{
 | 
			
		||||
	return talloc_asprintf(ctx, "add table inet %s { flags owner; };\n", table_name);
 | 
			
		||||
	return talloc_asprintf(ctx, "add table inet %s\n", table_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *upf_nft_ruleset_vmap_init(void *ctx, const char *table_name, int priority_pre, int priority_post)
 | 
			
		||||
{
 | 
			
		||||
	/* add chain inet osmo-upf pre { type filter hook prerouting priority -300; policy accept; }
 | 
			
		||||
	 * add chain inet osmo-upf post { type filter hook postrouting priority 400; policy accept; }
 | 
			
		||||
	 * add map inet osmo-upf tunmap-pre { typeof ip daddr . @ih,32,32 : verdict; }
 | 
			
		||||
	 * add map inet osmo-upf tunmap-post { typeof meta mark : verdict; }
 | 
			
		||||
	 * add rule inet osmo-upf pre udp dport 2152 ip daddr . @ih,32,32 vmap @tunmap-pre
 | 
			
		||||
	 * add rule inet osmo-upf post meta mark vmap @tunmap-post
 | 
			
		||||
	 */
 | 
			
		||||
	return talloc_asprintf(ctx,
 | 
			
		||||
		"add chain inet %s pre { type filter hook prerouting priority %d; policy accept; };\n"
 | 
			
		||||
		"add chain inet %s post { type filter hook postrouting priority %d; policy accept; };\n"
 | 
			
		||||
		"add map inet %s tunmap-pre { typeof ip daddr . @ih,32,32 : verdict; };\n"
 | 
			
		||||
		"add map inet %s tunmap-post { typeof meta mark : verdict; };\n"
 | 
			
		||||
		"add rule inet %s pre udp dport %u ip daddr . @ih,32,32 vmap @tunmap-pre;\n"
 | 
			
		||||
		"add rule inet %s post meta mark vmap @tunmap-post;\n",
 | 
			
		||||
		table_name, priority_pre,
 | 
			
		||||
		table_name, priority_post,
 | 
			
		||||
		table_name,
 | 
			
		||||
		table_name,
 | 
			
		||||
		table_name, PORT_GTP1_U,
 | 
			
		||||
		table_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int upf_nft_run_now(const char *ruleset)
 | 
			
		||||
static int upf_nft_run(const char *ruleset)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	const int logmax = 256;
 | 
			
		||||
 | 
			
		||||
	if (g_upf->tunmap.mockup) {
 | 
			
		||||
		LOGP(DNFT, LOGL_NOTICE, "tunmap/mockup active: not running nft ruleset: '%s'\n", ruleset);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!g_upf->tunmap.nft_ctx) {
 | 
			
		||||
	if (!g_upf->nft.nft_ctx) {
 | 
			
		||||
		rc = upf_nft_init();
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc = nft_run_cmd_from_buffer(g_upf->tunmap.nft_ctx, ruleset);
 | 
			
		||||
	rc = nft_run_cmd_from_buffer(g_upf->nft.nft_ctx, ruleset);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		LOGP(DNFT, LOGL_ERROR, "error running nft ruleset: rc=%d ruleset=%s\n",
 | 
			
		||||
		     rc, osmo_quote_str_c(OTC_SELECT, ruleset, -1));
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (log_check_level(DNFT, LOGL_DEBUG)) {
 | 
			
		||||
		size_t l = strlen(ruleset);
 | 
			
		||||
		LOGP(DNFT, LOGL_DEBUG, "ran nft ruleset, %zu chars: \"%s%s\"\n",
 | 
			
		||||
		     l,
 | 
			
		||||
		     osmo_escape_cstr_c(OTC_SELECT, ruleset, OSMO_MIN(logmax, l)),
 | 
			
		||||
		     l > logmax ? "..." : "");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct nft_queue {
 | 
			
		||||
	struct osmo_tdef *flush_time_tdef;
 | 
			
		||||
	struct osmo_tdef *ruleset_max_tdef;
 | 
			
		||||
	struct osmo_strbuf sb;
 | 
			
		||||
	/* 128 NFT rulesets amount to about 110 kb of char */
 | 
			
		||||
	char buf[1<<17];
 | 
			
		||||
	unsigned int ruleset_count;
 | 
			
		||||
	struct osmo_timer_list timer;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void nft_queue_clear_buf(struct nft_queue *q)
 | 
			
		||||
{
 | 
			
		||||
	q->sb = (struct osmo_strbuf){ .buf = q->buf, .len = sizeof(q->buf) };
 | 
			
		||||
	q->buf[0] = '\0';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nft_queue_init(void *ctx, struct nft_queue *q,
 | 
			
		||||
			   struct osmo_tdef *flush_time_tdef,
 | 
			
		||||
			   struct osmo_tdef *ruleset_max_tdef)
 | 
			
		||||
{
 | 
			
		||||
	*q = (struct nft_queue){
 | 
			
		||||
		.flush_time_tdef = flush_time_tdef,
 | 
			
		||||
		.ruleset_max_tdef = ruleset_max_tdef,
 | 
			
		||||
	};
 | 
			
		||||
	nft_queue_clear_buf(q);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nft_queue_flush(struct nft_queue *q, const char *reason)
 | 
			
		||||
{
 | 
			
		||||
	static unsigned int flush_count = 0;
 | 
			
		||||
	static unsigned int ruleset_count = 0;
 | 
			
		||||
 | 
			
		||||
	/* We will now flush the queue empty. A timer needs to run only when the next pending entry is added. */
 | 
			
		||||
	osmo_timer_del(&q->timer);
 | 
			
		||||
 | 
			
		||||
	/* Nothing to send? */
 | 
			
		||||
	if (!q->sb.chars_needed)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	flush_count++;
 | 
			
		||||
	ruleset_count += q->ruleset_count;
 | 
			
		||||
	LOGP(DNFT, LOGL_INFO, "Flushing NFT ruleset queue: %s: n:%u strlen:%zu (flush count: %u avg rules per flush: %s)\n",
 | 
			
		||||
	     reason,
 | 
			
		||||
	     q->ruleset_count, q->sb.chars_needed,
 | 
			
		||||
	     flush_count, osmo_int_to_float_str_c(OTC_SELECT, 10 * ruleset_count / flush_count, 1));
 | 
			
		||||
 | 
			
		||||
	q->ruleset_count = 0;
 | 
			
		||||
 | 
			
		||||
	upf_nft_run_now(q->sb.buf);
 | 
			
		||||
 | 
			
		||||
	nft_queue_clear_buf(q);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nft_queue_flush_cb(void *q)
 | 
			
		||||
{
 | 
			
		||||
	nft_queue_flush(q, "timeout");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nft_enqueue(struct nft_queue *q,
 | 
			
		||||
		       int (*tunmap_to_str_buf)(char *buf, size_t len, struct upf_tunmap *tunmap),
 | 
			
		||||
		       struct upf_tunmap *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	int ruleset_max;
 | 
			
		||||
	struct osmo_strbuf q_sb_was = q->sb;
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_APPEND(q->sb, tunmap_to_str_buf, tunmap);
 | 
			
		||||
 | 
			
		||||
	/* is that being cut off? then revert the addition. This should never happen in practice. */
 | 
			
		||||
	if (q->sb.chars_needed >= q->sb.len) {
 | 
			
		||||
		q->sb = q_sb_was;
 | 
			
		||||
		if (q->sb.pos)
 | 
			
		||||
			*q->sb.pos = '\0';
 | 
			
		||||
		nft_queue_flush(q, "reached max nr of chars");
 | 
			
		||||
		OSMO_STRBUF_APPEND(q->sb, tunmap_to_str_buf, tunmap);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Append separator -- no problem if that gets cut off. */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(q->sb, "\n");
 | 
			
		||||
 | 
			
		||||
	q->ruleset_count++;
 | 
			
		||||
 | 
			
		||||
	LOGP(DNFT, LOGL_INFO, "Added NFT ruleset to queue: n:%u strlen:%zu\n",
 | 
			
		||||
	     q->ruleset_count, q->sb.chars_needed);
 | 
			
		||||
 | 
			
		||||
	/* Added a rule, see if it has reached ruleset_max. */
 | 
			
		||||
	ruleset_max = osmo_tdef_get(q->ruleset_max_tdef, q->ruleset_max_tdef->T, OSMO_TDEF_CUSTOM, 128);
 | 
			
		||||
	if (q->ruleset_count >= ruleset_max) {
 | 
			
		||||
		nft_queue_flush(q, "reached max nr of rules");
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Item added. If the timer is not running yet, schedule a flush in given timeout */
 | 
			
		||||
	if (!osmo_timer_pending(&q->timer)) {
 | 
			
		||||
		struct osmo_tdef *t;
 | 
			
		||||
		unsigned long us;
 | 
			
		||||
		osmo_timer_setup(&q->timer, nft_queue_flush_cb, q);
 | 
			
		||||
		t = q->flush_time_tdef;
 | 
			
		||||
		us = osmo_tdef_get(t, t->T, OSMO_TDEF_US, 100000);
 | 
			
		||||
		osmo_timer_schedule(&q->timer, us / 1000000, us % 1000000);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nft_queue_free(struct nft_queue *q)
 | 
			
		||||
{
 | 
			
		||||
	osmo_timer_del(&q->timer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct nft_queue g_nft_queue = {};
 | 
			
		||||
 | 
			
		||||
int upf_nft_init()
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	nft_queue_init(g_upf, &g_nft_queue,
 | 
			
		||||
		       osmo_tdef_get_entry(g_upf_nft_tdefs, -32),
 | 
			
		||||
		       osmo_tdef_get_entry(g_upf_nft_tdefs, -33));
 | 
			
		||||
 | 
			
		||||
	/* Always set up the default settings, also in mockup mode, so that the VTY reflects sane values */
 | 
			
		||||
	if (!g_upf->tunmap.table_name)
 | 
			
		||||
		g_upf->tunmap.table_name = talloc_strdup(g_upf, "osmo-upf");
 | 
			
		||||
 | 
			
		||||
	/* When in mockup mode, do not set up nft_ctx and netfilter table */
 | 
			
		||||
	if (g_upf->tunmap.mockup) {
 | 
			
		||||
		LOGP(DNFT, LOGL_NOTICE,
 | 
			
		||||
		     "tunmap/mockup active: not allocating libnftables nft_ctx. FOR TESTING PURPOSES ONLY.\n");
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g_upf->tunmap.nft_ctx = nft_ctx_new(NFT_CTX_DEFAULT);
 | 
			
		||||
	if (!g_upf->tunmap.nft_ctx) {
 | 
			
		||||
	g_upf->nft.nft_ctx = nft_ctx_new(NFT_CTX_DEFAULT);
 | 
			
		||||
	if (!g_upf->nft.nft_ctx) {
 | 
			
		||||
		LOGP(DNFT, LOGL_ERROR, "cannot allocate libnftables nft_ctx\n");
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc = upf_nft_run_now(upf_nft_tunmap_get_table_init_str(OTC_SELECT));
 | 
			
		||||
	if (!g_upf->nft.table_name)
 | 
			
		||||
		g_upf->nft.table_name = talloc_strdup(g_upf, "osmo-upf");
 | 
			
		||||
 | 
			
		||||
	rc = upf_nft_run(upf_nft_ruleset_table_create(OTC_SELECT, g_upf->nft.table_name));
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DNFT, LOGL_ERROR, "Failed to create nft table %s\n",
 | 
			
		||||
		     osmo_quote_str_c(OTC_SELECT, g_upf->tunmap.table_name, -1));
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
	LOGP(DNFT, LOGL_NOTICE, "Created nft table %s\n", osmo_quote_str_c(OTC_SELECT, g_upf->tunmap.table_name, -1));
 | 
			
		||||
 | 
			
		||||
	rc = upf_nft_run_now(upf_nft_tunmap_get_vmap_init_str(OTC_SELECT));
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DNFT, LOGL_ERROR, "Failed to initialize nft verdict map in table %s\n", g_upf->tunmap.table_name);
 | 
			
		||||
		     osmo_quote_str_c(OTC_SELECT, g_upf->nft.table_name, -1));
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
	LOGP(DNFT, LOGL_NOTICE, "Created nft table %s\n", osmo_quote_str_c(OTC_SELECT, g_upf->nft.table_name, -1));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_nft_free()
 | 
			
		||||
{
 | 
			
		||||
	nft_queue_free(&g_nft_queue);
 | 
			
		||||
	if (!g_upf->tunmap.nft_ctx)
 | 
			
		||||
	if (!g_upf->nft.nft_ctx)
 | 
			
		||||
		return 0;
 | 
			
		||||
	nft_ctx_free(g_upf->tunmap.nft_ctx);
 | 
			
		||||
	g_upf->tunmap.nft_ctx = NULL;
 | 
			
		||||
	nft_ctx_free(g_upf->nft.nft_ctx);
 | 
			
		||||
	g_upf->nft.nft_ctx = NULL;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct upf_nft_args_peer {
 | 
			
		||||
	/* The source IP address in packets received from this peer */
 | 
			
		||||
	const struct osmo_sockaddr *addr_remote;
 | 
			
		||||
	const struct osmo_sockaddr *addr;
 | 
			
		||||
	/* The TEID that we send to the peer in GTP packets. */
 | 
			
		||||
	uint32_t teid_remote;
 | 
			
		||||
	/* The local destination IP address in packets received from this peer */
 | 
			
		||||
	const struct osmo_sockaddr *addr_local;
 | 
			
		||||
	/* The TEID that the peer sends to us in GTP packets. */
 | 
			
		||||
	uint32_t teid_local;
 | 
			
		||||
	/* The nft chain id that forwards packets received on addr_local,teid_local. Also used for the 'mark' id in
 | 
			
		||||
	 * the verdict map ruleset. */
 | 
			
		||||
	uint32_t chain_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct upf_nft_args {
 | 
			
		||||
	/* global table name */
 | 
			
		||||
	const char *table_name;
 | 
			
		||||
	/* chain name for this specific tunnel mapping */
 | 
			
		||||
	uint32_t chain_id;
 | 
			
		||||
	int priority;
 | 
			
		||||
 | 
			
		||||
	struct upf_nft_args_peer peer_a;
 | 
			
		||||
	struct upf_nft_args_peer peer_b;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int tunmap_add_single_direction(char *buf, size_t buflen,
 | 
			
		||||
				       const struct upf_nft_args *args,
 | 
			
		||||
				       bool dir_a2b)
 | 
			
		||||
static int tunmap_single_direction(char *buf, size_t buflen,
 | 
			
		||||
				   const struct upf_nft_args *args,
 | 
			
		||||
				   const struct upf_nft_args_peer *from_peer,
 | 
			
		||||
				   const struct upf_nft_args_peer *to_peer)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	const struct upf_nft_args_peer *from_peer;
 | 
			
		||||
	const struct upf_nft_args_peer *to_peer;
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add rule inet %s " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u", args->table_name, args->chain_id);
 | 
			
		||||
 | 
			
		||||
	if (dir_a2b) {
 | 
			
		||||
		from_peer = &args->peer_a;
 | 
			
		||||
		to_peer = &args->peer_b;
 | 
			
		||||
	} else {
 | 
			
		||||
		from_peer = &args->peer_b;
 | 
			
		||||
		to_peer = &args->peer_a;
 | 
			
		||||
	}
 | 
			
		||||
	/* Match only UDP packets */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " meta l4proto udp");
 | 
			
		||||
 | 
			
		||||
	/* # add chain for verdict map in prerouting
 | 
			
		||||
	 * add chain inet osmo-upf tunmap-pre-123
 | 
			
		||||
	 * # mangle destination address at prerouting
 | 
			
		||||
	 * add rule inet osmo-upf tunmap-pre-123 ip daddr set 1.1.1.1 meta mark set 123 counter accept
 | 
			
		||||
	 *
 | 
			
		||||
	 * # add chain for verdict map in postrouting
 | 
			
		||||
	 * add chain inet osmo-upf tunmap-post-123
 | 
			
		||||
	 * # mangle source address and GTP TID at postrouting
 | 
			
		||||
	 * add rule inet osmo-upf tunmap-post-123 ip saddr set 2.2.2.1 udp sport set 2152 @ih,32,32 set 0x00000102 counter accept
 | 
			
		||||
	 *
 | 
			
		||||
	 * # add elements to verdict map, jump to chain
 | 
			
		||||
	 * add element inet osmo-upf tunmap-pre { 2.2.2.3 . 0x00000203 : jump tunmap-pre-123 }
 | 
			
		||||
	 * add element inet osmo-upf tunmap-post { 123 : jump tunmap-post-123 }
 | 
			
		||||
	 */
 | 
			
		||||
	/* Match on packets coming in from from_peer */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " ip saddr ");
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, from_peer->addr);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add chain inet %s tunmap-pre-%u;\n",
 | 
			
		||||
			   args->table_name, from_peer->chain_id);
 | 
			
		||||
	/* Match on the TEID in the header */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " @ih,32,32 0x%08x", from_peer->teid_local);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add rule inet %s tunmap-pre-%u",
 | 
			
		||||
			   args->table_name, from_peer->chain_id);
 | 
			
		||||
	/* Change destination address to to_peer */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " ip daddr set ");
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, to_peer->addr_remote);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " meta mark set %u counter accept;\n", from_peer->chain_id);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, to_peer->addr);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add chain inet %s tunmap-post-%u;\n",
 | 
			
		||||
			   args->table_name, from_peer->chain_id);
 | 
			
		||||
	/* Change the TEID in the header to the one to_peer expects */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " @ih,32,32 set 0x%08x", to_peer->teid_remote);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add rule inet %s tunmap-post-%u",
 | 
			
		||||
			   args->table_name, from_peer->chain_id);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " ip saddr set ");
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, to_peer->addr_local);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " udp sport set 2152");
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " @ih,32,32 set 0x%x", to_peer->teid_remote);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " counter accept;\n");
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add element inet %s tunmap-pre { ",
 | 
			
		||||
			   args->table_name);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, from_peer->addr_local);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " . 0x%x : jump tunmap-pre-%u };\n",
 | 
			
		||||
			   from_peer->teid_local, from_peer->chain_id);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add element inet %s tunmap-post { %u : jump tunmap-post-%u };\n",
 | 
			
		||||
			   args->table_name, from_peer->chain_id, from_peer->chain_id);
 | 
			
		||||
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int tunmap_del_single_direction(char *buf, size_t buflen,
 | 
			
		||||
				       const struct upf_nft_args *args,
 | 
			
		||||
				       bool dir_a2b)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
	const struct upf_nft_args_peer *from_peer;
 | 
			
		||||
 | 
			
		||||
	if (dir_a2b)
 | 
			
		||||
		from_peer = &args->peer_a;
 | 
			
		||||
	else
 | 
			
		||||
		from_peer = &args->peer_b;
 | 
			
		||||
 | 
			
		||||
	/* delete element inet osmo-upf tunmap-pre { 2.2.2.3 . 0x203 }
 | 
			
		||||
	 * delete element inet osmo-upf tunmap-post { 123 }
 | 
			
		||||
	 * delete chain inet osmo-upf tunmap-pre-123
 | 
			
		||||
	 * delete chain inet osmo-upf tunmap-post-123
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "delete element inet %s tunmap-pre { ",
 | 
			
		||||
			   args->table_name);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, from_peer->addr_local);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " . 0x%x };\n", from_peer->teid_local);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "delete element inet %s tunmap-post { %u };\n",
 | 
			
		||||
			   args->table_name, from_peer->chain_id);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "delete chain inet %s tunmap-pre-%u;\n",
 | 
			
		||||
			   args->table_name, from_peer->chain_id);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "delete chain inet %s tunmap-post-%u;\n",
 | 
			
		||||
			   args->table_name, from_peer->chain_id);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, " counter\n");
 | 
			
		||||
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
@@ -379,129 +138,70 @@ static int upf_nft_ruleset_tunmap_create_buf(char *buf, size_t buflen, const str
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
 | 
			
		||||
	/* Add a chain for this tunnel mapping */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "add chain inet %s " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u { type filter hook prerouting priority %d; }\n",
 | 
			
		||||
			   args->table_name, args->chain_id, args->priority);
 | 
			
		||||
 | 
			
		||||
	/* Forwarding from peer_a to peer_b */
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, tunmap_add_single_direction, args, true);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, tunmap_single_direction, args, &args->peer_a, &args->peer_b);
 | 
			
		||||
	/* And from peer_b to peer_a */
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, tunmap_add_single_direction, args, false);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, tunmap_single_direction, args, &args->peer_b, &args->peer_a);
 | 
			
		||||
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *upf_nft_ruleset_tunmap_create_c(void *ctx, const struct upf_nft_args *args)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 512, "ERROR", upf_nft_ruleset_tunmap_create_buf, args)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int upf_nft_ruleset_tunmap_delete_buf(char *buf, size_t buflen, const struct upf_nft_args *args)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
 | 
			
		||||
	/* Forwarding from peer_a to peer_b */
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, tunmap_del_single_direction, args, true);
 | 
			
		||||
	/* And from peer_b to peer_a */
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, tunmap_del_single_direction, args, false);
 | 
			
		||||
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "delete chain inet %s " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u\n",
 | 
			
		||||
			   args->table_name, args->chain_id);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_nft_tunmap_to_str_buf(char *buf, size_t buflen, const struct upf_tunmap *tunmap)
 | 
			
		||||
static char *upf_nft_ruleset_tunmap_delete_c(void *ctx, const struct upf_nft_args *args)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
 | 
			
		||||
 | 
			
		||||
	/* ACCESS 1.1.1.2:0x102 <---> 2.2.2.1:0x201 UPF 2.2.2.3:0x203 <---> 3.3.3.2:0x302 CORE */
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, "ACCESS ");
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tunmap->access.tun.remote.addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, ":0x%x <---> ", tunmap->access.tun.remote.teid);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tunmap->access.tun.local.addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, ":0x%x UPF ", tunmap->access.tun.local.teid);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tunmap->core.tun.local.addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, ":0x%x <---> ", tunmap->core.tun.local.teid);
 | 
			
		||||
	OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tunmap->core.tun.remote.addr);
 | 
			
		||||
	OSMO_STRBUF_PRINTF(sb, ":0x%x CORE", tunmap->core.tun.remote.teid);
 | 
			
		||||
	return sb.chars_needed;
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_nft_ruleset_tunmap_delete_buf, args)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *upf_nft_tunmap_to_str_c(void *ctx, const struct upf_tunmap *tunmap)
 | 
			
		||||
static void upf_nft_args_from_tunmap_desc(struct upf_nft_args *args, const struct upf_nft_tunmap_desc *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 128, "ERROR", upf_nft_tunmap_to_str_buf, tunmap)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void upf_nft_args_from_tunmap(struct upf_nft_args *args, const struct upf_tunmap *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_ASSERT(osmo_sockaddr_port(&tunmap->access.tun.remote.addr.u.sa) == 0);
 | 
			
		||||
	OSMO_ASSERT(osmo_sockaddr_port(&tunmap->access.tun.local.addr.u.sa) == 0);
 | 
			
		||||
	OSMO_ASSERT(osmo_sockaddr_port(&tunmap->core.tun.remote.addr.u.sa) == 0);
 | 
			
		||||
	OSMO_ASSERT(osmo_sockaddr_port(&tunmap->core.tun.local.addr.u.sa) == 0);
 | 
			
		||||
 | 
			
		||||
	*args = (struct upf_nft_args){
 | 
			
		||||
		.table_name = g_upf->tunmap.table_name,
 | 
			
		||||
		.table_name = g_upf->nft.table_name,
 | 
			
		||||
		.chain_id = tunmap->id,
 | 
			
		||||
		.priority = g_upf->nft.priority,
 | 
			
		||||
		.peer_a = {
 | 
			
		||||
			.addr_remote = &tunmap->access.tun.remote.addr,
 | 
			
		||||
			.teid_remote = tunmap->access.tun.remote.teid,
 | 
			
		||||
			.addr_local = &tunmap->access.tun.local.addr,
 | 
			
		||||
			.teid_local = tunmap->access.tun.local.teid,
 | 
			
		||||
			.chain_id = tunmap->access.chain_id,
 | 
			
		||||
			.addr = &tunmap->access.gtp_remote_addr,
 | 
			
		||||
			.teid_remote = tunmap->access.remote_teid,
 | 
			
		||||
			.teid_local = tunmap->access.local_teid,
 | 
			
		||||
		},
 | 
			
		||||
		.peer_b = {
 | 
			
		||||
			.addr_remote = &tunmap->core.tun.remote.addr,
 | 
			
		||||
			.teid_remote = tunmap->core.tun.remote.teid,
 | 
			
		||||
			.addr_local = &tunmap->core.tun.local.addr,
 | 
			
		||||
			.teid_local = tunmap->core.tun.local.teid,
 | 
			
		||||
			.chain_id = tunmap->core.chain_id,
 | 
			
		||||
			.addr = &tunmap->core.gtp_remote_addr,
 | 
			
		||||
			.teid_remote = tunmap->core.remote_teid,
 | 
			
		||||
			.teid_local = tunmap->core.local_teid,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *upf_nft_tunmap_get_table_init_str(void *ctx)
 | 
			
		||||
{
 | 
			
		||||
	return upf_nft_ruleset_table_create(ctx, g_upf->tunmap.table_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *upf_nft_tunmap_get_vmap_init_str(void *ctx)
 | 
			
		||||
{
 | 
			
		||||
	return upf_nft_ruleset_vmap_init(ctx, g_upf->tunmap.table_name, g_upf->tunmap.priority_pre,
 | 
			
		||||
					 g_upf->tunmap.priority_post);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_nft_tunmap_get_ruleset_str_buf(char *buf, size_t len, struct upf_tunmap *tunmap)
 | 
			
		||||
int upf_nft_tunmap_create(struct upf_nft_tunmap_desc *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_nft_args args;
 | 
			
		||||
	upf_nft_args_from_tunmap(&args, tunmap);
 | 
			
		||||
	return upf_nft_ruleset_tunmap_create_buf(buf, len, &args);
 | 
			
		||||
 | 
			
		||||
	/* Give this tunnel mapping a new id, returned to the caller so that the tunnel mapping can be deleted later */
 | 
			
		||||
	g_upf->nft.next_id_state++;
 | 
			
		||||
	tunmap->id = g_upf->nft.next_id_state;
 | 
			
		||||
 | 
			
		||||
	upf_nft_args_from_tunmap_desc(&args, tunmap);
 | 
			
		||||
	return upf_nft_run(upf_nft_ruleset_tunmap_create_c(OTC_SELECT, &args));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *upf_nft_tunmap_get_ruleset_str(void *ctx, struct upf_tunmap *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 1024, "ERROR", upf_nft_tunmap_get_ruleset_str_buf, tunmap)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_nft_tunmap_get_ruleset_del_str_buf(char *buf, size_t len, struct upf_tunmap *tunmap)
 | 
			
		||||
int upf_nft_tunmap_delete(struct upf_nft_tunmap_desc *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	struct upf_nft_args args;
 | 
			
		||||
	upf_nft_args_from_tunmap(&args, tunmap);
 | 
			
		||||
	return upf_nft_ruleset_tunmap_delete_buf(buf, len, &args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *upf_nft_tunmap_get_ruleset_del_str(void *ctx, struct upf_tunmap *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_NAME_C_IMPL(ctx, 1024, "ERROR", upf_nft_tunmap_get_ruleset_del_str_buf, tunmap)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int upf_nft_tunmap_ensure_chain_id(struct upf_nft_tun *tun)
 | 
			
		||||
{
 | 
			
		||||
	if (tun->chain_id)
 | 
			
		||||
		return 0;
 | 
			
		||||
	tun->chain_id = upf_next_chain_id();
 | 
			
		||||
	if (!tun->chain_id)
 | 
			
		||||
		return -ENOSPC;
 | 
			
		||||
	hash_add(g_upf->tunmap.nft_tun_by_chain_id, &tun->node_by_chain_id, tun->chain_id);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_nft_tunmap_create(struct upf_tunmap *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	if (upf_nft_tunmap_ensure_chain_id(&tunmap->access)
 | 
			
		||||
	    || upf_nft_tunmap_ensure_chain_id(&tunmap->core))
 | 
			
		||||
		return -ENOSPC;
 | 
			
		||||
	return nft_enqueue(&g_nft_queue, upf_nft_tunmap_get_ruleset_str_buf, tunmap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int upf_nft_tunmap_delete(struct upf_tunmap *tunmap)
 | 
			
		||||
{
 | 
			
		||||
	return nft_enqueue(&g_nft_queue, upf_nft_tunmap_get_ruleset_del_str_buf, tunmap);
 | 
			
		||||
	upf_nft_args_from_tunmap_desc(&args, tunmap);
 | 
			
		||||
	return upf_nft_run(upf_nft_ruleset_tunmap_delete_c(OTC_SELECT, &args));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -37,14 +37,11 @@
 | 
			
		||||
#include <osmocom/upf/up_peer.h>
 | 
			
		||||
#include <osmocom/upf/up_session.h>
 | 
			
		||||
#include <osmocom/upf/up_gtp_action.h>
 | 
			
		||||
#include <osmocom/upf/netinst.h>
 | 
			
		||||
#include <osmocom/upf/upf_gtpu_echo.h>
 | 
			
		||||
 | 
			
		||||
enum upf_vty_node {
 | 
			
		||||
	PFCP_NODE = _LAST_OSMOVTY_NODE + 1,
 | 
			
		||||
	TUNEND_NODE,
 | 
			
		||||
	TUNMAP_NODE,
 | 
			
		||||
	NETINST_NODE,
 | 
			
		||||
	GTP_NODE,
 | 
			
		||||
	NFT_NODE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct cmd_node cfg_pfcp_node = {
 | 
			
		||||
@@ -54,7 +51,7 @@ static struct cmd_node cfg_pfcp_node = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define pfcp_vty (g_upf->pfcp.vty_cfg)
 | 
			
		||||
#define tunend_vty (g_upf->tunend.vty_cfg)
 | 
			
		||||
#define gtp_vty (g_upf->gtp.vty_cfg)
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_pfcp, cfg_pfcp_cmd,
 | 
			
		||||
      "pfcp",
 | 
			
		||||
@@ -81,32 +78,26 @@ DEFUN(cfg_pfcp_local_addr, cfg_pfcp_local_addr_cmd,
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct cmd_node cfg_tunend_node = {
 | 
			
		||||
	TUNEND_NODE,
 | 
			
		||||
	"%s(config-tunend)# ",
 | 
			
		||||
static struct cmd_node cfg_gtp_node = {
 | 
			
		||||
	GTP_NODE,
 | 
			
		||||
	"%s(config-gtp)# ",
 | 
			
		||||
	1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define TUNEND_NODE_STR "Enter the 'tunend' node to configure Linux GTP kernel module usage\n"
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunend, cfg_tunend_cmd, "tunend", TUNEND_NODE_STR)
 | 
			
		||||
DEFUN(cfg_gtp, cfg_gtp_cmd,
 | 
			
		||||
      "gtp",
 | 
			
		||||
      "Enter the 'gtp' node to configure GTP kernel module usage\n")
 | 
			
		||||
{
 | 
			
		||||
	vty->node = TUNEND_NODE;
 | 
			
		||||
	vty->node = GTP_NODE;
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* legacy compat: "tunend" was originally named "gtp" */
 | 
			
		||||
DEFUN_CMD_ELEMENT(cfg_tunend, cfg_gtp_cmd, "gtp", TUNEND_NODE_STR, CMD_ATTR_HIDDEN, 0);
 | 
			
		||||
 | 
			
		||||
static int config_write_tunend(struct vty *vty)
 | 
			
		||||
static int config_write_gtp(struct vty *vty)
 | 
			
		||||
{
 | 
			
		||||
	struct tunend_vty_cfg_dev *d;
 | 
			
		||||
	vty_out(vty, "tunend%s", VTY_NEWLINE);
 | 
			
		||||
	struct gtp_vty_cfg_dev *d;
 | 
			
		||||
	vty_out(vty, "gtp%s", VTY_NEWLINE);
 | 
			
		||||
 | 
			
		||||
	if (g_upf->tunend.mockup)
 | 
			
		||||
		vty_out(vty, " mockup%s", VTY_NEWLINE);
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(d, &tunend_vty.devs, entry) {
 | 
			
		||||
	llist_for_each_entry(d, >p_vty.devs, entry) {
 | 
			
		||||
		if (d->create) {
 | 
			
		||||
			vty_out(vty, " dev create %s", d->dev_name);
 | 
			
		||||
			if (d->local_addr)
 | 
			
		||||
@@ -121,76 +112,51 @@ static int config_write_tunend(struct vty *vty)
 | 
			
		||||
 | 
			
		||||
#define DEV_STR "Configure the GTP device to use for encaps/decaps.\n"
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunend_mockup, cfg_tunend_mockup_cmd,
 | 
			
		||||
      "mockup",
 | 
			
		||||
      "don't actually send commands to the GTP kernel module, just return success\n")
 | 
			
		||||
DEFUN(cfg_gtp_dev_create, cfg_gtp_dev_create_cmd,
 | 
			
		||||
      "dev create DEVNAME [LISTEN_ADDR]",
 | 
			
		||||
      DEV_STR
 | 
			
		||||
      "create a new GTP device. Will listen on GTPv1 port " OSMO_STRINGIFY_VAL(PORT_GTP1_U)
 | 
			
		||||
      " and GTPv0 port " OSMO_STRINGIFY_VAL(PORT_GTP0_U) " on the specified interface, or on ANY if LISTEN_ADDR is"
 | 
			
		||||
      " omitted.\n"
 | 
			
		||||
      "device name, e.g. 'apn0'\n"
 | 
			
		||||
      "IPv4 or IPv6 address to listen on, omit for ANY\n")
 | 
			
		||||
{
 | 
			
		||||
	g_upf->tunend.mockup = true;
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunend_no_mockup, cfg_tunend_no_mockup_cmd,
 | 
			
		||||
      "no mockup",
 | 
			
		||||
      NO_STR
 | 
			
		||||
      "operate GTP kernel module normally\n")
 | 
			
		||||
{
 | 
			
		||||
	g_upf->tunend.mockup = false;
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct tunend_vty_cfg_dev *tunend_dev_add(int argc, const char **argv, bool create)
 | 
			
		||||
{
 | 
			
		||||
	struct tunend_vty_cfg_dev *d = talloc_zero(g_upf, struct tunend_vty_cfg_dev);
 | 
			
		||||
	d->create = create;
 | 
			
		||||
	struct gtp_vty_cfg_dev *d = talloc_zero(g_upf, struct gtp_vty_cfg_dev);
 | 
			
		||||
	d->create = true;
 | 
			
		||||
	d->dev_name = talloc_strdup(d, argv[0]);
 | 
			
		||||
	if (argc > 1)
 | 
			
		||||
		d->local_addr = talloc_strdup(d, argv[1]);
 | 
			
		||||
	llist_add(&d->entry, &tunend_vty.devs);
 | 
			
		||||
	return d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunend_dev_create, cfg_tunend_dev_create_cmd,
 | 
			
		||||
      "dev create DEVNAME [LISTEN_ADDR]",
 | 
			
		||||
      DEV_STR
 | 
			
		||||
      "Add GTP device, creating a new Linux kernel GTP device. Will listen on GTPv1 port "
 | 
			
		||||
      OSMO_STRINGIFY_VAL(PORT_GTP1_U)
 | 
			
		||||
      " and GTPv0 port " OSMO_STRINGIFY_VAL(PORT_GTP0_U) " on the specified LISTEN_ADDR\n"
 | 
			
		||||
      "device name, e.g. 'apn0'\n"
 | 
			
		||||
      "IPv4 or IPv6 address to listen on, omit for ANY. LISTEN_ADDR is used to pick a GTP device matching the local"
 | 
			
		||||
      " address for a PFCP Network Instance, which are configured in the 'netinst' node.\n")
 | 
			
		||||
{
 | 
			
		||||
	struct tunend_vty_cfg_dev *d = tunend_dev_add(argc, argv, true);
 | 
			
		||||
	vty_out(vty, "Added GTP device %s on %s (create new)%s", d->dev_name, d->local_addr ? : "0.0.0.0", VTY_NEWLINE);
 | 
			
		||||
	llist_add(&d->entry, >p_vty.devs);
 | 
			
		||||
	vty_out(vty, "Added GTP device %s (create new)%s", d->dev_name, VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunend_dev_use, cfg_tunend_dev_use_cmd,
 | 
			
		||||
      "dev use DEVNAME [LOCAL_ADDR]",
 | 
			
		||||
DEFUN(cfg_gtp_dev_use, cfg_gtp_dev_use_cmd,
 | 
			
		||||
      "dev use DEVNAME",
 | 
			
		||||
      DEV_STR
 | 
			
		||||
      "Add GTP device, using an existing Linux kernel GTP device, e.g. created by 'gtp-link'\n"
 | 
			
		||||
      "device name, e.g. 'apn0'\n"
 | 
			
		||||
      "The local GTP address this device listens on. It is assumed to be ANY when omitted."
 | 
			
		||||
      " LOCAL_ADDR is used to pick a GTP device matching the local address for a PFCP Network Instance,"
 | 
			
		||||
      " which are configured in the 'netinst' node.\n")
 | 
			
		||||
      "use an existing GTP device, e.g. created by 'gtp-link'\n"
 | 
			
		||||
      "device name, e.g. 'apn0'\n")
 | 
			
		||||
{
 | 
			
		||||
	struct tunend_vty_cfg_dev *d = tunend_dev_add(argc, argv, false);
 | 
			
		||||
	vty_out(vty, "Added GTP device %s on %s (use existing)%s", d->dev_name, d->local_addr ? : "0.0.0.0",
 | 
			
		||||
		VTY_NEWLINE);
 | 
			
		||||
	struct gtp_vty_cfg_dev *d = talloc_zero(g_upf, struct gtp_vty_cfg_dev);
 | 
			
		||||
	d->create = false;
 | 
			
		||||
	d->dev_name = talloc_strdup(d, argv[0]);
 | 
			
		||||
	llist_add(&d->entry, >p_vty.devs);
 | 
			
		||||
	vty_out(vty, "Added GTP device %s (use existing)%s", d->dev_name, VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunend_dev_del, cfg_tunend_dev_del_cmd,
 | 
			
		||||
DEFUN(cfg_gtp_dev_del, cfg_gtp_dev_del_cmd,
 | 
			
		||||
      "dev delete DEVNAME",
 | 
			
		||||
      DEV_STR
 | 
			
		||||
      "Remove a GTP device from the configuration, and delete the Linux kernel GTP device if it was created here.\n"
 | 
			
		||||
      "Remove a GTP device from the configuration, and delete the device if it was created here.\n"
 | 
			
		||||
      "device name, e.g. 'apn0'\n")
 | 
			
		||||
{
 | 
			
		||||
	const char *dev_name = argv[0];
 | 
			
		||||
	struct tunend_vty_cfg_dev *d;
 | 
			
		||||
	struct gtp_vty_cfg_dev *d;
 | 
			
		||||
	struct upf_gtp_dev *dev;
 | 
			
		||||
 | 
			
		||||
	/* remove from VTY cfg */
 | 
			
		||||
	llist_for_each_entry(d, &tunend_vty.devs, entry) {
 | 
			
		||||
	llist_for_each_entry(d, >p_vty.devs, entry) {
 | 
			
		||||
		if (strcmp(d->dev_name, dev_name))
 | 
			
		||||
			continue;
 | 
			
		||||
		llist_del(&d->entry);
 | 
			
		||||
@@ -204,197 +170,37 @@ DEFUN(cfg_tunend_dev_del, cfg_tunend_dev_del_cmd,
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct cmd_node cfg_tunmap_node = {
 | 
			
		||||
	TUNMAP_NODE,
 | 
			
		||||
	"%s(config-tunmap)# ",
 | 
			
		||||
static struct cmd_node cfg_nft_node = {
 | 
			
		||||
	NFT_NODE,
 | 
			
		||||
	"%s(config-nft)# ",
 | 
			
		||||
	1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define TUNMAP_NODE_STR "Enter the 'tunmap' node to configure nftables usage\n"
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunmap, cfg_tunmap_cmd, "tunmap", TUNMAP_NODE_STR)
 | 
			
		||||
DEFUN(cfg_nft, cfg_nft_cmd,
 | 
			
		||||
      "nft",
 | 
			
		||||
      "Enter the 'nft' node to configure nftables usage\n")
 | 
			
		||||
{
 | 
			
		||||
	vty->node = TUNMAP_NODE;
 | 
			
		||||
	vty->node = NFT_NODE;
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* legacy compat: "tunmap" was originally named "nft" */
 | 
			
		||||
DEFUN_CMD_ELEMENT(cfg_tunmap, cfg_nft_cmd, "nft", TUNMAP_NODE_STR, CMD_ATTR_HIDDEN, 0);
 | 
			
		||||
 | 
			
		||||
static int config_write_tunmap(struct vty *vty)
 | 
			
		||||
static int config_write_nft(struct vty *vty)
 | 
			
		||||
{
 | 
			
		||||
	vty_out(vty, "tunmap%s", VTY_NEWLINE);
 | 
			
		||||
 | 
			
		||||
	if (g_upf->tunmap.mockup)
 | 
			
		||||
		vty_out(vty, " mockup%s", VTY_NEWLINE);
 | 
			
		||||
 | 
			
		||||
	if (g_upf->tunmap.table_name && strcmp(g_upf->tunmap.table_name, "osmo-upf"))
 | 
			
		||||
		vty_out(vty, " table-name %s%s", g_upf->tunmap.table_name, VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "nft%s", VTY_NEWLINE);
 | 
			
		||||
 | 
			
		||||
	if (g_upf->nft.table_name && strcmp(g_upf->nft.table_name, "osmo-upf"))
 | 
			
		||||
		vty_out(vty, " table-name %s%s", g_upf->nft.table_name, VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunmap_mockup, cfg_tunmap_mockup_cmd,
 | 
			
		||||
      "mockup",
 | 
			
		||||
      "don't actually send rulesets to nftables, just return success\n")
 | 
			
		||||
{
 | 
			
		||||
	g_upf->tunmap.mockup = true;
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunmap_no_mockup, cfg_tunmap_no_mockup_cmd,
 | 
			
		||||
      "no mockup",
 | 
			
		||||
      NO_STR
 | 
			
		||||
      "operate nftables rulesets normally\n")
 | 
			
		||||
{
 | 
			
		||||
	g_upf->tunmap.mockup = false;
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_tunmap_table_name, cfg_tunmap_table_name_cmd,
 | 
			
		||||
DEFUN(cfg_nft_table_name, cfg_nft_table_name_cmd,
 | 
			
		||||
      "table-name TABLE_NAME",
 | 
			
		||||
      "Set the nft inet table name to create and place GTP tunnel forwarding chains in"
 | 
			
		||||
      " (as in 'nft add table inet foo'). If multiple instances of osmo-upf are running on the same system, each"
 | 
			
		||||
      " osmo-upf must have its own table name. Otherwise the names of created forwarding chains will collide."
 | 
			
		||||
      " The default table name is \"osmo-upf\".\n"
 | 
			
		||||
      " osmo-upf must have its own table name. Otherwise the names of created forwarding chains will collide.\n"
 | 
			
		||||
      "nft inet table name\n")
 | 
			
		||||
{
 | 
			
		||||
	osmo_talloc_replace_string(g_upf, &g_upf->tunmap.table_name, argv[0]);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define NFT_RULE_STR "nftables rule specifics\n"
 | 
			
		||||
#define TUNMAP_STR "GTP tunmap use case (a.k.a. forwarding between two GTP tunnels)\n"
 | 
			
		||||
#define TUNMAP_APPEND_STR "'tunmap append' feature is no longer available.\n"
 | 
			
		||||
 | 
			
		||||
DEFUN_DEPRECATED(cfg_tunmap_nft_rule_append, cfg_tunmap_nft_rule_append_cmd,
 | 
			
		||||
      "nft-rule tunmap append .NFT_RULE",
 | 
			
		||||
      NFT_RULE_STR TUNMAP_STR TUNMAP_APPEND_STR TUNMAP_APPEND_STR)
 | 
			
		||||
{
 | 
			
		||||
	vty_out(vty, "%% deprecated config option: 'nft-rule tunmap append'%s", VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN_DEPRECATED(cfg_tunmap_no_nft_rule_append, cfg_tunmap_no_nft_rule_append_cmd,
 | 
			
		||||
      "no nft-rule tunmap append",
 | 
			
		||||
      NO_STR NFT_RULE_STR TUNMAP_STR TUNMAP_APPEND_STR TUNMAP_APPEND_STR)
 | 
			
		||||
{
 | 
			
		||||
	vty_out(vty, "%% deprecated config option: 'no nft-rule tunmap append'%s", VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN_DEPRECATED(show_nft_rule_append, show_nft_rule_append_cmd,
 | 
			
		||||
      "show nft-rule tunmap append",
 | 
			
		||||
      SHOW_STR NFT_RULE_STR TUNMAP_STR TUNMAP_APPEND_STR)
 | 
			
		||||
{
 | 
			
		||||
	vty_out(vty, "%% deprecated config option: 'show nft-rule tunmap append'%s", VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(show_nft_rule_tunmap_example, show_nft_rule_tunmap_example_cmd,
 | 
			
		||||
      "show nft-rule tunmap example",
 | 
			
		||||
      SHOW_STR NFT_RULE_STR TUNMAP_STR
 | 
			
		||||
      "Print a complete nftables ruleset for a tunmap filled with example IP addresses and TEIDs\n")
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_sockaddr_str str = {};
 | 
			
		||||
	struct upf_tunmap tunmap = {
 | 
			
		||||
		.access = {
 | 
			
		||||
			.tun = {
 | 
			
		||||
				.local.teid = 0x201,
 | 
			
		||||
				.remote.teid = 0x102,
 | 
			
		||||
			},
 | 
			
		||||
			.chain_id = 123,
 | 
			
		||||
		},
 | 
			
		||||
		.core = {
 | 
			
		||||
			.tun = {
 | 
			
		||||
				.local.teid = 0x203,
 | 
			
		||||
				.remote.teid = 0x302,
 | 
			
		||||
			},
 | 
			
		||||
			.chain_id = 321,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	osmo_sockaddr_str_from_str2(&str, "1.1.1.1");
 | 
			
		||||
	osmo_sockaddr_str_to_sockaddr(&str, &tunmap.access.tun.remote.addr.u.sas);
 | 
			
		||||
 | 
			
		||||
	osmo_sockaddr_str_from_str2(&str, "2.2.2.1");
 | 
			
		||||
	osmo_sockaddr_str_to_sockaddr(&str, &tunmap.access.tun.local.addr.u.sas);
 | 
			
		||||
 | 
			
		||||
	osmo_sockaddr_str_from_str2(&str, "2.2.2.3");
 | 
			
		||||
	osmo_sockaddr_str_to_sockaddr(&str, &tunmap.core.tun.local.addr.u.sas);
 | 
			
		||||
 | 
			
		||||
	osmo_sockaddr_str_from_str2(&str, "3.3.3.3");
 | 
			
		||||
	osmo_sockaddr_str_to_sockaddr(&str, &tunmap.core.tun.remote.addr.u.sas);
 | 
			
		||||
 | 
			
		||||
	vty_out(vty, "%% init verdict map:%s", VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "%s%s", upf_nft_tunmap_get_table_init_str(OTC_SELECT), VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "%s%s", upf_nft_tunmap_get_vmap_init_str(OTC_SELECT), VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "%% add tunmap:%s", VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "%% %s%s", upf_nft_tunmap_to_str_c(OTC_SELECT, &tunmap), VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "%s%s", upf_nft_tunmap_get_ruleset_str(OTC_SELECT, &tunmap), VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "%% delete tunmap:%s", VTY_NEWLINE);
 | 
			
		||||
	vty_out(vty, "%s%s", upf_nft_tunmap_get_ruleset_del_str(OTC_SELECT, &tunmap), VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct cmd_node cfg_netinst_node = {
 | 
			
		||||
	NETINST_NODE,
 | 
			
		||||
	"%s(config-netinst)# ",
 | 
			
		||||
	1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_netinst, cfg_netinst_cmd,
 | 
			
		||||
      "netinst",
 | 
			
		||||
      "Enter the Network Instance configuration node\n")
 | 
			
		||||
{
 | 
			
		||||
	vty->node = NETINST_NODE;
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int config_write_netinst(struct vty *vty)
 | 
			
		||||
{
 | 
			
		||||
	vty_out(vty, "netinst%s", VTY_NEWLINE);
 | 
			
		||||
	netinst_vty_write(vty, &g_upf->netinst, " ", NULL);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_netinst_add, cfg_netinst_add_cmd,
 | 
			
		||||
      "add NAME ADDR",
 | 
			
		||||
      "add Network Instance: associate a PFCP Network Instance name with a local IP address\n"
 | 
			
		||||
      "Network Instance name as received in PFCP Network Instance IE\n"
 | 
			
		||||
      "IP address of a local interface\n")
 | 
			
		||||
{
 | 
			
		||||
	const char *errmsg;
 | 
			
		||||
	if (!netinst_add(g_upf, &g_upf->netinst, argv[0], argv[1], &errmsg)) {
 | 
			
		||||
		vty_out(vty, "%% Error: netinst: cannot add %s %s: %s%s", argv[0], argv[1],
 | 
			
		||||
			errmsg ? : "(unknown error)", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(show_netinst, show_netinst_cmd,
 | 
			
		||||
      "show netinst [NAME]",
 | 
			
		||||
      SHOW_STR "List configured Network Instance entries\n"
 | 
			
		||||
      "Show the Network Instance with this name (show all when omitted)\n")
 | 
			
		||||
{
 | 
			
		||||
	const char *name_or_null = argc > 0 ? argv[0] : NULL;
 | 
			
		||||
 | 
			
		||||
	if (!netinst_vty_write(vty, &g_upf->netinst, " ", name_or_null)) {
 | 
			
		||||
		if (name_or_null)
 | 
			
		||||
			vty_out(vty, "%% No such Network Instance entry%s", VTY_NEWLINE);
 | 
			
		||||
		else
 | 
			
		||||
			vty_out(vty, "%% No Network Instance entries configured%s", VTY_NEWLINE);
 | 
			
		||||
	}
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(cfg_netinst_clear, cfg_netinst_clear_cmd,
 | 
			
		||||
      "clear",
 | 
			
		||||
      "Remove all Network Instance entries\n")
 | 
			
		||||
{
 | 
			
		||||
	int count = netinst_clear(&g_upf->netinst);
 | 
			
		||||
	vty_out(vty, "netinst entries removed: %d%s", count, VTY_NEWLINE);
 | 
			
		||||
	osmo_talloc_replace_string(g_upf, &g_upf->nft.table_name, argv[0]);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -435,11 +241,16 @@ DEFUN(show_pdr, show_pdr_cmd,
 | 
			
		||||
DEFUN(show_gtp, show_gtp_cmd,
 | 
			
		||||
      "show gtp",
 | 
			
		||||
      SHOW_STR
 | 
			
		||||
      "Active GTP tunnels, both tunend and tunmap\n")
 | 
			
		||||
      "Active GTP tunnels and forwardings\n")
 | 
			
		||||
{
 | 
			
		||||
	struct up_peer *peer;
 | 
			
		||||
	int count = 0;
 | 
			
		||||
 | 
			
		||||
	if (!upf_gtp_dev_first()) {
 | 
			
		||||
		vty_out(vty, "No GTP device open%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_SUCCESS;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
 | 
			
		||||
		struct up_session *session;
 | 
			
		||||
		int bkt;
 | 
			
		||||
@@ -482,110 +293,11 @@ DEFUN(show_session, show_session_cmd,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	vty_out(vty, "(%d fully-active + %d active with some PDR/FAR ignored + %d inactive)%s",
 | 
			
		||||
	vty_out(vty, "(%d fully-active + %d partially active + %d inactive)%s",
 | 
			
		||||
		fully_active_count, active_count, inactive_count, VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* variant:
 | 
			
		||||
 *  0 "gtp1u-echo send to (A.B.C.D|X:X::X:X)"
 | 
			
		||||
 *  1 "gtp1u-echo send to (A.B.C.D|X:X::X:X) local-ip (A.B.C.D|X:X::X:X)"
 | 
			
		||||
 *  2 "gtp1u-echo send to (A.B.C.D|X:X::X:X) local-dev DEV_NAME"
 | 
			
		||||
 */
 | 
			
		||||
static int _gtp_echo_tx(struct vty *vty, int variant, int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_sockaddr_str addr;
 | 
			
		||||
	struct osmo_sockaddr osa_remote;
 | 
			
		||||
	struct osmo_sockaddr osa_local;
 | 
			
		||||
	struct upf_gtp_dev *gtp_dev = NULL;
 | 
			
		||||
	const char *remote_str = argv[0];
 | 
			
		||||
	const char *local_str = NULL;
 | 
			
		||||
	if (argc > 1)
 | 
			
		||||
		local_str = argv[1];
 | 
			
		||||
 | 
			
		||||
	/* GTP can be received on port 2152 only, i.e. the remote port must be 2152. (The sending port is allowed to
 | 
			
		||||
	 * differ). */
 | 
			
		||||
	if (osmo_sockaddr_str_from_str(&addr, remote_str, 2152)
 | 
			
		||||
	    || osmo_sockaddr_str_to_osa(&addr, &osa_remote)) {
 | 
			
		||||
		vty_out(vty, "%% Error: cannot send Echo: invalid IP address: %s%s",
 | 
			
		||||
			osmo_quote_str(remote_str, -1), VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (variant) {
 | 
			
		||||
	case 0:
 | 
			
		||||
		gtp_dev = llist_first_entry_or_null(&g_upf->tunend.devs, struct upf_gtp_dev, entry);
 | 
			
		||||
		if (!gtp_dev) {
 | 
			
		||||
			vty_out(vty, "%% Error: cannot send Echo: there is no GTP device%s",
 | 
			
		||||
				VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	case 1:
 | 
			
		||||
		if (osmo_sockaddr_str_from_str(&addr, local_str, 2152)
 | 
			
		||||
		    || osmo_sockaddr_str_to_osa(&addr, &osa_local)) {
 | 
			
		||||
			vty_out(vty, "%% Error: cannot send Echo: invalid IP address: %s%s",
 | 
			
		||||
				osmo_quote_str(local_str, -1), VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
		gtp_dev = upf_gtp_dev_find_by_local_addr(&osa_local);
 | 
			
		||||
		if (!gtp_dev) {
 | 
			
		||||
			vty_out(vty, "%% Error: cannot send Echo: this does not seem to be a locally bound GTP address: %s%s",
 | 
			
		||||
				osmo_sockaddr_to_str_c(OTC_SELECT, &osa_local), VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	case 2:
 | 
			
		||||
		gtp_dev = upf_gtp_dev_find_by_name(local_str);
 | 
			
		||||
		if (!gtp_dev) {
 | 
			
		||||
			vty_out(vty, "%% Error: cannot send Echo: there is no GTP device by the name of '%s'%s",
 | 
			
		||||
				local_str, VTY_NEWLINE);
 | 
			
		||||
			return CMD_WARNING;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	OSMO_ASSERT(gtp_dev);
 | 
			
		||||
 | 
			
		||||
	if (upf_gtpu_echo_req_tx(gtp_dev, &osa_remote, g_upf->gtp.next_echo_seq_nr++)) {
 | 
			
		||||
		vty_out(vty, "%% Error: Failed to transmit Echo Request (see DGTP logging)%s", VTY_NEWLINE);
 | 
			
		||||
		return CMD_WARNING;
 | 
			
		||||
	}
 | 
			
		||||
	vty_out(vty, "%s -> %s tx Echo Request; for responses, see DGTP logging level INFO%s",
 | 
			
		||||
		gtp_dev->name, osmo_sockaddr_to_str_c(OTC_SELECT, &osa_remote), VTY_NEWLINE);
 | 
			
		||||
	return CMD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define IP46_STR "IPv4 address\nIPv6 address\n"
 | 
			
		||||
#define GTP_ECHO_TX_STR \
 | 
			
		||||
      "GTP1-U Echo probing\n" \
 | 
			
		||||
      "Send a GTP1-U Echo Request to a remote peer\n" \
 | 
			
		||||
      "Send to remote peer's GTP address\n" IP46_STR
 | 
			
		||||
 | 
			
		||||
DEFUN(gtp_echo_tx, gtp_echo_tx_cmd,
 | 
			
		||||
      "gtp1u-echo send to " VTY_IPV46_CMD,
 | 
			
		||||
      GTP_ECHO_TX_STR)
 | 
			
		||||
{
 | 
			
		||||
	return _gtp_echo_tx(vty, 0, argc, argv);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(gtp_echo_tx_local_ip, gtp_echo_tx_local_ip_cmd,
 | 
			
		||||
      "gtp1u-echo send to " VTY_IPV46_CMD " local-ip " VTY_IPV46_CMD,
 | 
			
		||||
      GTP_ECHO_TX_STR
 | 
			
		||||
      "Send from local GTP device, chosen by IP address\n"
 | 
			
		||||
      IP46_STR)
 | 
			
		||||
{
 | 
			
		||||
	return _gtp_echo_tx(vty, 1, argc, argv);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFUN(gtp_echo_tx_local_dev, gtp_echo_tx_local_dev_cmd,
 | 
			
		||||
      "gtp1u-echo send to " VTY_IPV46_CMD " local-dev DEV_NAME",
 | 
			
		||||
      GTP_ECHO_TX_STR
 | 
			
		||||
      "Send from local GTP device, chosen by name as configured in 'dev create' or 'dev use'.\n"
 | 
			
		||||
      "A GTP device name as it appears in the cfg\n")
 | 
			
		||||
{
 | 
			
		||||
	return _gtp_echo_tx(vty, 2, argc, argv);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upf_vty_init()
 | 
			
		||||
{
 | 
			
		||||
	OSMO_ASSERT(g_upf != NULL);
 | 
			
		||||
@@ -593,43 +305,22 @@ void upf_vty_init()
 | 
			
		||||
	install_element_ve(&show_pdr_cmd);
 | 
			
		||||
	install_element_ve(&show_gtp_cmd);
 | 
			
		||||
	install_element_ve(&show_session_cmd);
 | 
			
		||||
	install_element_ve(&show_netinst_cmd);
 | 
			
		||||
	install_element_ve(&show_nft_rule_append_cmd);
 | 
			
		||||
	install_element_ve(>p_echo_tx_cmd);
 | 
			
		||||
	install_element_ve(>p_echo_tx_local_ip_cmd);
 | 
			
		||||
	install_element_ve(>p_echo_tx_local_dev_cmd);
 | 
			
		||||
 | 
			
		||||
	install_node(&cfg_pfcp_node, config_write_pfcp);
 | 
			
		||||
	install_element(CONFIG_NODE, &cfg_pfcp_cmd);
 | 
			
		||||
 | 
			
		||||
	install_element(PFCP_NODE, &cfg_pfcp_local_addr_cmd);
 | 
			
		||||
 | 
			
		||||
	install_node(&cfg_tunend_node, config_write_tunend);
 | 
			
		||||
	install_element(CONFIG_NODE, &cfg_tunend_cmd);
 | 
			
		||||
	install_node(&cfg_gtp_node, config_write_gtp);
 | 
			
		||||
	install_element(CONFIG_NODE, &cfg_gtp_cmd);
 | 
			
		||||
 | 
			
		||||
	install_element(TUNEND_NODE, &cfg_tunend_mockup_cmd);
 | 
			
		||||
	install_element(TUNEND_NODE, &cfg_tunend_no_mockup_cmd);
 | 
			
		||||
	install_element(TUNEND_NODE, &cfg_tunend_dev_create_cmd);
 | 
			
		||||
	install_element(TUNEND_NODE, &cfg_tunend_dev_use_cmd);
 | 
			
		||||
	install_element(TUNEND_NODE, &cfg_tunend_dev_del_cmd);
 | 
			
		||||
	install_element(GTP_NODE, &cfg_gtp_dev_create_cmd);
 | 
			
		||||
	install_element(GTP_NODE, &cfg_gtp_dev_use_cmd);
 | 
			
		||||
	install_element(GTP_NODE, &cfg_gtp_dev_del_cmd);
 | 
			
		||||
 | 
			
		||||
	install_node(&cfg_tunmap_node, config_write_tunmap);
 | 
			
		||||
	install_element(CONFIG_NODE, &cfg_tunmap_cmd);
 | 
			
		||||
	install_node(&cfg_nft_node, config_write_nft);
 | 
			
		||||
	install_element(CONFIG_NODE, &cfg_nft_cmd);
 | 
			
		||||
 | 
			
		||||
	install_element(TUNMAP_NODE, &cfg_tunmap_mockup_cmd);
 | 
			
		||||
	install_element(TUNMAP_NODE, &cfg_tunmap_no_mockup_cmd);
 | 
			
		||||
	install_element(TUNMAP_NODE, &cfg_tunmap_table_name_cmd);
 | 
			
		||||
	install_element(TUNMAP_NODE, &cfg_tunmap_nft_rule_append_cmd);
 | 
			
		||||
	install_element(TUNMAP_NODE, &cfg_tunmap_no_nft_rule_append_cmd);
 | 
			
		||||
	install_element(TUNMAP_NODE, &show_nft_rule_append_cmd);
 | 
			
		||||
	install_element(TUNMAP_NODE, &show_nft_rule_tunmap_example_cmd);
 | 
			
		||||
 | 
			
		||||
	install_node(&cfg_netinst_node, config_write_netinst);
 | 
			
		||||
	install_element(CONFIG_NODE, &cfg_netinst_cmd);
 | 
			
		||||
 | 
			
		||||
	install_element(NETINST_NODE, &cfg_netinst_clear_cmd);
 | 
			
		||||
	install_element(NETINST_NODE, &cfg_netinst_add_cmd);
 | 
			
		||||
	install_element(NETINST_NODE, &show_netinst_cmd);
 | 
			
		||||
	install_element(NFT_NODE, &cfg_nft_table_name_cmd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	unique_ids \
 | 
			
		||||
	libosmo-gtlv \
 | 
			
		||||
	libosmo-pfcp \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
 | 
			
		||||
@@ -21,7 +22,7 @@ $(srcdir)/package.m4: $(top_srcdir)/configure.ac
 | 
			
		||||
             } >'$(srcdir)/package.m4'
 | 
			
		||||
 | 
			
		||||
EXTRA_DIST = \
 | 
			
		||||
	$(srcdir)/*.vty \
 | 
			
		||||
	upf.vty \
 | 
			
		||||
	testsuite.at \
 | 
			
		||||
	$(srcdir)/package.m4 \
 | 
			
		||||
	$(TESTSUITE) \
 | 
			
		||||
@@ -52,7 +53,7 @@ VTY_TEST ?= *.vty
 | 
			
		||||
vty-test:
 | 
			
		||||
	osmo_verify_transcript_vty.py -v \
 | 
			
		||||
		-n OsmoUPF -p 4275 \
 | 
			
		||||
		-r "$(top_builddir)/src/osmo-upf/osmo-upf -c $(top_srcdir)/doc/examples/osmo-upf/osmo-upf-mockup.cfg" \
 | 
			
		||||
		-r "$(top_builddir)/src/osmo-upf/osmo-upf -c $(top_srcdir)/doc/examples/osmo-upf/osmo-upf.cfg" \
 | 
			
		||||
		$(U) $(srcdir)/$(VTY_TEST)
 | 
			
		||||
 | 
			
		||||
check-local: atconfig $(TESTSUITE)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
OsmoUPF> list
 | 
			
		||||
...
 | 
			
		||||
  gtp1u-echo send to (A.B.C.D|X:X::X:X)
 | 
			
		||||
  gtp1u-echo send to (A.B.C.D|X:X::X:X) local-ip (A.B.C.D|X:X::X:X)
 | 
			
		||||
  gtp1u-echo send to (A.B.C.D|X:X::X:X) local-dev DEV_NAME
 | 
			
		||||
...
 | 
			
		||||
OsmoUPF> enable
 | 
			
		||||
OsmoUPF# list
 | 
			
		||||
...
 | 
			
		||||
  gtp1u-echo send to (A.B.C.D|X:X::X:X)
 | 
			
		||||
  gtp1u-echo send to (A.B.C.D|X:X::X:X) local-ip (A.B.C.D|X:X::X:X)
 | 
			
		||||
  gtp1u-echo send to (A.B.C.D|X:X::X:X) local-dev DEV_NAME
 | 
			
		||||
...
 | 
			
		||||
OsmoUPF# configure terminal
 | 
			
		||||
OsmoUPF(config)# list
 | 
			
		||||
... !gtp1u-echo
 | 
			
		||||
OsmoUPF(config)# end
 | 
			
		||||
 | 
			
		||||
OsmoUPF# gtp1u-echo?
 | 
			
		||||
  gtp1u-echo  GTP1-U Echo probing
 | 
			
		||||
OsmoUPF# gtp1u-echo ?
 | 
			
		||||
  send  Send a GTP1-U Echo Request to a remote peer
 | 
			
		||||
OsmoUPF# gtp1u-echo send ?
 | 
			
		||||
  to  Send to remote peer's GTP address
 | 
			
		||||
OsmoUPF# gtp1u-echo send to ?
 | 
			
		||||
  A.B.C.D   IPv4 address
 | 
			
		||||
  X:X::X:X  IPv6 address
 | 
			
		||||
OsmoUPF# gtp1u-echo send to 1.2.3.4 ?
 | 
			
		||||
  local-ip   Send from local GTP device, chosen by IP address
 | 
			
		||||
  local-dev  Send from local GTP device, chosen by name as configured in 'dev create' or 'dev use'.
 | 
			
		||||
  <cr>       
 | 
			
		||||
OsmoUPF# gtp1u-echo send to 1.2.3.4 local-ip ?
 | 
			
		||||
  A.B.C.D   IPv4 address
 | 
			
		||||
  X:X::X:X  IPv6 address
 | 
			
		||||
OsmoUPF# gtp1u-echo send to 1.2.3.4 local-dev ?
 | 
			
		||||
  DEV_NAME  A GTP device name as it appears in the cfg
 | 
			
		||||
OsmoUPF# gtp1u-echo send to 1.2.3.4
 | 
			
		||||
% Error: cannot send Echo: there is no GTP device
 | 
			
		||||
OsmoUPF# gtp1u-echo send to 1.2.3.4 local-ip 1.2.3.4
 | 
			
		||||
% Error: cannot send Echo: this does not seem to be a locally bound GTP address: 1.2.3.4:2152
 | 
			
		||||
OsmoUPF# gtp1u-echo send to 1.2.3.4 local-dev apn0
 | 
			
		||||
% Error: cannot send Echo: there is no GTP device by the name of 'apn0'
 | 
			
		||||
							
								
								
									
										49
									
								
								tests/libosmo-gtlv/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								tests/libosmo-gtlv/Makefile.am
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	test_gtlv_gen \
 | 
			
		||||
	test_tliv \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_CPPFLAGS = \
 | 
			
		||||
	$(all_includes) \
 | 
			
		||||
	-I$(top_srcdir)/include \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_CFLAGS = \
 | 
			
		||||
	-Wall \
 | 
			
		||||
	$(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
noinst_PROGRAMS = \
 | 
			
		||||
	gtlv_test \
 | 
			
		||||
	gtlv_dec_enc_test \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
EXTRA_DIST = \
 | 
			
		||||
	gtlv_test.ok \
 | 
			
		||||
	gtlv_dec_enc_test.ok \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
gtlv_test_SOURCES = \
 | 
			
		||||
	gtlv_test.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
gtlv_test_LDADD = \
 | 
			
		||||
	$(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
gtlv_dec_enc_test_SOURCES = \
 | 
			
		||||
	gtlv_dec_enc_test.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
gtlv_dec_enc_test_LDADD = \
 | 
			
		||||
	$(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
.PHONY: update_exp
 | 
			
		||||
update_exp:
 | 
			
		||||
	$(builddir)/gtlv_test >$(srcdir)/gtlv_test.ok
 | 
			
		||||
	$(builddir)/gtlv_dec_enc_test >$(srcdir)/gtlv_dec_enc_test.ok
 | 
			
		||||
	$(MAKE) -C test_gtlv_gen update_exp
 | 
			
		||||
	$(MAKE) -C test_tliv update_exp
 | 
			
		||||
							
								
								
									
										422
									
								
								tests/libosmo-gtlv/gtlv_dec_enc_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										422
									
								
								tests/libosmo-gtlv/gtlv_dec_enc_test.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,422 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gtlv/gtlv_dec_enc.h>
 | 
			
		||||
 | 
			
		||||
void *ctx;
 | 
			
		||||
 | 
			
		||||
enum tags {
 | 
			
		||||
	TAG_FOO = 1,
 | 
			
		||||
	TAG_BAR,
 | 
			
		||||
	TAG_BAZ,
 | 
			
		||||
	TAG_REPEAT_INT,
 | 
			
		||||
	TAG_REPEAT_STRUCT,
 | 
			
		||||
	TAG_NEST,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string tag_names[] = {
 | 
			
		||||
	{ TAG_FOO, "FOO" },
 | 
			
		||||
	{ TAG_BAR, "BAR" },
 | 
			
		||||
	{ TAG_BAZ, "BAZ" },
 | 
			
		||||
	{ TAG_REPEAT_INT, "REPEAT_INT" },
 | 
			
		||||
	{ TAG_REPEAT_STRUCT, "REPEAT_STRUCT" },
 | 
			
		||||
	{ TAG_NEST, "NEST" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct bar {
 | 
			
		||||
	char str[23];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct baz {
 | 
			
		||||
	int v_int;
 | 
			
		||||
	bool v_bool;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum repeat_enum {
 | 
			
		||||
	R_A,
 | 
			
		||||
	R_B,
 | 
			
		||||
	R_C,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct value_string repeat_enum_names[] = {
 | 
			
		||||
	OSMO_VALUE_STRING(R_A),
 | 
			
		||||
	OSMO_VALUE_STRING(R_B),
 | 
			
		||||
	OSMO_VALUE_STRING(R_C),
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct repeat {
 | 
			
		||||
	int v_int;
 | 
			
		||||
	bool v_bool;
 | 
			
		||||
	enum repeat_enum v_enum;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct nested_inner_msg {
 | 
			
		||||
	int foo;
 | 
			
		||||
	struct bar bar;
 | 
			
		||||
	struct baz baz;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct decoded_msg {
 | 
			
		||||
	int foo;
 | 
			
		||||
	struct bar bar;
 | 
			
		||||
 | 
			
		||||
	bool baz_present;
 | 
			
		||||
	struct baz baz;
 | 
			
		||||
 | 
			
		||||
	unsigned int repeat_int_count;
 | 
			
		||||
	int repeat_int[32];
 | 
			
		||||
 | 
			
		||||
	unsigned int repeat_struct_count;
 | 
			
		||||
	struct repeat repeat_struct[32];
 | 
			
		||||
 | 
			
		||||
	bool nest_present;
 | 
			
		||||
	struct nested_inner_msg nest;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int dec_u16(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	int *foo = decode_to;
 | 
			
		||||
	if (gtlv->len != 2)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	*foo = osmo_load16be(gtlv->val);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_u16(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const int *foo = encode_from;
 | 
			
		||||
	if (*foo > INT16_MAX)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	msgb_put_u16(gtlv->dst, *foo);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_to_str_u16(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const int *foo = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "%d", *foo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int dec_bar(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	struct bar *bar = decode_to;
 | 
			
		||||
	if (gtlv->len > sizeof(bar->str) - 1)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	osmo_strlcpy(bar->str, (const char *)gtlv->val, OSMO_MIN(gtlv->len + 1, sizeof(bar->str)));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_bar(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct bar *bar = encode_from;
 | 
			
		||||
	int len = strnlen(bar->str, sizeof(bar->str));
 | 
			
		||||
	memcpy(msgb_put(gtlv->dst, len), bar, len);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_to_str_bar(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct bar *bar = encode_from;
 | 
			
		||||
	return osmo_quote_str_buf3(buf, buflen, bar->str, -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int dec_baz(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	struct baz *baz = decode_to;
 | 
			
		||||
	uint16_t l;
 | 
			
		||||
	if (gtlv->len != 2)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	l = osmo_load16be(gtlv->val);
 | 
			
		||||
	baz->v_int = l & 0x7fff;
 | 
			
		||||
	baz->v_bool = (l & 0x8000) ? true : false;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_baz(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct baz *baz = encode_from;
 | 
			
		||||
	if (baz->v_int > 0x7fff)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	msgb_put_u16(gtlv->dst, (baz->v_bool ? 0x8000 : 0) + (baz->v_int & 0x7fff));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_to_str_baz(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct baz *baz = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "{%d,%s}", baz->v_int, baz->v_bool ? "true" : "false");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int dec_repeat_struct(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	struct repeat *repeat_struct = decode_to;
 | 
			
		||||
	if (gtlv->len != 3)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	repeat_struct->v_int = osmo_load16be(gtlv->val);
 | 
			
		||||
	repeat_struct->v_bool = gtlv->val[2] & 0x80;
 | 
			
		||||
	repeat_struct->v_enum = gtlv->val[2] & 0x7f;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_repeat_struct(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct repeat *repeat_struct = encode_from;
 | 
			
		||||
	msgb_put_u16(gtlv->dst, repeat_struct->v_int);
 | 
			
		||||
	msgb_put_u8(gtlv->dst, (repeat_struct->v_bool ? 0x80 : 0) + (repeat_struct->v_enum & 0x7f));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int enc_to_str_repeat_struct(char *buf, size_t buflen, const void *encode_from)
 | 
			
		||||
{
 | 
			
		||||
	const struct repeat *repeat_struct = encode_from;
 | 
			
		||||
	return snprintf(buf, buflen, "{%d,%s,%s}", repeat_struct->v_int, repeat_struct->v_bool ? "true" : "false",
 | 
			
		||||
			get_value_string(repeat_enum_names, repeat_struct->v_enum));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct osmo_gtlv_coding nested_inner_msg_ies[] = {
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_FOO },
 | 
			
		||||
		.dec_func = dec_u16,
 | 
			
		||||
		.enc_func = enc_u16,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_u16,
 | 
			
		||||
		.memb_ofs = offsetof(struct nested_inner_msg, foo),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_BAR },
 | 
			
		||||
		.dec_func = dec_bar,
 | 
			
		||||
		.enc_func = enc_bar,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_bar,
 | 
			
		||||
		.memb_ofs = offsetof(struct nested_inner_msg, bar),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_BAZ },
 | 
			
		||||
		.dec_func = dec_baz,
 | 
			
		||||
		.enc_func = enc_baz,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_baz,
 | 
			
		||||
		.memb_ofs = offsetof(struct nested_inner_msg, baz),
 | 
			
		||||
	},
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct osmo_gtlv_coding msg_ie_coding[] = {
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_FOO },
 | 
			
		||||
		.dec_func = dec_u16,
 | 
			
		||||
		.enc_func = enc_u16,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_u16,
 | 
			
		||||
		.memb_ofs = offsetof(struct decoded_msg, foo),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_BAR },
 | 
			
		||||
		.dec_func = dec_bar,
 | 
			
		||||
		.enc_func = enc_bar,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_bar,
 | 
			
		||||
		.memb_ofs = offsetof(struct decoded_msg, bar),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_BAZ },
 | 
			
		||||
		.dec_func = dec_baz,
 | 
			
		||||
		.enc_func = enc_baz,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_baz,
 | 
			
		||||
		.memb_ofs = offsetof(struct decoded_msg, baz),
 | 
			
		||||
		.has_presence_flag = true,
 | 
			
		||||
		.presence_flag_ofs = offsetof(struct decoded_msg, baz_present),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_REPEAT_INT },
 | 
			
		||||
		.dec_func = dec_u16,
 | 
			
		||||
		.enc_func = enc_u16,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_u16,
 | 
			
		||||
		.memb_ofs = offsetof(struct decoded_msg, repeat_int),
 | 
			
		||||
		.memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(struct decoded_msg, repeat_int),
 | 
			
		||||
		.has_count = true,
 | 
			
		||||
		.count_ofs = offsetof(struct decoded_msg, repeat_int_count),
 | 
			
		||||
		.count_max = ARRAY_SIZE(((struct decoded_msg *)0)->repeat_int),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_REPEAT_STRUCT },
 | 
			
		||||
		.dec_func = dec_repeat_struct,
 | 
			
		||||
		.enc_func = enc_repeat_struct,
 | 
			
		||||
		.enc_to_str_func = enc_to_str_repeat_struct,
 | 
			
		||||
		.memb_ofs = offsetof(struct decoded_msg, repeat_struct),
 | 
			
		||||
		.memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(struct decoded_msg, repeat_struct),
 | 
			
		||||
		.has_count = true,
 | 
			
		||||
		.count_ofs = offsetof(struct decoded_msg, repeat_struct_count),
 | 
			
		||||
		.count_max = ARRAY_SIZE(((struct decoded_msg *)0)->repeat_struct),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.ti = { TAG_NEST },
 | 
			
		||||
		.memb_ofs = offsetof(struct decoded_msg, nest),
 | 
			
		||||
		.nested_ies = nested_inner_msg_ies,
 | 
			
		||||
		.has_presence_flag = true,
 | 
			
		||||
		.presence_flag_ofs = offsetof(struct decoded_msg, nest_present),
 | 
			
		||||
	},
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
char *decoded_msg_to_str(const struct decoded_msg *m)
 | 
			
		||||
{
 | 
			
		||||
	return osmo_gtlvs_encode_to_str_c(ctx, m, 0, msg_ie_coding, tag_names);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const struct decoded_msg enc_dec_tests[] = {
 | 
			
		||||
	{
 | 
			
		||||
		.foo = 23,
 | 
			
		||||
		.bar = { "twentythree" },
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.foo = 23,
 | 
			
		||||
		.bar = { "twentythree" },
 | 
			
		||||
 | 
			
		||||
		.baz_present = true,
 | 
			
		||||
		.baz = {
 | 
			
		||||
			.v_int = 2323,
 | 
			
		||||
			.v_bool = true,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.foo = 23,
 | 
			
		||||
		.bar = { "twentythree" },
 | 
			
		||||
 | 
			
		||||
		.baz_present = true,
 | 
			
		||||
		.baz = {
 | 
			
		||||
			.v_int = 2323,
 | 
			
		||||
			.v_bool = true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.repeat_int_count = 3,
 | 
			
		||||
		.repeat_int = { 1, 2, 0x7fff },
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.foo = 23,
 | 
			
		||||
		.bar = { "twentythree" },
 | 
			
		||||
 | 
			
		||||
		.baz_present = true,
 | 
			
		||||
		.baz = {
 | 
			
		||||
			.v_int = 2323,
 | 
			
		||||
			.v_bool = true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.repeat_int_count = 3,
 | 
			
		||||
		.repeat_int = { 1, 2, 0x7fff },
 | 
			
		||||
 | 
			
		||||
		.repeat_struct_count = 2,
 | 
			
		||||
		.repeat_struct = {
 | 
			
		||||
			{
 | 
			
		||||
				.v_int = 1001,
 | 
			
		||||
				.v_bool = true,
 | 
			
		||||
				.v_enum = R_A,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				.v_int = 1002,
 | 
			
		||||
				.v_bool = false,
 | 
			
		||||
				.v_enum = R_B,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.nest_present = true,
 | 
			
		||||
		.nest = {
 | 
			
		||||
			.foo = 42,
 | 
			
		||||
			.bar = { "fortytwo" },
 | 
			
		||||
			.baz = {
 | 
			
		||||
				.v_int = 4242,
 | 
			
		||||
				.v_bool = false,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int verify_err_cb_data;
 | 
			
		||||
 | 
			
		||||
void err_cb(void *data, void *decoded_struct, const char *file, int line, const char *fmt, ...)
 | 
			
		||||
{
 | 
			
		||||
	assert(data == &verify_err_cb_data);
 | 
			
		||||
	va_list args;
 | 
			
		||||
	va_start(args, fmt);
 | 
			
		||||
	//printf("ERR: %s:%d ", file, line);
 | 
			
		||||
	printf("ERR: ");
 | 
			
		||||
	vprintf(fmt, args);
 | 
			
		||||
	va_end(args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_enc_dec(const char *label, const struct osmo_gtlv_cfg *cfg, bool ordered)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
	for (i = 0; i < ARRAY_SIZE(enc_dec_tests); i++) {
 | 
			
		||||
		int rc;
 | 
			
		||||
		const struct decoded_msg *orig = &enc_dec_tests[i];
 | 
			
		||||
		struct decoded_msg parsed = {};
 | 
			
		||||
		struct osmo_gtlv_load load;
 | 
			
		||||
		struct osmo_gtlv_put put;
 | 
			
		||||
 | 
			
		||||
		printf("\n=== start %s %s[%d]\n", label, __func__, i);
 | 
			
		||||
		printf("encoded: %s\n", decoded_msg_to_str(orig));
 | 
			
		||||
 | 
			
		||||
		put = (struct osmo_gtlv_put){
 | 
			
		||||
			.cfg = cfg,
 | 
			
		||||
			.dst = msgb_alloc(1024, __func__),
 | 
			
		||||
		};
 | 
			
		||||
		rc = osmo_gtlvs_encode(&put, (void *)orig, 0, msg_ie_coding, err_cb, &verify_err_cb_data, tag_names);
 | 
			
		||||
		printf("osmo_gtlvs_encode() rc = %d\n", rc);
 | 
			
		||||
		printf("%s.\n", osmo_hexdump(put.dst->data, put.dst->len));
 | 
			
		||||
 | 
			
		||||
		load = (struct osmo_gtlv_load){
 | 
			
		||||
			.cfg = cfg,
 | 
			
		||||
			.src = { put.dst->data, put.dst->len },
 | 
			
		||||
		};
 | 
			
		||||
		rc = osmo_gtlvs_decode(&parsed, 0, &load, ordered, msg_ie_coding, err_cb, &verify_err_cb_data, tag_names);
 | 
			
		||||
		printf("osmo_gtlvs_decode() rc = %d\n", rc);
 | 
			
		||||
		printf("decoded: %s\n", decoded_msg_to_str(&parsed));
 | 
			
		||||
		if (strcmp(decoded_msg_to_str(orig), decoded_msg_to_str(&parsed))) {
 | 
			
		||||
			printf(" ERROR: parsed != orig\n");
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		printf("=== end %s %s[%d]\n", label, __func__, i);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main()
 | 
			
		||||
{
 | 
			
		||||
	ctx = talloc_named_const(NULL, 0, "gtlv_test");
 | 
			
		||||
	msgb_talloc_ctx_init(ctx, 0);
 | 
			
		||||
 | 
			
		||||
	test_enc_dec("t8l8v ordered", &osmo_t8l8v_cfg, true);
 | 
			
		||||
	test_enc_dec("t8l8v unordered", &osmo_t8l8v_cfg, false);
 | 
			
		||||
 | 
			
		||||
	test_enc_dec("t16l16v ordered", &osmo_t16l16v_cfg, true);
 | 
			
		||||
	test_enc_dec("t16l16v unordered", &osmo_t16l16v_cfg, false);
 | 
			
		||||
 | 
			
		||||
	talloc_free(ctx);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								tests/libosmo-gtlv/gtlv_dec_enc_test.ok
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								tests/libosmo-gtlv/gtlv_dec_enc_test.ok
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
 | 
			
		||||
=== start t8l8v ordered test_enc_dec[0]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
=== end t8l8v ordered test_enc_dec[0]
 | 
			
		||||
 | 
			
		||||
=== start t8l8v ordered test_enc_dec[1]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
=== end t8l8v ordered test_enc_dec[1]
 | 
			
		||||
 | 
			
		||||
=== start t8l8v ordered test_enc_dec[2]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
=== end t8l8v ordered test_enc_dec[2]
 | 
			
		||||
 | 
			
		||||
=== start t8l8v ordered test_enc_dec[3]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff 05 03 03 e9 80 05 03 03 ea 01 06 12 01 02 00 2a 02 08 66 6f 72 74 79 74 77 6f 03 02 10 92 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
=== end t8l8v ordered test_enc_dec[3]
 | 
			
		||||
 | 
			
		||||
=== start t8l8v unordered test_enc_dec[0]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
=== end t8l8v unordered test_enc_dec[0]
 | 
			
		||||
 | 
			
		||||
=== start t8l8v unordered test_enc_dec[1]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
=== end t8l8v unordered test_enc_dec[1]
 | 
			
		||||
 | 
			
		||||
=== start t8l8v unordered test_enc_dec[2]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
=== end t8l8v unordered test_enc_dec[2]
 | 
			
		||||
 | 
			
		||||
=== start t8l8v unordered test_enc_dec[3]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff 05 03 03 e9 80 05 03 03 ea 01 06 12 01 02 00 2a 02 08 66 6f 72 74 79 74 77 6f 03 02 10 92 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
=== end t8l8v unordered test_enc_dec[3]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v ordered test_enc_dec[0]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
=== end t16l16v ordered test_enc_dec[0]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v ordered test_enc_dec[1]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
=== end t16l16v ordered test_enc_dec[1]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v ordered test_enc_dec[2]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
=== end t16l16v ordered test_enc_dec[2]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v ordered test_enc_dec[3]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff 00 05 00 03 03 e9 80 00 05 00 03 03 ea 01 00 06 00 18 00 01 00 02 00 2a 00 02 00 08 66 6f 72 74 79 74 77 6f 00 03 00 02 10 92 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
=== end t16l16v ordered test_enc_dec[3]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v unordered test_enc_dec[0]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree"
 | 
			
		||||
=== end t16l16v unordered test_enc_dec[0]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v unordered test_enc_dec[1]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
 | 
			
		||||
=== end t16l16v unordered test_enc_dec[1]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v unordered test_enc_dec[2]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
 | 
			
		||||
=== end t16l16v unordered test_enc_dec[2]
 | 
			
		||||
 | 
			
		||||
=== start t16l16v unordered test_enc_dec[3]
 | 
			
		||||
encoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
osmo_gtlvs_encode() rc = 0
 | 
			
		||||
00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff 00 05 00 03 03 e9 80 00 05 00 03 03 ea 01 00 06 00 18 00 01 00 02 00 2a 00 02 00 08 66 6f 72 74 79 74 77 6f 00 03 00 02 10 92 .
 | 
			
		||||
osmo_gtlvs_decode() rc = 0
 | 
			
		||||
decoded:  'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
 | 
			
		||||
=== end t16l16v unordered test_enc_dec[3]
 | 
			
		||||
							
								
								
									
										629
									
								
								tests/libosmo-gtlv/gtlv_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										629
									
								
								tests/libosmo-gtlv/gtlv_test.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,629 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 *  it under the terms of the GNU General Public License as published by
 | 
			
		||||
 *  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 *  (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 *  This program is distributed in the hope that it will be useful,
 | 
			
		||||
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 *  GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 *  You should have received a copy of the GNU General Public License
 | 
			
		||||
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gtlv/gtlv.h>
 | 
			
		||||
 | 
			
		||||
void *ctx;
 | 
			
		||||
 | 
			
		||||
struct ie {
 | 
			
		||||
	struct osmo_gtlv_tag_inst ti;
 | 
			
		||||
	const char *val;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* write all IEs to a msgb */
 | 
			
		||||
struct msgb *test_tlv_enc(const struct osmo_gtlv_cfg *cfg, const struct ie *ies)
 | 
			
		||||
{
 | 
			
		||||
	const struct ie *ie;
 | 
			
		||||
	struct osmo_gtlv_put gtlv = {
 | 
			
		||||
		.cfg = cfg,
 | 
			
		||||
		.dst = msgb_alloc(1024, __func__),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	for (ie = ies; ie->val; ie++) {
 | 
			
		||||
		/* put header without knowing length yet */
 | 
			
		||||
		OSMO_ASSERT(osmo_gtlv_put_tli(>lv, &ie->ti, 0) == 0);
 | 
			
		||||
		/* put value data, as much as desired */
 | 
			
		||||
		msgb_put(gtlv.dst, osmo_hexparse(ie->val, gtlv.dst->tail, msgb_tailroom(gtlv.dst)));
 | 
			
		||||
		/* update header len from amount of written data */
 | 
			
		||||
		OSMO_ASSERT(osmo_gtlv_put_update_tl(>lv) == 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printf("- encoded: %s.\n", osmo_hexdump(gtlv.dst->data, gtlv.dst->len));
 | 
			
		||||
	return gtlv.dst;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* read all IEs from the msgb, and verify that it matches the given list of IEs */
 | 
			
		||||
void test_tlv_dec(const struct osmo_gtlv_cfg *cfg, const struct ie *ies, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	const struct ie *ie;
 | 
			
		||||
	struct osmo_gtlv_load gtlv = {
 | 
			
		||||
		.cfg = cfg,
 | 
			
		||||
		.src = { msg->data, msg->len },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	printf("- decoding:\n");
 | 
			
		||||
	osmo_gtlv_load_start(>lv);
 | 
			
		||||
 | 
			
		||||
	for (ie = ies; ie->val; ie++) {
 | 
			
		||||
		int rc = osmo_gtlv_load_next(>lv);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: osmo_gtlv_load_next() rc = %d\n", rc);
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		/* end of TLV structure? */
 | 
			
		||||
		if (!gtlv.val)
 | 
			
		||||
			break;
 | 
			
		||||
		printf("  T=%s L=%zu", osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL), gtlv.len);
 | 
			
		||||
		printf(" v=%s\n", osmo_hexdump_nospc(gtlv.val, gtlv.len));
 | 
			
		||||
		if (gtlv.ti.tag != ie->ti.tag) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: expected tag %u, got tag %u\n", ie->ti.tag, gtlv.ti.tag);
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: expected val %s, got val %s\n", ie->val,
 | 
			
		||||
			       osmo_hexdump_nospc(gtlv.val, gtlv.len));
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_tlv_peek(const struct osmo_gtlv_cfg *cfg, const struct ie *ies, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	const struct ie *ie;
 | 
			
		||||
	struct osmo_gtlv_load gtlv = {
 | 
			
		||||
		.cfg = cfg,
 | 
			
		||||
		.src = { msg->data, msg->len },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	printf("- peeking:\n");
 | 
			
		||||
	osmo_gtlv_load_start(>lv);
 | 
			
		||||
 | 
			
		||||
	ie = ies;
 | 
			
		||||
	while (1) {
 | 
			
		||||
		int rc;
 | 
			
		||||
		struct osmo_gtlv_tag_inst next_tag;
 | 
			
		||||
		rc = osmo_gtlv_load_peek_tag(>lv, &next_tag);
 | 
			
		||||
		if (rc == -ENOENT) {
 | 
			
		||||
			printf("  peek rc=-ENOENT\n");
 | 
			
		||||
		} else {
 | 
			
		||||
			printf("  peek T=%s", osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL));
 | 
			
		||||
			printf("\n");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (ie->val && osmo_gtlv_tag_inst_cmp(&next_tag, &ie->ti)) {
 | 
			
		||||
			printf("  ERROR peeking tag: expected tag %s, got tag %s\n",
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL),
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL));
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (!ie->val && rc != -ENOENT) {
 | 
			
		||||
			printf("  ERROR peeking tag: expected -ENOENT, got rc=%d, tag %s\n", rc,
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL));
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (rc == -ENOENT)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		/* go to the next TLV */
 | 
			
		||||
		rc = osmo_gtlv_load_next(>lv);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: osmo_gtlv_load_next() rc = %d\n", rc);
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (ie->val)
 | 
			
		||||
			ie++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Decode TLV in random order, each time searching for a tag in the raw data */
 | 
			
		||||
void test_tlv_dec_by_tag(const struct osmo_gtlv_cfg *cfg, const struct ie *ies, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	const struct ie *last_ie;
 | 
			
		||||
	const struct ie *ie;
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct osmo_gtlv_load gtlv = {
 | 
			
		||||
		.cfg = cfg,
 | 
			
		||||
		.src = { msg->data, msg->len },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	printf("- decoding in reverse order:\n");
 | 
			
		||||
 | 
			
		||||
	last_ie = ies;
 | 
			
		||||
	while (last_ie->val) last_ie++;
 | 
			
		||||
	last_ie--;
 | 
			
		||||
 | 
			
		||||
	for (ie = last_ie; ie >= ies; ie--) {
 | 
			
		||||
		/* each time, look from the beginning */
 | 
			
		||||
		osmo_gtlv_load_start(>lv);
 | 
			
		||||
		rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) rc = %d\n",
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), rc);
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (!gtlv.val) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) returned NULL val\n",
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL));
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (osmo_gtlv_tag_inst_cmp(>lv.ti, &ie->ti)) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: expected tag %s, got tag %s\n",
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL),
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
 | 
			
		||||
			while (1) {
 | 
			
		||||
				printf("   (mismatch: T=%s L=%zu v=%s, checking for another occurrence of T=%s)\n",
 | 
			
		||||
				       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
 | 
			
		||||
				       gtlv.len,
 | 
			
		||||
				       osmo_hexdump_nospc(gtlv.val, gtlv.len),
 | 
			
		||||
				       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
 | 
			
		||||
 | 
			
		||||
				rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
 | 
			
		||||
				if (rc || !gtlv.val) {
 | 
			
		||||
					printf("  ERROR val not found\n");
 | 
			
		||||
					exit(1);
 | 
			
		||||
				}
 | 
			
		||||
				if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len)) == 0) {
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		printf("  T=%s L=%zu v=%s\n",
 | 
			
		||||
		       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
 | 
			
		||||
		       gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printf("- decoding every second tag:\n");
 | 
			
		||||
 | 
			
		||||
	osmo_gtlv_load_start(>lv);
 | 
			
		||||
	for (ie = ies; ie->val; ie++) {
 | 
			
		||||
		/* skip one tag */
 | 
			
		||||
		ie++;
 | 
			
		||||
		if (!ie->val)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) rc = %d\n",
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), rc);
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (!gtlv.val) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) returned NULL val\n",
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL));
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (osmo_gtlv_tag_inst_cmp(>lv.ti, &ie->ti)) {
 | 
			
		||||
			printf("  ERROR loading TLV structure: expected tag %s, got tag %s\n",
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL),
 | 
			
		||||
			       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
 | 
			
		||||
			exit(1);
 | 
			
		||||
		}
 | 
			
		||||
		if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
 | 
			
		||||
			while (1) {
 | 
			
		||||
				printf("   (mismatch: T=%s L=%zu v=%s, checking for another occurrence of T=%s)\n",
 | 
			
		||||
				       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
 | 
			
		||||
				       gtlv.len,
 | 
			
		||||
				       osmo_hexdump_nospc(gtlv.val, gtlv.len),
 | 
			
		||||
				       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
 | 
			
		||||
 | 
			
		||||
				rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
 | 
			
		||||
				if (rc || !gtlv.val) {
 | 
			
		||||
					printf("  ERROR val not found\n");
 | 
			
		||||
					exit(1);
 | 
			
		||||
				}
 | 
			
		||||
				if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len)) == 0) {
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		printf("  T=%s L=%zu v=%s\n",
 | 
			
		||||
		       osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
 | 
			
		||||
		       gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printf("- enforcing order: without restart, a past tag is not parsed again:\n");
 | 
			
		||||
	/* Try to read the first tag, expect that it isn't found because we're already halfway in the message data */
 | 
			
		||||
	ie = ies;
 | 
			
		||||
	rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
 | 
			
		||||
	printf("  osmo_gtlv_load_next_by_tag_inst(%s) rc=", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL));
 | 
			
		||||
	if (rc == -ENOENT) {
 | 
			
		||||
		printf("-ENOENT\n");
 | 
			
		||||
	} else {
 | 
			
		||||
		printf("%d\n", rc);
 | 
			
		||||
		printf("  ERROR: expected -ENOENT\n");
 | 
			
		||||
		exit(1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_tlv(const char *label, struct ie *tests[], size_t tests_len, const struct osmo_gtlv_cfg *cfg)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
	for (i = 0; i < tests_len; i++) {
 | 
			
		||||
		const struct ie *ies = tests[i];
 | 
			
		||||
		struct msgb *msg;
 | 
			
		||||
		printf("\n=== start: %s[%d]\n", label, i);
 | 
			
		||||
 | 
			
		||||
		msg = test_tlv_enc(cfg, ies);
 | 
			
		||||
		test_tlv_dec(cfg, ies, msg);
 | 
			
		||||
		test_tlv_peek(cfg, ies, msg);
 | 
			
		||||
		test_tlv_dec_by_tag(cfg, ies, msg);
 | 
			
		||||
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
 | 
			
		||||
		printf("=== end: %s[%d]\n", label, i);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ie t8l8v_test1[] = {
 | 
			
		||||
	/* smallest T */
 | 
			
		||||
	{ {}, "2342" },
 | 
			
		||||
	/* largest T */
 | 
			
		||||
	{ {255}, "2342" },
 | 
			
		||||
 | 
			
		||||
	/* smallest V (no V data) */
 | 
			
		||||
	{ {1}, "" },
 | 
			
		||||
	/* largest V, 255 bytes is the largest that an 8bit size length can express. */
 | 
			
		||||
	{ {123}, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	       "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	       "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	       "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	       "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	       "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	       "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	       "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	/* arbitrary test data */
 | 
			
		||||
	{ {101}, "11" },
 | 
			
		||||
	{ {102}, "2222" },
 | 
			
		||||
	{ {103}, "333333" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie t8l8v_test_multi[] = {
 | 
			
		||||
	{ {42}, "42" },
 | 
			
		||||
	{ {2}, "0101" },
 | 
			
		||||
	{ {2}, "2222" },
 | 
			
		||||
	{ {3}, "11" },
 | 
			
		||||
	{ {3}, "2222" },
 | 
			
		||||
	{ {3}, "333333" },
 | 
			
		||||
	{ {23}, "23" },
 | 
			
		||||
	{ {42}, "666f72747974776f" },
 | 
			
		||||
	{ {23}, "7477656e74797468726565" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie *t8l8v_tests[] = {
 | 
			
		||||
	t8l8v_test1,
 | 
			
		||||
	t8l8v_test_multi,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void test_t8l8v()
 | 
			
		||||
{
 | 
			
		||||
	test_tlv(__func__, t8l8v_tests, ARRAY_SIZE(t8l8v_tests), &osmo_t8l8v_cfg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ie t16l16v_test1[] = {
 | 
			
		||||
	/* smallest T */
 | 
			
		||||
	{ {}, "2342" },
 | 
			
		||||
	/* largest T */
 | 
			
		||||
	{ {65535}, "2342" },
 | 
			
		||||
 | 
			
		||||
	/* smallest V (no V data) */
 | 
			
		||||
	{ {1}, "" },
 | 
			
		||||
	/* 256 bytes is one more than an 8bit size length can express. */
 | 
			
		||||
	{ {123}, "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	/* arbitrary test data */
 | 
			
		||||
	{ {1001}, "11" },
 | 
			
		||||
	{ {1002}, "2222" },
 | 
			
		||||
	{ {1003}, "333333" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie t16l16v_test_multi[] = {
 | 
			
		||||
	{ {1042}, "42" },
 | 
			
		||||
	{ {102}, "0101" },
 | 
			
		||||
	{ {102}, "2222" },
 | 
			
		||||
	{ {103}, "11" },
 | 
			
		||||
	{ {103}, "2222" },
 | 
			
		||||
	{ {103}, "333333" },
 | 
			
		||||
	{ {1023}, "23" },
 | 
			
		||||
	{ {1042}, "666f72747974776f" },
 | 
			
		||||
	{ {1023}, "7477656e74797468726565" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie *t16l16v_tests[] = {
 | 
			
		||||
	t16l16v_test1,
 | 
			
		||||
	t16l16v_test_multi,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void test_t16l16v()
 | 
			
		||||
{
 | 
			
		||||
	test_tlv(__func__, t16l16v_tests, ARRAY_SIZE(t16l16v_tests), &osmo_t16l16v_cfg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ie txlxv_test1[] = {
 | 
			
		||||
	/* smallest T */
 | 
			
		||||
	{ {}, "2342" },
 | 
			
		||||
	/* largest T that still fits in one encoded octet (highest bit serves as flag) */
 | 
			
		||||
	{ {0x7f}, "2342" },
 | 
			
		||||
	/* smallest T that needs two octets to be encoded (first octet = 0x80 flag + 0, second octet = 0x1) */
 | 
			
		||||
	{ {0x80}, "2342" },
 | 
			
		||||
	/* largest T that can be encoded in 16bit - one flag bit. */
 | 
			
		||||
	{ {0x7fff}, "2342" },
 | 
			
		||||
 | 
			
		||||
	/* smallest V (no V data) */
 | 
			
		||||
	{ {1}, "" },
 | 
			
		||||
	/* 256 bytes is one more than an 8bit size length can express. */
 | 
			
		||||
	{ {123}, "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	       "0000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	/* arbitrary test data */
 | 
			
		||||
	{ {1002}, "2222" },
 | 
			
		||||
	{ {1003}, "333333" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie txlxv_test_multi[] = {
 | 
			
		||||
	{ {1042}, "42" },
 | 
			
		||||
	{ {1002}, "0101" },
 | 
			
		||||
	{ {1002}, "2222" },
 | 
			
		||||
	{ {103}, "11" },
 | 
			
		||||
	{ {103}, "2222" },
 | 
			
		||||
	{ {103}, "333333" },
 | 
			
		||||
	{ {1023}, "23" },
 | 
			
		||||
	{ {1042}, "666f72747974776f" },
 | 
			
		||||
	{ {1023}, "7477656e74797468726565" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie *txlxv_tests[] = {
 | 
			
		||||
	txlxv_test1,
 | 
			
		||||
	txlxv_test_multi,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Example of defining a variable TL, where size of T and L depend on the actual tag and length values: load. */
 | 
			
		||||
int txlxv_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t src_data_len)
 | 
			
		||||
{
 | 
			
		||||
	const uint8_t *pos = src_data;
 | 
			
		||||
	const uint8_t *end = src_data + src_data_len;
 | 
			
		||||
	if (pos[0] & 0x80) {
 | 
			
		||||
		if (pos + 2 > end)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		gtlv->ti.tag = (((int)pos[1]) << 7) + (pos[0] & 0x7f);
 | 
			
		||||
		pos += 2;
 | 
			
		||||
	} else {
 | 
			
		||||
		gtlv->ti.tag = pos[0];
 | 
			
		||||
		pos++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (gtlv->ti.tag) {
 | 
			
		||||
	case 1002:
 | 
			
		||||
		/* fixed-length IE */
 | 
			
		||||
		gtlv->len = 2;
 | 
			
		||||
		break;
 | 
			
		||||
	case 123:
 | 
			
		||||
		/* 16bit length IE */
 | 
			
		||||
		if (pos + 2 > end)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		gtlv->len = osmo_load16be(pos);
 | 
			
		||||
		pos += 2;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		/* 8bit length IE */
 | 
			
		||||
		if (pos + 1 > end)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		gtlv->len = *pos;
 | 
			
		||||
		pos++;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	gtlv->val = pos;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Example of defining a variable TL, where size of T and L depend on the actual tag and length values: store. */
 | 
			
		||||
int txlxv_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_gtlv_tag_inst *ti, size_t len,
 | 
			
		||||
		   struct osmo_gtlv_put *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t *pos = dst_data;
 | 
			
		||||
	uint8_t *end = dst_data + dst_data_avail;
 | 
			
		||||
	unsigned int tag = ti->tag;
 | 
			
		||||
	if (tag < 0x80) {
 | 
			
		||||
		if (pos + 1 > end)
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		pos[0] = tag;
 | 
			
		||||
		pos++;
 | 
			
		||||
	} else {
 | 
			
		||||
		if (pos + 2 > end)
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		pos[0] = 0x80 + (tag & 0x7f);
 | 
			
		||||
		pos[1] = tag >> 7;
 | 
			
		||||
		pos += 2;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (tag) {
 | 
			
		||||
	case 1002:
 | 
			
		||||
		/* fixed-length IE, write no len */
 | 
			
		||||
		break;
 | 
			
		||||
	case 123:
 | 
			
		||||
		/* 16bit length IE */
 | 
			
		||||
		if (len > UINT16_MAX)
 | 
			
		||||
			return -ERANGE;
 | 
			
		||||
		if (pos + 2 > end)
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		osmo_store16be(len, pos);
 | 
			
		||||
		pos += 2;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		/* 8bit length IE */
 | 
			
		||||
		if (len > UINT8_MAX)
 | 
			
		||||
			return -ERANGE;
 | 
			
		||||
		if (pos + 1 > end)
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		pos[0] = len;
 | 
			
		||||
		pos++;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	return pos - dst_data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct osmo_gtlv_cfg txlxv_cfg = {
 | 
			
		||||
	.tl_min_size = 1,
 | 
			
		||||
	.load_tl = txlxv_load_tl,
 | 
			
		||||
	.store_tl = txlxv_store_tl,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void test_txlxv()
 | 
			
		||||
{
 | 
			
		||||
	test_tlv(__func__, txlxv_tests, ARRAY_SIZE(txlxv_tests), &txlxv_cfg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Example of defining a TLI, with an instance indicator */
 | 
			
		||||
static int tliv_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t src_data_len)
 | 
			
		||||
{
 | 
			
		||||
	/* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 2. */
 | 
			
		||||
	gtlv->ti.tag = src_data[0];
 | 
			
		||||
	gtlv->len = src_data[1];
 | 
			
		||||
 | 
			
		||||
	switch (gtlv->ti.tag) {
 | 
			
		||||
	/* All tags that are TLIV go here */
 | 
			
		||||
	case 5:
 | 
			
		||||
	case 7:
 | 
			
		||||
	case 9:
 | 
			
		||||
		if (src_data_len < 3)
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		gtlv->ti.instance_present = true;
 | 
			
		||||
		gtlv->ti.instance = src_data[2];
 | 
			
		||||
		gtlv->val = src_data + 3;
 | 
			
		||||
		return 0;
 | 
			
		||||
	default:
 | 
			
		||||
		gtlv->val = src_data + 2;
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int tliv_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_gtlv_tag_inst *ti, size_t len,
 | 
			
		||||
			 struct osmo_gtlv_put *gtlv)
 | 
			
		||||
{
 | 
			
		||||
	if (ti->tag > UINT8_MAX)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	if (len > UINT8_MAX)
 | 
			
		||||
		return -EMSGSIZE;
 | 
			
		||||
	if (dst_data_avail < 2)
 | 
			
		||||
		return -ENOSPC;
 | 
			
		||||
 | 
			
		||||
	dst_data[0] = ti->tag;
 | 
			
		||||
	dst_data[1] = len;
 | 
			
		||||
 | 
			
		||||
	switch (ti->tag) {
 | 
			
		||||
	/* All tags that are TLIV go here */
 | 
			
		||||
	case 5:
 | 
			
		||||
	case 7:
 | 
			
		||||
	case 9:
 | 
			
		||||
		if (dst_data_avail < 3)
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		if (!ti->instance_present)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		if (ti->instance > UINT8_MAX)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		dst_data[2] = ti->instance;
 | 
			
		||||
		return 3;
 | 
			
		||||
	default:
 | 
			
		||||
		return 2;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct osmo_gtlv_cfg osmo_tliv_cfg = {
 | 
			
		||||
	.tl_min_size = 2,
 | 
			
		||||
	.load_tl = tliv_load_tl,
 | 
			
		||||
	.store_tl = tliv_store_tl,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie tliv_test1[] = {
 | 
			
		||||
	/* TLV */
 | 
			
		||||
	{ {1}, "0002" },
 | 
			
		||||
	/* TLIV */
 | 
			
		||||
	{ {5, true, 1}, "0017" },
 | 
			
		||||
	/* TLIV */
 | 
			
		||||
	{ {5, true, 2}, "0018" },
 | 
			
		||||
	/* TLIV */
 | 
			
		||||
	{ {5, true, 3}, "0019" },
 | 
			
		||||
	/* TLV */
 | 
			
		||||
	{ {6}, "001a" },
 | 
			
		||||
	/* TLIV */
 | 
			
		||||
	{ {7, true, 1}, "001b" },
 | 
			
		||||
	/* TLIV */
 | 
			
		||||
	{ {9, true, 1}, "001c" },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ie *tliv_tests[] = {
 | 
			
		||||
	tliv_test1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void test_tliv()
 | 
			
		||||
{
 | 
			
		||||
	test_tlv(__func__, tliv_tests, ARRAY_SIZE(tliv_tests), &osmo_tliv_cfg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main()
 | 
			
		||||
{
 | 
			
		||||
	ctx = talloc_named_const(NULL, 0, "gtlv_test");
 | 
			
		||||
	msgb_talloc_ctx_init(ctx, 0);
 | 
			
		||||
 | 
			
		||||
	test_t8l8v();
 | 
			
		||||
	test_t16l16v();
 | 
			
		||||
	test_txlxv();
 | 
			
		||||
	test_tliv();
 | 
			
		||||
 | 
			
		||||
	talloc_free(ctx);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user