16 Commits

Author SHA1 Message Date
Neels Hofmeyr
f149770f32 tliv wip
Change-Id: I76c0e3e60eff61354e580cb70ef8645f09a29ec8
2022-02-25 01:53:16 +01:00
Neels Hofmeyr
510266094e ladder charts
Related: SYS#5599
Change-Id: Ie8c1b1fa50cd4f1569f0fcb02d42c713e6e49cad
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
8cfea100b1 add osmo-pfcp-tool
Related: SYS#5599
Change-Id: I34a80d43a14c7b68952c7d337d8042d6f28ceae7
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
c02aabe77d add nft
Change-Id: Ic0d319eb4f98cd51a5999c804c4203ab0bdda650
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
af1aec37c6 add osmo-upf
Related: SYS#5599
Change-Id: I745bcbde6859004c41ddbfd2558036bf9a2d1de2
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
6dc0ff7fa8 add libgtpnl dependency
Related: SYS#5599
Change-Id: I9928be6f62f5a89d98bdac63428f7a046c95c855
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
163473ccdc add pfcp heartbeat fsm
Related: SYS#5599
Change-Id: Id822c9c7a71461d17f062c2a3d10cb2f616c7f63
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
b00446f580 add pfcp_endpoint
Related: SYS#5599
Change-Id: Ic8d42e201b63064a71b40ca45a5a40e29941e8ac
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
e1248ef746 add initial FSM design charts
Related: SYS#5599
Change-Id: I55474daa6bb204a0fe7da0a3bf888bb7d1c46677
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
4dc863f74f libosmo-pfcp: implement PFCP header and msg handling
Related: SYS#5599
Change-Id: I3f85ea052a6b7c064244a8093777e53a47c8c61e
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
70f46ece7b libosmo-pfcp: implement/generate TLV and IE value coding
Related: SYS#5599
Change-Id: I3069045b2d42dac88d955c636230adc64a7a4aa7
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
20440519fb libosmo-pfcp: add pfcp_proto.h pfcp_strs.h
Related: SYS#5599
Change-Id: I568b821e89007ed52eeefcdbcb6edd8052a8b5be
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
e7199f5f8c contrib: add PFCP cause and IEI string maps
These help to build enums and value_strings using regexes. They are a
verbatim copy from 3GPP TS 29.244 version 16.6.0 Release 16, paired with
C-compatible and possibly abbreviated name strings.

Related: SYS#5599
Change-Id: I7f37efd3cfc4c7b0ae49740ac15e461c52fae6e8
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
63cba988d3 libosmo-tlv: add C code generator for IE structs and arrays
Defining a protocol of message types with lists of IEs bears a lot of
repetitive, copy-paste-error-prone writing out of data structures.
Add a third layer to libosmo-tlv, which allows helpful code generation.

By non-repetitive data structures that briefly describe the protocol's
messages and IEs, generate possibly repetitive IE list arrays and
decoded-struct definitions automatically, avoiding grunt work errors.

I tried C macros for this at first, but it became too convoluted.
Generating C code that can be read and grepped makes things easier.

A usage example is found in tests/libosmo-tlv/test_tlv_gen/.

Related: SYS#5599
Change-Id: Ifb3ea54d2797ce060b95834aa117725ec2d6c4cf
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
74a3747662 libosmo-tlv: add auto dec/enc to/from structs
Add osmo_tlv_coding: describe the value part of a TLV (decode and
encode), describe a struct with its members, and get/put readily decoded
structs from/to a raw PDU, directly.

With osmo_tlv_coding defined for a protocol's tags, we only deal with
encoded PDUs or fully decoded C structs, no TLV related
re-implementations clutter up the message handling code.

A usage example is given in tlv_dec_enc_test. The first real use will be
the PFCP protocol in osmo-upf.git.

With osmo_tlv_coding, there still is a lot of monkey work involved in
describing the decoded structs. A subsequent patch adds a generator for
osmo_tlv_coding and message structs from tag value lists.

Related: SYS#5599
Change-Id: I65de793105882a452124ee58adb0e58469e6e796
2022-02-25 01:53:07 +01:00
Neels Hofmeyr
5ce21e849a libosmo-tlv: add versatile TLV de- and encoder
An all new TLV parser supporting:

- Any size of T and L (determined by callback function),
- "Grouped IEs", so that an IE payload is a nested IE structure,
- optional/mandatory/multi-occurence IEs.

Will be used for PFCP message decoding and encoding, a T16L16V protocol
which requires above features.

Previously, the way we deal with TLVs causes a lot of code
re-implementation. The TL decoding is taken care of by the API, but for
encoding, we essentially re-implement the TL encoding for each protocol
and each encoded message. This API is an improvement in that we only
once implement the TL coding (or just use osmo_t8l8v_cfg /
osmo_t16l16v_cfg), get symmetric de- and encoding of the TL, and only
need to deal with the value part of each IE.

The common pattern of
- store TL preliminarily,
- write V data and
- update L after V is complete
is conveniently done by osmo_tlv_put_update_tl().

Related: SYS#5599
Change-Id: Ib0fd00d9f288ffe13b7e67701f3e47073587404a
2022-02-25 01:53:07 +01:00
93 changed files with 14545 additions and 7 deletions

View File

@@ -42,6 +42,9 @@ 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.2.1)
PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2)
dnl checks for header files
AC_HEADER_STDC
@@ -197,14 +200,23 @@ AM_CONFIG_HEADER(config.h)
AC_OUTPUT(
include/Makefile
include/osmocom/Makefile
include/osmocom/tlv/Makefile
include/osmocom/pfcp/Makefile
include/osmocom/upf/Makefile
src/Makefile
src/libosmo-tlv/Makefile
src/libosmo-pfcp/Makefile
src/osmo-upf/Makefile
src/osmo-pfcp-tool/Makefile
tests/Makefile
tests/atlocal
tests/libosmo-tlv/Makefile
tests/libosmo-tlv/test_tlv_gen/Makefile
tests/libosmo-tlv/test_tliv/Makefile
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile
doc/charts/Makefile
contrib/Makefile
contrib/systemd/Makefile
Makefile)

View File

@@ -0,0 +1,3 @@
peer 127.0.0.1
tx assoc-setup-req
sleep 1

View File

@@ -0,0 +1,8 @@
peer 127.0.0.1
tx assoc-setup-req
sleep 3
retrans req
sleep 5
retrans req
sleep 1
retrans req

View File

@@ -0,0 +1,10 @@
peer 127.0.0.1
tx heartbeat
sleep 2
tx heartbeat
sleep 2
tx heartbeat
sleep 2
tx heartbeat
sleep 2
tx heartbeat

View File

@@ -0,0 +1,5 @@
log stderr
logging level set-all info
local-addr 127.0.0.2
listen

View File

@@ -0,0 +1,8 @@
timer pfcp x23 0
peer 127.0.0.1
tx assoc-setup-req
sleep 1
session
tx session-est-req forw
sleep 5
tx session-del-req

View File

@@ -0,0 +1,14 @@
timer pfcp x23 0
peer 127.0.0.1
tx assoc-setup-req
sleep 1
session
tx session-est-req drop
sleep 3
tx session-mod-req forw
sleep 5
tx session-mod-req drop
sleep 3
tx session-mod-req forw
sleep 3
tx session-del-req

View File

@@ -32,6 +32,7 @@ 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

19
contrib/pfcp_cause.txt Normal file
View File

@@ -0,0 +1,19 @@
0 RESERVED Reserved
1 REQUEST_ACCEPTED Request accepted (success)
2 MORE_USAGE_REPORT_TO_SEND More Usage Report to send
64 REQUEST_REJECTED Request rejected (reason not specified)
65 SESSION_CTX_NOT_FOUND Session context not found
66 MANDATORY_IE_MISSING Mandatory IE missing
67 CONDITIONAL_IE_MISSING Conditional IE missing
68 INVALID_LENGTH Invalid length
69 MANDATORY_IE_INCORRECT Mandatory IE incorrect
70 INVALID_FORW_POLICY Invalid Forwarding Policy
71 INVALID_F_TEID_ALLOC_OPTION Invalid F-TEID allocation option
72 NO_ESTABLISHED_PFCP_ASSOC No established PFCP Association
73 RULE_CREATION_MOD_FAILURE Rule creation/modification Failure
74 PFCP_ENTITY_IN_CONGESTION PFCP entity in congestion
75 NO_RESOURCES_AVAILABLE No resources available
76 SERVICE_NOT_SUPPORTED Service not supported
77 SYSTEM_FAILURE System failure
78 REDIRECTION_REQUESTED Redirection Requested
79 ALL_DYNAMIC_ADDRESSES_ARE_OCCUPIED All dynamic addresses are occupied

271
contrib/pfcp_iei.txt Normal file
View File

@@ -0,0 +1,271 @@
1 CREATE_PDR Create PDR
2 PDI PDI
3 CREATE_FAR Create FAR
4 FORW_PARAMS Forwarding Parameters
5 DUPL_PARAMS Duplicating Parameters
6 CREATE_URR Create URR
7 CREATE_QER Create QER
8 CREATED_PDR Created PDR
9 UPD_PDR Update PDR
10 UPD_FAR Update FAR
11 UPD_FORW_PARAMS Update Forwarding Parameters
12 UPD_BAR_SESS_REP_RESP Update BAR (PFCP Session Report Response)
13 UPD_URR Update URR
14 UPD_QER Update QER
15 REMOVE_PDR Remove PDR
16 REMOVE_FAR Remove FAR
17 REMOVE_URR Remove URR
18 REMOVE_QER Remove QER
19 CAUSE Cause
20 SOURCE_IFACE Source Interface
21 F_TEID F-TEID
22 NETWORK_INST Network Instance
23 SDF_FILTER SDF Filter
24 APPLICATION_ID Application ID
25 GATE_STATUS Gate Status
26 MBR MBR
27 GBR GBR
28 QER_CORRELATION_ID QER Correlation ID
29 PRECEDENCE Precedence
30 TRANSPORT_LEVEL_MARKING Transport Level Marking
31 VOLUME_THRESH Volume Threshold
32 TIME_THRESH Time Threshold
33 MONITORING_TIME Monitoring Time
34 SUBSEQUENT_VOLUME_THRESH Subsequent Volume Threshold
35 SUBSEQUENT_TIME_THRESH Subsequent Time Threshold
36 INACT_DETECTION_TIME Inactivity Detection Time
37 REPORTING_TRIGGERS Reporting Triggers
38 REDIRECT_INFO Redirect Information
39 REP_TYPE Report Type
40 OFFENDING_IE Offending IE
41 FORW_POLICY Forwarding Policy
42 DESTINATION_IFACE Destination Interface
43 UP_FUNCTION_FEATURES UP Function Features
44 APPLY_ACTION Apply Action
45 DL_DATA_SERVICE_INFO Downlink Data Service Information
46 DL_DATA_NOTIFICATION_DELAY Downlink Data Notification Delay
47 DL_BUFF_DURATION DL Buffering Duration
48 DL_BUFF_SUGGESTED_PACKET_COUNT DL Buffering Suggested Packet Count
49 PFCPSMREQ_FLAGS PFCPSMReq-Flags
50 PFCPSRRSP_FLAGS PFCPSRRsp-Flags
51 LOAD_CTRL_INFO Load Control Information
52 SEQUENCE_NUMBER Sequence Number
53 METRIC Metric
54 OVERLOAD_CTRL_INFO Overload Control Information
55 TIMER Timer
56 PDR_ID PDR ID
57 F_SEID F-SEID
58 APPLICATION_IDS_PFDS Application ID's PFDs
59 PFD_CONTEXT PFD context
60 NODE_ID Node ID
61 PFD_CONTENTS PFD contents
62 MEAS_METHOD Measurement Method
63 USAGE_REP_TRIGGER Usage Report Trigger
64 MEAS_PERIOD Measurement Period
65 FQ_CSID FQ-CSID
66 VOLUME_MEAS Volume Measurement
67 DURATION_MEAS Duration Measurement
68 APPLICATION_DETECTION_INFO Application Detection Information
69 TIME_OF_FIRST_PACKET Time of First Packet
70 TIME_OF_LAST_PACKET Time of Last Packet
71 QUOTA_HOLDING_TIME Quota Holding Time
72 DROPPED_DL_TRAFFIC_THRESH Dropped DL Traffic Threshold
73 VOLUME_QUOTA Volume Quota
74 TIME_QUOTA Time Quota
75 START_TIME Start Time
76 END_TIME End Time
77 QUERY_URR Query URR
78 USAGE_REP_SESS_MOD_RESP Usage Report (Session Modification Response)
79 USAGE_REP_SESS_DEL_RESP Usage Report (Session Deletion Response)
80 USAGE_REP_SESS_REP_REQ Usage Report (Session Report Request)
81 URR_ID URR ID
82 LINKED_URR_ID Linked URR ID
83 DL_DATA_REP Downlink Data Report
84 OUTER_HEADER_CREATION Outer Header Creation
85 CREATE_BAR Create BAR
86 UPD_BAR_SESS_MOD_REQ Update BAR (Session Modification Request)
87 REMOVE_BAR Remove BAR
88 BAR_ID BAR ID
89 CP_FUNCTION_FEATURES CP Function Features
90 USAGE_INFO Usage Information
91 APPLICATION_INST_ID Application Instance ID
92 FLOW_INFO Flow Information
93 UE_IP_ADDRESS UE IP Address
94 PACKET_RATE Packet Rate
95 OUTER_HEADER_REMOVAL Outer Header Removal
96 RECOVERY_TIME_STAMP Recovery Time Stamp
97 DL_FLOW_LEVEL_MARKING DL Flow Level Marking
98 HEADER_ENRICHMENT Header Enrichment
99 ERROR_IND_REP Error Indication Report
100 MEAS_INFO Measurement Information
101 NODE_REP_TYPE Node Report Type
102 USER_PLANE_PATH_FAILURE_REP User Plane Path Failure Report
103 REMOTE_GTP_U_PEER Remote GTP-U Peer
104 UR_SEQN UR-SEQN
105 UPD_DUPL_PARAMS Update Duplicating Parameters
106 ACTIVATE_PREDEFINED_RULES Activate Predefined Rules
107 DEACTIVATE_PREDEFINED_RULES Deactivate Predefined Rules
108 FAR_ID FAR ID
109 QER_ID QER ID
110 OCI_FLAGS OCI Flags
111 PFCP_ASSOC_RELEASE_REQ PFCP Association Release Request
112 GRACEFUL_RELEASE_PERIOD Graceful Release Period
113 PDN_TYPE PDN Type
114 FAILED_RULE_ID Failed Rule ID
115 TIME_QUOTA_MECHANISM Time Quota Mechanism
116 RESERVED Reserved
117 USER_PLANE_INACT_TIMER User Plane Inactivity Timer
118 AGGREGATED_URRS Aggregated URRs
119 MULTIPLIER Multiplier
120 AGGREGATED_URR_ID Aggregated URR ID
121 SUBSEQUENT_VOLUME_QUOTA Subsequent Volume Quota
122 SUBSEQUENT_TIME_QUOTA Subsequent Time Quota
123 RQI RQI
124 QFI QFI
125 QUERY_URR_REFERENCE Query URR Reference
126 ADDITIONAL_USAGE_REPS_INFO Additional Usage Reports Information
127 CREATE_TRAFFIC_ENDPOINT Create Traffic Endpoint
128 CREATED_TRAFFIC_ENDPOINT Created Traffic Endpoint
129 UPD_TRAFFIC_ENDPOINT Update Traffic Endpoint
130 REMOVE_TRAFFIC_ENDPOINT Remove Traffic Endpoint
131 TRAFFIC_ENDPOINT_ID Traffic Endpoint ID
132 ETHERNET_PACKET_FILTER Ethernet Packet Filter
133 MAC_ADDRESS MAC address
134 C_TAG C-TAG
135 S_TAG S-TAG
136 ETHERTYPE Ethertype
137 PROXYING Proxying
138 ETHERNET_FILTER_ID Ethernet Filter ID
139 ETHERNET_FILTER_PROPERTIES Ethernet Filter Properties
140 SUGGESTED_BUFF_PACKETS_COUNT Suggested Buffering Packets Count
141 USER_ID User ID
142 ETHERNET_PDU_SESS_INFO Ethernet PDU Session Information
143 ETHERNET_TRAFFIC_INFO Ethernet Traffic Information
144 MAC_ADDRS_DETECTED MAC Addresses Detected
145 MAC_ADDRS_REMOVED MAC Addresses Removed
146 ETHERNET_INACT_TIMER Ethernet Inactivity Timer
147 ADDITIONAL_MONITORING_TIME Additional Monitoring Time
148 EVENT_QUOTA Event Quota
149 EVENT_THRESH Event Threshold
150 SUBSEQUENT_EVENT_QUOTA Subsequent Event Quota
151 SUBSEQUENT_EVENT_THRESH Subsequent Event Threshold
152 TRACE_INFO Trace Information
153 FRAMED_ROUTE Framed-Route
154 FRAMED_ROUTING Framed-Routing
155 FRAMED_IPV6_ROUTE Framed-IPv6-Route
156 TIME_STAMP Time Stamp
157 AVERAGING_WINDOW Averaging Window
158 PAGING_POLICY_INDICATOR Paging Policy Indicator
159 APN_DNN APN/DNN
160 3GPP_IFACE_TYPE 3GPP Interface Type
161 PFCPSRREQ_FLAGS PFCPSRReq-Flags
162 PFCPAUREQ_FLAGS PFCPAUReq-Flags
163 ACTIVATION_TIME Activation Time
164 DEACTIVATION_TIME Deactivation Time
165 CREATE_MAR Create MAR
166 3GPP_ACCESS_FORW_ACTION_INFO 3GPP Access Forwarding Action Information
167 NON_3GPP_ACCESS_FORW_ACTION_INFO Non-3GPP Access Forwarding Action Information
168 REMOVE_MAR Remove MAR
169 UPD_MAR Update MAR
170 MAR_ID MAR ID
171 STEERING_FUNCTIONALITY Steering Functionality
172 STEERING_MODE Steering Mode
173 WEIGHT Weight
174 PRIORITY Priority
175 UPD_3GPP_ACCESS_FORW_ACTION_INFO Update 3GPP Access Forwarding Action Information
176 UPD_NON_3GPP_ACCESS_FORW_ACTION_INFO Update Non 3GPP Access Forwarding Action Information
177 UE_IP_ADDRESS_POOL_IDENTITY UE IP address Pool Identity
178 ALTERNATIVE_SMF_IP_ADDRESS Alternative SMF IP Address
179 PACKET_REPLICATION_AND_DETECTION_CARRY_ON_INFO Packet Replication and Detection Carry-On Information
180 SMF_SET_ID SMF Set ID
181 QUOTA_VALIDITY_TIME Quota Validity Time
182 NUMBER_OF_REPS Number of Reports
183 PFCP_SESS_RETENTION_INFO_IN_ASSOC_SETUP_REQ PFCP Session Retention Information (within PFCP Association Setup Request)
184 PFCPASRSP_FLAGS PFCPASRsp-Flags
185 CP_ENTITY_IP_ADDRESS CP PFCP Entity IP Address
186 PFCPSEREQ_FLAGS PFCPSEReq-Flags
187 USER_PLANE_PATH_RECOVERY_REP User Plane Path Recovery Report
188 IP_MULTICAST_ADDR_INFO_IN_SESS_EST_REQ IP Multicast Addressing Info within PFCP Session Establishment Request
189 JOIN_IP_MULTICAST_INFO_IE_IN_USAGE_REP Join IP Multicast Information IE within Usage Report
190 LEAVE_IP_MULTICAST_INFO_IE_IN_USAGE_REP Leave IP Multicast Information IE within Usage Report
191 IP_MULTICAST_ADDRESS IP Multicast Address
192 SOURCE_IP_ADDRESS Source IP Address
193 PACKET_RATE_STATUS Packet Rate Status
194 CREATE_BRIDGE_INFO_FOR_TSC Create Bridge Info for TSC
195 CREATED_BRIDGE_INFO_FOR_TSC Created Bridge Info for TSC
196 DS_TT_PORT_NUMBER DS-TT Port Number
197 NW_TT_PORT_NUMBER NW-TT Port Number
198 TSN_BRIDGE_ID TSN Bridge ID
199 TSC_MGMT_INFO_IE_IN_SESS_MOD_REQ TSC Management Information IE within PFCP Session Modification Request
200 TSC_MGMT_INFO_IE_IN_SESS_MOD_RESP TSC Management Information IE within PFCP Session Modification Response
201 TSC_MGMT_INFO_IE_IN_SESS_REP_REQ TSC Management Information IE within PFCP Session Report Request
202 PORT_MGMT_INFO_CONTAINER Port Management Information Container
203 CLOCK_DRIFT_CTRL_INFO Clock Drift Control Information
204 REQUESTED_CLOCK_DRIFT_INFO Requested Clock Drift Information
205 CLOCK_DRIFT_REP Clock Drift Report
206 TSN_TIME_DOMAIN_NUMBER TSN Time Domain Number
207 TIME_OFFSET_THRESH Time Offset Threshold
208 CUMULATIVE_RATERATIO_THRESH Cumulative rateRatio Threshold
209 TIME_OFFSET_MEAS Time Offset Measurement
210 CUMULATIVE_RATERATIO_MEAS Cumulative rateRatio Measurement
211 REMOVE_SRR Remove SRR
212 CREATE_SRR Create SRR
213 UPD_SRR Update SRR
214 SESS_REP Session Report
215 SRR_ID SRR ID
216 ACCESS_AVAIL_CTRL_INFO Access Availability Control Information
217 REQUESTED_ACCESS_AVAIL_INFO Requested Access Availability Information
218 ACCESS_AVAIL_REP Access Availability Report
219 ACCESS_AVAIL_INFO Access Availability Information
220 PROVIDE_ATSSS_CTRL_INFO Provide ATSSS Control Information
221 ATSSS_CTRL_PARAMS ATSSS Control Parameters
222 MPTCP_CTRL_INFO MPTCP Control Information
223 ATSSS_LL_CTRL_INFO ATSSS-LL Control Information
224 PMF_CTRL_INFO PMF Control Information
225 MPTCP_PARAMS MPTCP Parameters
226 ATSSS_LL_PARAMS ATSSS-LL Parameters
227 PMF_PARAMS PMF Parameters
228 MPTCP_ADDRESS_INFO MPTCP Address Information
229 UE_LINK_SPECIFIC_IP_ADDRESS UE Link-Specific IP Address
230 PMF_ADDRESS_INFO PMF Address Information
231 ATSSS_LL_INFO ATSSS-LL Information
232 DATA_NETWORK_ACCESS_IDENTIFIER Data Network Access Identifier
233 UE_IP_ADDRESS_POOL_INFO UE IP address Pool Information
234 AVERAGE_PACKET_DELAY Average Packet Delay
235 MIN_PACKET_DELAY Minimum Packet Delay
236 MAX_PACKET_DELAY Maximum Packet Delay
237 QOS_REP_TRIGGER QoS Report Trigger
238 GTP_U_PATH_QOS_CTRL_INFO GTP-U Path QoS Control Information
239 GTP_U_PATH_QOS_REP_NODE_REP_REQ GTP-U Path QoS Report (PFCP Node Report Request)
240 QOS_INFO_IN_GTP_U_PATH_QOS_REP QoS Information in GTP-U Path QoS Report
241 GTP_U_PATH_IFACE_TYPE GTP-U Path Interface Type
242 QOS_MONITORING_PER_QOS_FLOW_CTRL_INFO QoS Monitoring per QoS flow Control Information
243 REQUESTED_QOS_MONITORING Requested QoS Monitoring
244 REPORTING_FREQUENCY Reporting Frequency
245 PACKET_DELAY_THRESHOLDS Packet Delay Thresholds
246 MIN_WAIT_TIME Minimum Wait Time
247 QOS_MONITORING_REP QoS Monitoring Report
248 QOS_MONITORING_MEAS QoS Monitoring Measurement
249 MT_EDT_CTRL_INFO MT-EDT Control Information
250 DL_DATA_PACKETS_SIZE DL Data Packets Size
251 QER_CTRL_INDICATIONS QER Control Indications
252 PACKET_RATE_STATUS_REP Packet Rate Status Report
253 NF_INST_ID NF Instance ID
254 ETHERNET_CONTEXT_INFO Ethernet Context Information
255 REDUNDANT_TRANSMISSION_PARAMS Redundant Transmission Parameters
256 UPDATED_PDR Updated PDR
257 S_NSSAI S-NSSAI
258 IP_VERSION IP version
259 PFCPASREQ_FLAGS PFCPASReq-Flags
260 DATA_STATUS Data Status
261 PROVIDE_RDS_CONF_INFO Provide RDS configuration information
262 RDS_CONF_INFO RDS configuration information
263 QUERY_PACKET_RATE_STATUS_IE_IN_SESS_MOD_REQ Query Packet Rate Status IE within PFCP Session Modification Request
264 PACKET_RATE_STATUS_REP_IE_IN_SESS_MOD_RESP Packet Rate Status Report IE within PFCP Session Modification Response
265 MPTCP_APPLICABLE_IND MPTCP Applicable Indication
266 BRIDGE_MGMT_INFO_CONTAINER Bridge Management Information Container
267 UE_IP_ADDRESS_USAGE_INFO UE IP Address Usage Information
268 NUMBER_OF_UE_IP_ADDRS Number of UE IP Addresses
269 VALIDITY_TIMER Validity Timer
270 REDUNDANT_TRANSMISSION_FORW_PARAMS Redundant Transmission Forwarding Parameters
271 TRANSPORT_DELAY_REPORTING Transport Delay Reporting

1
debian/control vendored
View File

@@ -12,6 +12,7 @@ Build-Depends: debhelper (>=9),
pkg-config,
python3-minimal,
libtalloc-dev,
libgtpnl-dev (>= 1.2.0),
libosmocore-dev (>= 1.6.0),
osmo-gsm-manuals-dev (>= 1.2.0)
Standards-Version: 3.9.8

View File

@@ -1,4 +1,5 @@
SUBDIRS = \
examples \
manuals \
charts \
$(NULL)

28
doc/charts/Makefile.am Normal file
View 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

View 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]
}

View 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"]
}

View 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"]
}

View 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]
}

View 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

View 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

View 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
}

View 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"]
}

View 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"]
}

View File

@@ -0,0 +1,17 @@
log stderr
logging filter all 1
logging color 1
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 set-all debug
timer pfcp x24 5000
pfcp
local-addr 127.0.0.1
gtp
dev create apn23

View File

@@ -6,4 +6,10 @@ log stderr
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
local-addr 127.0.0.1

View File

@@ -1,3 +1,5 @@
SUBDIRS = \
tlv \
pfcp \
upf \
$(NULL)

View 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-tlv/tlv_gen.c \
$(top_srcdir)/include/osmocom/tlv/tlv_gen.h
$(MAKE) -C $(top_builddir)/src/libosmo-tlv
$(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

View File

@@ -0,0 +1,77 @@
#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);

View File

@@ -0,0 +1,17 @@
#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);

View File

@@ -0,0 +1,135 @@
/* Definitions for decoded PFCP IEs, to be used by the auto-generated pfcp_ies_auto.c. */
#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];
};
};
/* 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];
};

View File

@@ -0,0 +1,143 @@
/* PFCP message encoding and decoding */
#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;
/* 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;
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_msg_tlv.h, which is generated from pfcp_msg_gen.c.
*/
union osmo_pfcp_ies 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, struct osmo_pfcp_msg *pfcp_msg);
int osmo_pfcp_msg_decode_header(struct osmo_tlv_load *tlv, struct osmo_pfcp_msg *m,
const struct msgb *msg);
int osmo_pfcp_msg_decode_tlv(struct osmo_pfcp_msg *m, struct osmo_tlv_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);
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);

View File

@@ -0,0 +1,505 @@
/* 3GPP TS 29.244: Packet Forwarding Control Protocol */
#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,
};

View File

@@ -0,0 +1,65 @@
#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);
}

View File

@@ -0,0 +1,7 @@
tlv_HEADERS = \
tlv.h \
tlv_dec_enc.h \
tlv_gen.h \
$(NULL)
tlvdir = $(includedir)/osmocom/tlv

153
include/osmocom/tlv/tlv.h Normal file
View File

@@ -0,0 +1,153 @@
/*
* (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 <limits.h>
#include <stdbool.h>
struct msgb;
struct osmo_tlv_load;
struct osmo_tlv_put;
struct osmo_tlv_tag_inst {
unsigned int tag;
bool instance_present;
unsigned int instance;
};
int osmo_tlv_tag_inst_cmp(const struct osmo_tlv_tag_inst *a, const struct osmo_tlv_tag_inst *b);
char *osmo_tlv_tag_inst_to_str_buf( asdf
/*! TL configuration for osmo_tlv_load*() and osmo_tlv_put*(). Depending on these implementations provided by the caller,
* osmo_tlv 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_tlv_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 tlv Return the T (tag) value read from src_data in tlv->tag.
* Return the L (length) value read from src_data in tlv->len.
* Return the I (instance) value read from src_data in tlv->len; ignore if there is no I.
* Return the position just after the TL in tlv->*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_tlv_load *tlv, 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_tlv_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 'tlv' arg to figure out how to memmove the message data:
* When invoked by osmo_tlv_put_tl(), dst_data == tlv->dst->tail and dst_data_avail == msgb_tailroom().
* When invoked by osmo_tlv_put_update_tl(), dst_data < tlv->dst->tail, dst_data points at the start of the
* TL section written earlier by osmo_tlv_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 tlv Backpointer to the osmo_tlv_put struct, including tlv->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_tlv_tag_inst *ti, size_t len,
struct osmo_tlv_put *tlv);
};
/*! Configuration that allows parsing an 8bit tag and 8bit length TLV. */
extern const struct osmo_tlv_cfg osmo_t8l8v_cfg;
/*! Configuration that allows parsing a 16bit tag and 16bit length TLV (see for example PFCP). */
extern const struct osmo_tlv_cfg osmo_t16l16v_cfg;
/*! State for loading a TLV structure from raw data. */
struct osmo_tlv_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_tlv_cfg *cfg;
/*! Overall message buffer being parsed. */
struct {
const uint8_t *data;
size_t len;
} src;
/*! Return value from last invocation of osmo_tlv_load_next*(): tag value of parsed IE. */
struct osmo_tlv_tag_inst ti;
/*! Return value from last invocation of osmo_tlv_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_tlv_load_next*(): Length of the IE's payload data (without tag and
* length) */
size_t len;
};
/* Start or restart the tlv from the first IE in the overall TLV data. */
static inline void osmo_tlv_load_start(struct osmo_tlv_load *tlv)
{
tlv->val = NULL;
}
int osmo_tlv_load_next(struct osmo_tlv_load *tlv);
int osmo_tlv_load_peek_tag(const struct osmo_tlv_load *tlv, struct osmo_tlv_tag_inst *ti);
int osmo_tlv_load_next_by_tag(struct osmo_tlv_load *tlv, unsigned int tag);
int osmo_tlv_load_next_by_tag_inst(struct osmo_tlv_load *tlv, const struct osmo_tlv_tag_inst *ti);
/* State for storing a TLV structure into a msgb. */
struct osmo_tlv_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_tlv_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_tlv_tag_inst last_ti;
uint8_t *last_tl;
uint8_t *last_val;
};
int osmo_tlv_put_tl(struct osmo_tlv_put *tlv, unsigned int tag, size_t len);
int osmo_tlv_put_tli(struct osmo_tlv_put *tlv, const struct osmo_tlv_tag_inst *ti, size_t len);
int osmo_tlv_put_update_tl(struct osmo_tlv_put *tlv);

View File

@@ -0,0 +1,200 @@
/* 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/tlv/tlv.h>
struct value_string;
/* User defined function to decode a single TLV value part. See struct osmo_tlv_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 tlv TLV loader, pointing at a tlv->val of tlv->len bytes.
* \return 0 on success, nonzero on error, e.g. -EINVAL if the tlv->val is invalid.
*/
typedef int (*osmo_tlv_dec_func)(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv);
/* User defined function to encode a single TLV value part. See struct osmo_tlv_coding.
* \param tlv TLV writer, pointing at a tlv->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_tlv_enc_func)(struct osmo_tlv_put *tlv, void *decoded_struct, 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_tlv_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_tlv_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_tlv_enc_to_str_func)(char *buf, size_t buflen, void *str_of);
/* Whether TLV structures nested inside the value data of an outer IE should be parsed in the same order. */
enum osmo_tlv_coding_nested_ies_ordered {
/*! When stepping into nested IEs, keep the same ordering requirement as the outer IE. */
OSMO_TLV_NESTED_IES_ORDERED_SAME = 0,
/*! Require IEs in a PDU to appear exactly in the order defined by osmo_tlv_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_TLV_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_TLV_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 tlv_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-tlv/test_tlv_gen/. */
struct osmo_tlv_coding {
/*! the IEI discriminator, and optional instance number */
struct osmo_tlv_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_tlv_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_tlv_enc_func enc_func;
/*! Means to output the decoded value to a human readable string, optional. */
osmo_tlv_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_madatory;
/*! 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 goo sub structs depending on the tag value.
*
* struct goo {
* int aaa;
* int bbb;
* };
*
* struct foo_msg {
* struct goo goo;
* struct goo other_goo;
* };
*
* struct osmo_tlv_coding goo_nested_ies[] = {
* { FOO_IEI_AAA, .memb_ofs = offsetof(struct goo, aaa), },
* { FOO_IEI_BBB, .memb_ofs = offsetof(struct goo, bbb), },
* {0}
* };
*
* struct osmo_tlv_coding foo_msg_ies[] = {
* { FOO_IEI_GOO, .memb_ofs = offsetof(struct foo_msg, goo), .nested_ies = goo_nested_ies, },
* { FOO_IEI_OTHER_GOO, .memb_ofs = offsetof(struct foo_msg, other_goo), .nested_ies = goo_nested_ies, },
* {0}
* };
*/
const struct osmo_tlv_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_tlv_cfg *nested_ies_cfg;
/*! When stepping into nested IEs, what is the ordering requirement for the nested TLV structure? */
enum osmo_tlv_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_tlv_err_cb)(void *decoded_struct, const char *file, int line, const char *fmt, ...);
int osmo_tlvs_decode(void *decoded_struct, unsigned int obj_ofs, struct osmo_tlv_load *tlv, bool tlv_ordered,
const struct osmo_tlv_coding *ie_coding,
osmo_tlv_err_cb err_cb, const struct value_string *iei_strs);
int osmo_tlvs_encode(struct osmo_tlv_put *tlv, void *decoded_struct, unsigned int obj_ofs,
const struct osmo_tlv_coding *ie_coding,
osmo_tlv_err_cb err_cb, const struct value_string *iei_strs);
int osmo_tlvs_encode_to_str_buf(char *buf, size_t buflen, const void *decoded_struct, unsigned int obj_ofs,
const struct osmo_tlv_coding *ie_coding, const struct value_string *iei_strs);
char *osmo_tlvs_encode_to_str_c(void *ctx, const void *decoded_struct, unsigned int obj_ofs,
const struct osmo_tlv_coding *ie_coding, const struct value_string *iei_strs);
static inline bool osmo_tlv_coding_end(const struct osmo_tlv_coding *iec)
{
return iec->dec_func == NULL && iec->enc_func == NULL && iec->nested_ies == NULL;
}

View File

@@ -0,0 +1,128 @@
/* Write h and c source files for TLV protocol definitions, based on very sparse TLV definitions.
* For a usage example see tests/libosmo-tlv/test_tlv_gen/. */
#pragma once
#include <stdbool.h>
struct osmo_tlv_gen_ie;
#define OSMO_TLV_GEN_NO_INSTANCE INT_MAX
/*! Modifier for Mandatory/Optional/Multiple around an osmo_tlv_gen_ie. */
struct osmo_tlv_gen_ie_o {
/*! Whether to add a bool foo_present, and to skip the coding if false. */
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 */
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 */
const struct osmo_tlv_gen_ie *ie;
};
#define OSMO_TLV_GEN_O(TLV_GEN_IE) { .optional = true, .ie = &TLV_GEN_IE }
#define OSMO_TLV_GEN_M(TLV_GEN_IE) { .ie = &(TLV_GEN_IE) }
#define OSMO_TLV_GEN_O_MULTI(MAX, TLV_GEN_IE) { .multi = MAX, .ie = &(TLV_GEN_IE) }
#define OSMO_TLV_GEN_M_MULTI(MAX, MAND_COUNT, TLV_GEN_IE) \
{ .multi = MAX, .multi_mandatory = MAND_COUNT, .ie = &(TLV_GEN_IE) }
#define OSMO_TLV_GEN_O_INST(INSTANCE, TLV_GEN_IE) { .optional = true, .instance = INSTANCE, .ie = &TLV_GEN_IE }
#define OSMO_TLV_GEN_M_INST(INSTANCE, TLV_GEN_IE) { .instance = INSTANCE, .ie = &(TLV_GEN_IE) }
/*! Define decoding and encoding of a single IE, i.e. one full TLV. */
struct osmo_tlv_gen_ie {
/*! Member name.
* The C name of the member in a decoded struct.
* Some other names, if NULL, are derived from this name.
*
* For example, simply this
*
* struct osmo_tlv_gen_ie bar {
* .name = "bar",
* };
*
* Generates an osmo_tlv_coding entry of
*
* { MYPROTO_IEI_BAR,
* .memb_ofs = offsetof(struct myproto_foo, bar),
* .dec_func = myproto_dec_bar,
* .enc_func = myproto_enc_bar,
* }
*/
const char *name;
/*! like "uint32_t".
* If NULL, auto-generate a "struct myproto_ie_foo" instead, useful only for an IE that contains nested IEs
* where the struct members can be derived from the nested_ies list.
*/
const char *decoded_type;
/*! C name of this tag value, e.g. "MYPROTO_IEI_FOO". If NULL, take "MYPROTO_IEI_"+upper(name) instead. */
const char *tag_name;
/*! Name of the dec/enc functions. "foo" -> myproto_dec_foo(), myproto_enc_foo().
* These functions need to be implemented by the caller in a myproto_ies_custom.c. */
const char *dec_enc;
/*! Name of the enc_to_str function. "foo" -> myproto_enc_to_str_foo(). optional. */
const char *to_str;
/*! List of inner IEs terminated by {0}. If non-NULL, this is a "Grouped IE" with an inner TLV structure inside
* this IE's V part. */
const struct osmo_tlv_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_tlv_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_tlv_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_tlv_gen_msg *msg_defs;
};
/*! 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_tlv_gen_msg {
const char *name;
const struct osmo_tlv_gen_ie_o *ies;
};
int osmo_tlv_gen_main(const struct osmo_tlv_gen_cfg *cfg, int argc, const char **argv);

View File

@@ -1,3 +1,9 @@
noinst_HEADERS = \
up_endpoint.h \
up_peer.h \
up_session.h \
upf.h \
upf_gtp.h \
upf_nft.h \
up_gtp_action.h \
$(NULL)

View File

@@ -0,0 +1,45 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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/lienses/>.
*
*/
#pragma once
#include <osmocom/core/linuxlist.h>
struct osmo_pfcp_msg;
struct osmo_pfcp_endpoint;
struct osmo_sockaddr;
#define UP_USE_MSG_RX "msg-rx"
#define UP_USE_MSG_TX "msg-tx"
struct up_endpoint {
struct osmo_pfcp_endpoint *pfcp_ep;
struct llist_head peers;
uint64_t next_seid_state;
uint32_t next_teid_state;
};
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_seid(struct up_endpoint *ep);
uint32_t up_endpoint_next_teid(struct up_endpoint *ep);

View File

@@ -0,0 +1,50 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/fsm.h>
#include <osmocom/upf/up_session.h>
#include <osmocom/upf/upf_gtp.h>
#include <osmocom/upf/upf_nft.h>
#define LOG_UP_GTP_ACTION(A, LEVEL, FMT, ARGS...) \
LOGP(DGTP, LEVEL, "%s: " FMT, up_gtp_action_to_str_c(OTC_SELECT, A), ##ARGS)
struct up_session;
enum up_gtp_action_kind {
UP_GTP_DROP,
UP_GTP_U_ENDECAPS,
UP_GTP_U_FORW,
};
struct up_gtp_action {
struct llist_head entry;
struct up_session *session;
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_gtp_tun_desc endecaps;
/* Forward GTP: translate from one TEID to another and forward */
struct upf_nft_forw_desc forw;
};
/* volatile loop variable to match up wanted and actually present GTP actions */
void *handle;
};
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);
int up_gtp_action_disable(struct up_gtp_action *a);
int up_gtp_action_to_str_buf(char *buf, size_t buflen, const struct up_gtp_action *a);
char *up_gtp_action_to_str_c(void *ctx, const struct up_gtp_action *a);

View File

@@ -0,0 +1,75 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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/lienses/>.
*
*/
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/use_count.h>
#include <osmocom/pfcp/pfcp_msg.h>
enum up_peer_event {
UP_PEER_EV_RX_ASSOC_SETUP_REQ,
UP_PEER_EV_RX_ASSOC_UPD_REQ,
UP_PEER_EV_RX_ASSOC_REL_REQ,
UP_PEER_EV_RX_SESSION_EST_REQ,
UP_PEER_EV_HEARTBEAT_FAILURE,
UP_PEER_EV_USE_COUNT_ZERO,
UP_PEER_EV_SESSION_TERM,
};
struct up_peer {
struct llist_head entry;
struct osmo_fsm_inst *fi;
struct up_endpoint *up_endpoint;
/* peer's remote address */
struct osmo_sockaddr remote_addr;
struct osmo_pfcp_ie_node_id remote_node_id;
uint32_t remote_recovery_timestamp;
struct osmo_pfcp_ie_up_function_features local_up_features;
struct osmo_pfcp_ie_cp_function_features peer_cp_features;
uint32_t next_seq_nr;
struct osmo_fsm_inst *heartbeat_fi;
struct osmo_use_count use_count;
struct osmo_use_count_entry use_count_buf[5];
DECLARE_HASHTABLE(sessions_by_up_seid, 6);
DECLARE_HASHTABLE(sessions_by_cp_seid, 6);
};
struct up_peer *up_peer_find_or_add(struct up_endpoint *up_ep, const struct osmo_sockaddr *remote_addr);
struct up_peer *up_peer_find(struct up_endpoint *up_ep, const struct osmo_sockaddr *remote_addr);
void up_peer_set_msg_ctx(struct up_peer *peer, struct osmo_pfcp_msg *m);
char *up_peer_remote_addr_str(struct up_peer *peer);
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);
void up_peer_free(struct up_peer *peer);

View File

@@ -0,0 +1,94 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/use_count.h>
#include <osmocom/pfcp/pfcp_msg.h>
struct osmo_fsm_inst;
struct osmo_pfcp_msg;
struct up_peer;
enum up_session_fsm_event {
UP_SESSION_EV_RX_SESSION_EST_REQ,
UP_SESSION_EV_RX_SESSION_MOD_REQ,
UP_SESSION_EV_RX_SESSION_DEL_REQ,
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;
struct up_peer *up_peer;
struct osmo_pfcp_ie_f_seid cp_f_seid;
uint64_t up_seid;
struct osmo_use_count use_count;
struct osmo_use_count_entry use_count_buf[8];
struct llist_head pdrs;
struct llist_head fars;
struct llist_head chosen_f_teids;
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,
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);
char *up_session_gtp_status(struct up_session *session);
bool up_session_is_active(struct up_session *session);
bool up_session_is_fully_active(struct up_session *session, int *active_p, int *inactive_p);
int up_session_discard(struct up_session *session);
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;
struct up_session *session;
struct osmo_pfcp_ie_create_pdr desc;
struct osmo_pfcp_ie_f_teid *local_f_teid;
struct osmo_pfcp_ie_f_teid _local_f_teid_buf;
struct far *far;
bool rx_decaps;
bool forw_encaps;
bool forw_to_core;
bool forw_from_core;
struct pdr *reverse_pdr;
bool active;
char *inactive_reason;
};
int pdr_to_str_buf(char *buf, size_t buflen, const struct pdr *pdr);
char *pdr_to_str_c(void *ctx, const struct pdr *pdr);
struct far {
struct llist_head entry;
struct up_session *session;
struct osmo_pfcp_ie_create_far desc;
bool active;
};

View File

View File

@@ -1,12 +1,77 @@
/* Global definitions for OsmoUPF */
#pragma once
#include <osmocom/core/tdef.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/select.h>
#include <osmocom/core/linuxlist.h>
struct osmo_tdef;
struct ctrl_handle;
struct upf_gtp_dev;
struct nft_ctx;
#define UPF_PFCP_LISTEN_DEFAULT "0.0.0.0"
extern struct osmo_tdef_group g_upf_tdef_groups[];
struct pfcp_vty_cfg {
char *local_addr;
uint16_t local_port;
};
struct gtp_vty_cfg_dev {
struct llist_head entry;
bool create;
char *dev_name;
char *local_addr;
};
struct gtp_vty_cfg {
struct llist_head devs;
};
struct g_upf {
struct ctrl_handle *ctrl;
struct {
struct pfcp_vty_cfg vty_cfg;
struct up_endpoint *ep;
} pfcp;
/* Tunnel encaps decaps via GTP kernel module */
struct {
struct gtp_vty_cfg vty_cfg;
struct llist_head devs;
struct mnl_socket *nl;
int32_t genl_id;
} gtp;
/* Tunnel forwarding via linux netfilter */
struct {
struct nft_ctx *nft_ctx;
const char *table_name;
int priority;
uint32_t next_id_state;
} nft;
};
extern struct g_upf *g_upf;
enum upf_log_subsys {
DREF,
DPEER,
DSESSION,
DGTP,
DNFT,
};
void g_upf_alloc(void *ctx);
void upf_vty_init();
int upf_pfcp_listen();
int upf_gtp_devs_open();
void upf_gtp_devs_close();

View File

@@ -0,0 +1,53 @@
#pragma once
#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;
bool created;
char *name;
struct {
bool enabled;
struct osmo_sockaddr local_addr;
struct osmo_fd ofd;
} gtpv0;
struct {
struct osmo_sockaddr local_addr;
struct osmo_fd ofd;
} gtpv1;
bool sgsn_mode;
uint32_t ifidx;
struct llist_head tunnels;
};
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_tun_desc_cmp(const struct upf_gtp_tun_desc *a, const struct upf_gtp_tun_desc *b);
int upf_gtp_genl_open();
void upf_gtp_genl_close();
struct upf_gtp_dev *upf_gtp_dev_create(const char *name, const char *local_addr, bool listen_for_gtpv0, bool sgsn_mode);
struct upf_gtp_dev *upf_gtp_dev_use(const char *name);
struct upf_gtp_dev *upf_gtp_dev_find_by_name(const char *name);
struct upf_gtp_dev *upf_gtp_dev_first();
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);

View File

@@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/socket.h>
struct upf_nft_forw_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;
};
int upf_nft_init();
int upf_nft_free();
int upf_nft_forward_create(struct upf_nft_forw_desc *forw);
int upf_nft_forward_delete(struct upf_nft_forw_desc *forw);

View File

@@ -1,3 +1,6 @@
SUBDIRS = \
libosmo-tlv \
libosmo-pfcp \
osmo-upf \
osmo-pfcp-tool \
$(NULL)

View 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-tlv/tlv_gen.c \
$(top_srcdir)/include/osmocom/tlv/tlv_gen.h
$(MAKE) -C $(top_builddir)/src/libosmo-tlv
$(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-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(COVERAGE_LDFLAGS) \
$(NULL)

View File

@@ -0,0 +1,448 @@
/* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <stdbool.h>
#include <stdio.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include <osmocom/tlv/tlv_gen.h>
/* O means optional, M means mandatory */
#define O OSMO_TLV_GEN_O
#define M OSMO_TLV_GEN_M
#define O_MULTI OSMO_TLV_GEN_O_MULTI
#define M_MULTI OSMO_TLV_GEN_M_MULTI
static const struct osmo_tlv_gen_ie recovery_time_stamp = {
"recovery_time_stamp",
"uint32_t",
.dec_enc = "32be",
.spec_ref = "7.4.2",
};
static const struct osmo_tlv_gen_ie node_id = {
"node_id",
.spec_ref = "8.2.38",
};
static const struct osmo_tlv_gen_ie up_function_features = {
"up_function_features",
.spec_ref = "8.2.25",
};
static const struct osmo_tlv_gen_ie cp_function_features = {
"cp_function_features",
.spec_ref = "8.2.58",
};
static const struct osmo_tlv_gen_ie cause = {
"cause",
"enum osmo_pfcp_cause",
.spec_ref = "8.2.1",
};
static const struct osmo_tlv_gen_ie offending_ie = {
"offending_ie",
.decoded_type = "enum osmo_pfcp_iei",
.spec_ref = "8.2.22",
};
static const struct osmo_tlv_gen_ie cp_f_seid = {
"cp_f_seid",
.tag_name = "f_seid",
.spec_ref = "8.2.37",
};
static const struct osmo_tlv_gen_ie up_f_seid = {
"up_f_seid",
.tag_name = "f_seid",
.spec_ref = "8.2.37",
};
static const struct osmo_tlv_gen_ie pdr_id = {
"pdr_id",
.decoded_type = "uint16_t",
.dec_enc = "16be",
.spec_ref = "8.2.36",
};
static const struct osmo_tlv_gen_ie precedence = {
"precedence",
.decoded_type = "uint32_t",
.dec_enc = "32be",
.spec_ref = "8.2.11",
};
static const struct osmo_tlv_gen_ie source_iface = {
"source_iface",
.decoded_type = "enum osmo_pfcp_source_iface",
.spec_ref = "8.2.2",
};
static const struct osmo_tlv_gen_ie source_iface_type = {
"source_iface_type",
.decoded_type = "enum osmo_pfcp_3gpp_iface_type",
.tag_name = "3gpp_iface_type",
.spec_ref = "8.2.118",
};
static const struct osmo_tlv_gen_ie local_f_teid = {
"local_f_teid",
.tag_name = "f_teid",
.spec_ref = "8.2.3",
};
static const struct osmo_tlv_gen_ie ue_ip_address = {
"ue_ip_address",
.spec_ref = "8.2.62",
};
static const struct osmo_tlv_gen_ie traffic_endpoint_id = {
"traffic_endpoint_id",
.decoded_type = "uint8_t",
.dec_enc = "8",
.spec_ref = "8.2.92",
};
static const struct osmo_tlv_gen_ie_o ies_in_pdi[] = {
M(source_iface),
O(local_f_teid),
O(ue_ip_address),
O(traffic_endpoint_id),
O(source_iface_type),
{0}
};
static const struct osmo_tlv_gen_ie pdi = {
"pdi",
.nested_ies = ies_in_pdi,
.spec_ref = "7.5.2.2-2",
};
static const struct osmo_tlv_gen_ie outer_header_removal = {
"outer_header_removal",
.spec_ref = "8.2.64",
};
static const struct osmo_tlv_gen_ie far_id = {
"far_id",
.decoded_type = "uint32_t",
.dec_enc = "32be",
.spec_ref = "8.2.74",
};
static const struct osmo_tlv_gen_ie activate_predefined_rules = {
"activate_predefined_rules",
.spec_ref = "8.2.72",
};
static const struct osmo_tlv_gen_ie_o ies_in_create_pdr[] = {
M(pdr_id),
M(precedence),
M(pdi),
O(outer_header_removal),
O(far_id),
O(activate_predefined_rules),
{0}
};
static const struct osmo_tlv_gen_ie create_pdr = {
"create_pdr",
.nested_ies = ies_in_create_pdr,
.spec_ref = "7.5.2.2",
};
static const struct osmo_tlv_gen_ie_o ies_in_created_pdr[] = {
M(pdr_id),
O(local_f_teid),
{0}
};
static const struct osmo_tlv_gen_ie created_pdr = {
"created_pdr",
.nested_ies = ies_in_created_pdr,
.spec_ref = "7.5.3.2",
};
static const struct osmo_tlv_gen_ie_o ies_in_upd_pdr[] = {
M(pdr_id),
O(outer_header_removal),
O(pdi),
O(far_id),
O(activate_predefined_rules),
{0}
};
static const struct osmo_tlv_gen_ie upd_pdr = {
"upd_pdr",
.nested_ies = ies_in_upd_pdr,
.spec_ref = "7.5.4.2",
};
static const struct osmo_tlv_gen_ie_o ies_in_updated_pdr[] = {
M(pdr_id),
O(local_f_teid),
{0}
};
static const struct osmo_tlv_gen_ie updated_pdr = {
"updated_pdr",
.nested_ies = ies_in_updated_pdr,
.spec_ref = "7.5.9.3",
};
static const struct osmo_tlv_gen_ie_o ies_in_remove_pdr[] = {
M(pdr_id),
{0}
};
static const struct osmo_tlv_gen_ie remove_pdr = {
"remove_pdr",
.nested_ies = ies_in_remove_pdr,
.spec_ref = "7.5.4.6",
};
static const struct osmo_tlv_gen_ie apply_action = {
"apply_action",
.decoded_type = "struct osmo_pfcp_ie_apply_action",
.spec_ref = "8.2.26",
};
static const struct osmo_tlv_gen_ie destination_iface = {
"destination_iface",
.decoded_type = "enum osmo_pfcp_dest_iface",
.dec_enc = "dest_iface",
.spec_ref = "8.2.24",
};
static const struct osmo_tlv_gen_ie destination_iface_type = {
"destination_iface_type",
.decoded_type = "enum osmo_pfcp_3gpp_iface_type",
.tag_name = "3gpp_iface_type",
.spec_ref = "8.2.118",
};
static const struct osmo_tlv_gen_ie network_inst = {
"network_inst",
.spec_ref = "8.2.4",
};
static const struct osmo_tlv_gen_ie outer_header_creation = {
"outer_header_creation",
.spec_ref = "8.2.56",
};
static const struct osmo_tlv_gen_ie linked_te_id = {
"linked_te_id",
.tag_name = "traffic_endpoint_id",
.decoded_type = "uint8_t",
.dec_enc = "8",
.spec_ref = "8.2.92",
};
static const struct osmo_tlv_gen_ie_o ies_in_forw_params[] = {
M(destination_iface),
O(network_inst),
O(outer_header_creation),
O(linked_te_id),
O(destination_iface_type),
{0}
};
static const struct osmo_tlv_gen_ie forw_params = {
"forw_params",
.nested_ies = ies_in_forw_params,
.spec_ref = "7.5.2.3-2",
};
static const struct osmo_tlv_gen_ie_o ies_in_upd_forw_params[] = {
O(destination_iface),
O(network_inst),
O(outer_header_creation),
O(linked_te_id),
O(destination_iface_type),
{0}
};
static const struct osmo_tlv_gen_ie upd_forw_params = {
"upd_forw_params",
.nested_ies = ies_in_upd_forw_params,
.spec_ref = "7.5.4.3-2",
};
static const struct osmo_tlv_gen_ie_o ies_in_create_far[] = {
M(far_id),
M(apply_action),
O(forw_params),
{0}
};
static const struct osmo_tlv_gen_ie create_far = {
"create_far",
.nested_ies = ies_in_create_far,
.spec_ref = "7.5.2.3",
};
static const struct osmo_tlv_gen_ie_o ies_in_remove_far[] = {
M(far_id),
{0}
};
static const struct osmo_tlv_gen_ie remove_far = {
"remove_far",
.nested_ies = ies_in_remove_far,
.spec_ref = "7.5.4.6",
};
static const struct osmo_tlv_gen_ie_o ies_in_upd_far[] = {
M(far_id),
O(apply_action),
O(upd_forw_params),
{0}
};
static const struct osmo_tlv_gen_ie upd_far = {
"upd_far",
.nested_ies = ies_in_upd_far,
.spec_ref = "7.5.4.3",
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_heartbeat_req[] = {
M(recovery_time_stamp),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_heartbeat_resp[] = {
M(recovery_time_stamp),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_setup_req[] = {
M(node_id),
M(recovery_time_stamp),
O(up_function_features),
O(cp_function_features),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_setup_resp[] = {
M(node_id),
M(cause),
M(recovery_time_stamp),
O(up_function_features),
O(cp_function_features),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_release_req[] = {
M(node_id),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_release_resp[] = {
M(node_id),
M(cause),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_session_est_req[] = {
M(node_id),
O(cp_f_seid),
M_MULTI(32, 1, create_pdr),
M_MULTI(32, 1, create_far),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_session_est_resp[] = {
M(node_id),
M(cause),
O(offending_ie),
O(up_f_seid),
M_MULTI(32, 1, created_pdr),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_session_mod_req[] = {
O(cp_f_seid),
O_MULTI(32, remove_pdr),
O_MULTI(32, remove_far),
O_MULTI(32, create_pdr),
O_MULTI(32, create_far),
O_MULTI(32, upd_pdr),
O_MULTI(32, upd_far),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_session_mod_resp[] = {
M(cause),
O(offending_ie),
O_MULTI(32, created_pdr),
O_MULTI(32, updated_pdr),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_session_del_req[] = {
/* no IEs */
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_msg_session_del_resp[] = {
M(cause),
{0}
};
static const struct osmo_tlv_gen_msg pfcp_msg_defs[] = {
{ "heartbeat_req", ies_in_msg_heartbeat_req },
{ "heartbeat_resp", ies_in_msg_heartbeat_resp },
{ "assoc_setup_req", ies_in_msg_assoc_setup_req },
{ "assoc_setup_resp", ies_in_msg_assoc_setup_resp },
{ "assoc_release_req", ies_in_msg_assoc_release_req },
{ "assoc_release_resp", ies_in_msg_assoc_release_resp },
{ "session_est_req", ies_in_msg_session_est_req },
{ "session_est_resp", ies_in_msg_session_est_resp },
{ "session_mod_req", ies_in_msg_session_mod_req },
{ "session_mod_resp", ies_in_msg_session_mod_resp },
{ "session_del_req", ies_in_msg_session_del_req },
{ "session_del_resp", ies_in_msg_session_del_resp },
{0}
};
int main(int argc, const char **argv)
{
struct osmo_tlv_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,
};
return osmo_tlv_gen_main(&cfg, argc, argv);
}

View File

@@ -0,0 +1,413 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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/lienses/>.
*
*/
#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"
},
{0}
};
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_tlv_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

View File

@@ -0,0 +1,158 @@
#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),
{0}
};
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);
}

View File

@@ -0,0 +1,729 @@
/* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <errno.h>
#include <osmocom/tlv/tlv.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((struct osmo_pfcp_msg*)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_tlv_load *tlv. Usage:
* ENSURE_LENGTH_IS(> 1);
*/
#define ENSURE_LENGTH_IS(CONDITION) \
if (!(tlv->len CONDITION)) \
RETURN_ERROR(-EINVAL, "IE has length = %zu, expected length " #CONDITION, tlv->len);
/* Assumes presence of local variable osmo_tlv_load *tlv. Usage:
* const uint8_t *pos = tlv->val;
* ENSURE_REMAINING_LENGTH_IS("first part", pos, >= 23);
* <parse first part>
* pos += 23;
* ENSURE_REMAINING_LENGTH_IS("very long part", pos, >= 235);
* <parse very long part>
* pos += 235;
*/
#define ENSURE_REMAINING_LENGTH_IS(NAME, POS, CONDITION) \
if (!((tlv->len - ((POS) - tlv->val)) CONDITION)) \
RETURN_ERROR(-EINVAL, \
"at value octet %d: %zu octets remaining, but " #NAME " requires length " #CONDITION, \
(int)((POS) - tlv->val), \
tlv->len - ((POS) - tlv->val));
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_tlv_load *tlv)
{
enum osmo_pfcp_cause *cause = decode_to;
ENSURE_LENGTH_IS(== 1);
*cause = *tlv->val;
return 0;
}
int osmo_pfcp_enc_cause(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
const enum osmo_pfcp_cause *cause = encode_from;
msgb_put_u8(tlv->dst, *cause);
return 0;
}
int osmo_pfcp_dec_offending_ie(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
enum osmo_pfcp_iei *offending_ie = decode_to;
ENSURE_LENGTH_IS(== 2);
*offending_ie = osmo_load16be(tlv->val);
return 0;
}
int osmo_pfcp_enc_offending_ie(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
const enum osmo_pfcp_iei *offending_ie = encode_from;
msgb_put_u16(tlv->dst, *offending_ie);
return 0;
}
int osmo_pfcp_dec_8(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
uint8_t *u8 = decode_to;
ENSURE_LENGTH_IS(>= 1);
*u8 = tlv->val[0];
return 0;
}
int osmo_pfcp_enc_8(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
uint8_t *u8 = encode_from;
msgb_put_u8(tlv->dst, *u8);
return 0;
}
int osmo_pfcp_dec_16be(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
uint16_t *u16 = decode_to;
ENSURE_LENGTH_IS(>= 2);
*u16 = osmo_load16be(tlv->val);
return 0;
}
int osmo_pfcp_enc_16be(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
uint16_t *u16 = encode_from;
msgb_put_u16(tlv->dst, *u16);
return 0;
}
int osmo_pfcp_dec_32be(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
uint32_t *u32 = decode_to;
ENSURE_LENGTH_IS(>= 4);
*u32 = osmo_load32be(tlv->val);
return 0;
}
int osmo_pfcp_enc_32be(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
uint32_t *u32 = encode_from;
msgb_put_u32(tlv->dst, *u32);
return 0;
}
int osmo_pfcp_dec_3gpp_iface_type(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = decode_to;
ENSURE_LENGTH_IS(>= 1);
*_3gpp_iface_type = tlv->val[0] & 0x3f;
return 0;
}
int osmo_pfcp_enc_3gpp_iface_type(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_source_iface(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
enum osmo_pfcp_source_iface *source_interface = decode_to;
ENSURE_LENGTH_IS(>= 1);
*source_interface = tlv->val[0] & 0xf;
return 0;
}
int osmo_pfcp_enc_source_iface(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
enum osmo_pfcp_source_iface *source_interface = encode_from;
msgb_put_u8(tlv->dst, (uint8_t)(*source_interface) & 0xf);
return 0;
}
int osmo_pfcp_dec_dest_iface(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
enum osmo_pfcp_dest_iface *dest_interface = decode_to;
ENSURE_LENGTH_IS(>= 1);
*dest_interface = tlv->val[0] & 0xf;
return 0;
}
int osmo_pfcp_enc_dest_iface(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
enum osmo_pfcp_dest_iface *dest_interface = encode_from;
msgb_put_u8(tlv->dst, (uint8_t)(*dest_interface) & 0xf);
return 0;
}
int osmo_pfcp_dec_node_id(void *decoded_struct, void *decode_to, const struct osmo_tlv_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(>= 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(node_id->fqdn, ip, ip_len);
break;
default:
RETURN_ERROR(-EINVAL, "Invalid Node ID Type: %d", node_id->type);
}
return 0;
}
int osmo_pfcp_enc_node_id(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
unsigned int l;
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 = strlen(node_id->fqdn);
/* Copy without trailing nul */
strncpy((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_dec_up_function_features(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct osmo_pfcp_ie_up_function_features *up_function_features = decode_to;
ENSURE_LENGTH_IS(>= 6);
memcpy(up_function_features->bits, tlv->val, 6);
return 0;
}
int osmo_pfcp_enc_up_function_features(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_cp_function_features(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct osmo_pfcp_ie_cp_function_features *cp_function_features = decode_to;
ENSURE_LENGTH_IS(>= 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_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_f_seid(void *decoded_struct, void *decode_to, const struct osmo_tlv_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(>= 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_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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;
}
int osmo_pfcp_dec_f_teid(void *decoded_struct, void *decode_to, const struct osmo_tlv_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){0};
pos = tlv->val;
ENSURE_REMAINING_LENGTH_IS("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("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("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("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("CHOOSE ID", pos, >= 1);
f_teid->choose.choose_id = *pos;
pos++;
}
}
return 0;
}
int osmo_pfcp_enc_f_teid(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_apply_action(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct osmo_pfcp_ie_apply_action *apply_action = decode_to;
ENSURE_LENGTH_IS(>= 1);
*apply_action = (struct osmo_pfcp_ie_apply_action){0};
memcpy(apply_action->bits, tlv->val, OSMO_MIN(tlv->len, sizeof(apply_action->bits)));
return 0;
}
int osmo_pfcp_enc_apply_action(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_network_inst(void *decoded_struct, void *decode_to, const struct osmo_tlv_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_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_outer_header_creation(void *decoded_struct, void *decode_to, const struct osmo_tlv_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){0};
ENSURE_LENGTH_IS(>= 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("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("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("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("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("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("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_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_activate_predefined_rules(void *decoded_struct, void *decode_to, const struct osmo_tlv_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_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_outer_header_removal(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = decode_to;
ENSURE_LENGTH_IS(>= 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_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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_dec_ue_ip_address(void *decoded_struct, void *decode_to, const struct osmo_tlv_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("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("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("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("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("IPv6 prefix length", pos, >= 1);
ue_ip_address->ipv6_prefix_length = *pos;
pos++;
}
return 0;
}
int osmo_pfcp_enc_ue_ip_address(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
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;
}

603
src/libosmo-pfcp/pfcp_msg.c Normal file
View File

@@ -0,0 +1,603 @@
/*
* (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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#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/tlv/tlv_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;
}
}
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 };
bool first = true;
/* nul terminate in case no bits are ONE */
*sb.buf = '\0';
for (; bit_strs->str; bit_strs++) {
if (osmo_pfcp_bits_get(bits, bit_strs->value)) {
OSMO_STRBUF_PRINTF(sb, "%s%s", first ? "" : " ", bit_strs->str);
first = false;
}
}
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_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);
if (ft->fixed.ip_addr.v4_present) {
OSMO_STRBUF_PRINTF(sb, " ");
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &ft->fixed.ip_addr.v4);
}
if (ft->fixed.ip_addr.v6_present) {
OSMO_STRBUF_PRINTF(sb, " ");
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &ft->fixed.ip_addr.v6);
}
}
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)
}
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_tlv_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_tlv_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_tlv_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_tlv_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_tlv_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 *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 *)decoded_struct, LOGL_ERROR, file, line, "%s", errmsg);
}
}
int osmo_pfcp_msg_decode_tlv(struct osmo_pfcp_msg *m, struct osmo_tlv_load *tlv)
{
return osmo_pfcp_ies_decode(&m->ies, tlv, false, m->h.message_type, osmo_pfcp_msg_err_cb, osmo_pfcp_iei_strs);
}
static int osmo_pfcp_msg_encode_tlv(struct msgb *msg, struct osmo_pfcp_msg *m)
{
struct osmo_tlv_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, 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, 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

View File

@@ -0,0 +1,468 @@
/*
* (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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#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 (OSMO_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, "OSMO_PFCPSMReq-Flags" },
{ OSMO_PFCP_IEI_PFCPSRRSP_FLAGS, "OSMO_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, "OSMO_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, "OSMO_PFCPSRReq-Flags" },
{ OSMO_PFCP_IEI_PFCPAUREQ_FLAGS, "OSMO_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, "OSMO_PFCP Session Retention Information (within OSMO_PFCP Association Setup Request)" },
{ OSMO_PFCP_IEI_PFCPASRSP_FLAGS, "OSMO_PFCPASRsp-Flags" },
{ OSMO_PFCP_IEI_CP_ENTITY_IP_ADDRESS, "CP OSMO_PFCP Entity IP Address" },
{ OSMO_PFCP_IEI_PFCPSEREQ_FLAGS, "OSMO_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 OSMO_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 OSMO_PFCP Session Modification Request" },
{ OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_MOD_RESP, "TSC Management Information IE within OSMO_PFCP Session Modification Response" },
{ OSMO_PFCP_IEI_TSC_MGMT_INFO_IE_IN_SESS_REP_REQ, "TSC Management Information IE within OSMO_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 (OSMO_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, "OSMO_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 OSMO_PFCP Session Modification Request" },
{ OSMO_PFCP_IEI_PACKET_RATE_STATUS_REP_IE_IN_SESS_MOD_RESP, "Packet Rate Status Report IE within OSMO_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 OSMO_PFCP Association" },
{ OSMO_PFCP_CAUSE_RULE_CREATION_MOD_FAILURE, "Rule creation/modification Failure" },
{ OSMO_PFCP_CAUSE_PFCP_ENTITY_IN_CONGESTION, "OSMO_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" },
{0}
};
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" },
{0}
};
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" },
{0}
};
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" },
{0}
};
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" },
{0}
};
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" },
{0}
};
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" },
{0}
};

View 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-tlv.a \
$(NULL)
libosmo_tlv_a_SOURCES = \
tlv.c \
tlv_dec_enc.c \
tlv_gen.c \
$(NULL)

323
src/libosmo-tlv/tlv.c Normal file
View File

@@ -0,0 +1,323 @@
/*
* (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/tlv/tlv.h>
int osmo_tlv_tag_inst_cmp(const struct osmo_tlv_tag_inst *a, const struct osmo_tlv_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;
}
static int next_tl_valid(const struct osmo_tlv_load *tlv, 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 (!tlv->val)
ie_start = tlv->src.data;
else
ie_start = tlv->val + tlv->len;
/* Sanity */
if (ie_start < tlv->src.data || ie_start > tlv->src.data + tlv->src.len)
return -ENOSPC;
buflen_left = tlv->src.len - (ie_start - tlv->src.data);
/* Too short for parsing an IE? Check also against integer overflow. */
if (buflen_left && ((buflen_left < tlv->cfg->tl_min_size) || (buflen_left > tlv->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 tlv parameter.
* When tlv->val is NULL, return the first IE in the data buffer.
* Otherwise assume that tlv points at a valid IE in the data structure, and return the subsequent IE.
*
* Usage example:
*
* struct osmo_tlv tlv = {
* .cfg = osmo_t16l16v_cfg,
* .src = { .data = msgb_l3(msg), .len = msgb_l3len(msg) },
* };
* for (;;) {
* if (osmo_tlv_next(&tlv)) {
* printf("Error\n");
* break;
* }
* if (!tlv.val) {
* printf("End\n");
* break;
* }
* printf("Tag %u: %zu octets: %s\n", tlv.tag, tlv.len, osmo_hexdump(tlv.val, tlv.len));
* }
*
* \param[inout] tlv Buffer to return the IE data, and state for TLV parsing position. tlv->msg should indicate the
* overall message buffer. The other tlv 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 tlv->tag, tlv->len and tlv->val;
* tlv->val == NULL if no more IEs remain in the buffer.
*/
int osmo_tlv_load_next(struct osmo_tlv_load *tlv)
{
const uint8_t *ie_start;
const uint8_t *ie_end;
size_t buflen_left;
int rc;
rc = next_tl_valid(tlv, &ie_start, &buflen_left);
if (rc)
return rc;
/* No more IEs? */
if (!buflen_left) {
tlv->val = NULL;
return 0;
}
/* Locate next IE */
OSMO_ASSERT(tlv->cfg->load_tl);
tlv->ti = (struct osmo_tlv_tag_inst){0};
rc = tlv->cfg->load_tl(tlv, ie_start, buflen_left);
if (rc)
return rc;
/* Sanity */
ie_end = tlv->val + tlv->len;
if (ie_end < tlv->src.data || ie_end > tlv->src.data + tlv->src.len)
return -EBADMSG;
return 0;
}
/* Return the tag of the IE that osmo_tlv_next() would yield, do not change the tlv state.
*
* \param[in] tlv 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_TLV_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_tlv_load_peek_tag(const struct osmo_tlv_load *tlv, struct osmo_tlv_tag_inst *ti)
{
const uint8_t *ie_start;
size_t buflen_left;
int rc;
/* Guard against modification by load_tl(). */
struct osmo_tlv_load mtlv = *tlv;
mtlv.ti = (struct osmo_tlv_tag_inst){0};
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 = tlv->cfg->load_tl(&mtlv, ie_start, buflen_left);
if (rc)
return -EBADMSG;
if (ti)
*ti = mtlv.ti;
return 0;
}
/* Same as osmo_tlv_load_next(), but skip any IEs until the given tag is reached. Change the tlv state only when success
* is returned.
* \param[out] tlv 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_TLV_NO_INSTANCE.
* \return 0 when the tag is found. Return -ENOENT when no such tag follows and keep the tlv unchanged. */
int osmo_tlv_load_next_by_tag(struct osmo_tlv_load *tlv, unsigned int tag)
{
struct osmo_tlv_tag_inst ti = { .tag = tag };
return osmo_tlv_load_next_by_tag_inst(tlv, &ti);
}
int osmo_tlv_load_next_by_tag_inst(struct osmo_tlv_load *tlv, const struct osmo_tlv_tag_inst *ti)
{
struct osmo_tlv_load work = *tlv;
for (;;) {
int rc = osmo_tlv_load_next(&work);
if (rc)
return rc;
if (!work.val)
return -ENOENT;
if (!osmo_tlv_tag_inst_cmp(&work.ti, ti)) {
*tlv = work;
return 0;
}
}
}
/* Put tag header and length at the end of the msgb, according to tlv->cfg->store_tl().
* If the length is not known yet, it can be passed as 0 at first, and osmo_tlv_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_tlv_put tlv = {
* .cfg = osmo_t16l16v_cfg,
* .dst = msg,
* }
*
* osmo_tlv_put_tl(tlv, 23, 0); // tag 23, length 0 = not known yet
*
* msgb_put(msg, 42);
* ...
* msgb_put(msg, 42);
* ...
* msgb_put(msg, 42);
*
* osmo_tlv_put_update_tl(tlv);
*
* Return 0 on success, -EINVAL if the tag value is invalid, -EMSGSIZE if len is too large.
*/
int osmo_tlv_put_tl(struct osmo_tlv_put *tlv, unsigned int tag, size_t len)
{
struct osmo_tlv_tag_inst ti = { .tag = tag };
return osmo_tlv_put_tli(tlv, &ti, len);
}
/* Put tag header, instance value and length at the end of the msgb, according to tlv->cfg->store_tl().
* This is the same as osmo_tlv_put_tl(), only osmo_tlv_put_tl() passes instance = 0.
*/
int osmo_tlv_put_tli(struct osmo_tlv_put *tlv, const struct osmo_tlv_tag_inst *ti, size_t len)
{
int rc;
uint8_t *last_tl;
OSMO_ASSERT(tlv->cfg->store_tl);
last_tl = tlv->dst->tail;
rc = tlv->cfg->store_tl(tlv->dst->tail, msgb_tailroom(tlv->dst), ti, len, tlv);
if (rc < 0)
return rc;
if (rc > 0)
msgb_put(tlv->dst, rc);
tlv->last_ti = *ti;
tlv->last_tl = last_tl;
tlv->last_val = tlv->dst->tail;
return 0;
}
/* Update the length of the last put IE header (last call to osmo_tlv_put_tl()) to match with the current
* tlv->dst->tail.
* Return 0 on success, -EMSGSIZE if the amount of data written since osmo_tlv_put_tl() is too large.
*/
int osmo_tlv_put_update_tl(struct osmo_tlv_put *tlv)
{
size_t len = tlv->dst->tail - tlv->last_val;
int rc = tlv->cfg->store_tl(tlv->last_tl, tlv->last_val - tlv->last_tl, &tlv->last_ti, len, tlv);
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. */
tlv->last_val = tlv->last_tl + rc;
return 0;
}
static int t8l8v_load_tl(struct osmo_tlv_load *tlv, const uint8_t *src_data, size_t src_data_len)
{
/* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 2. */
tlv->ti.tag = src_data[0];
tlv->len = src_data[1];
tlv->val = src_data + 2;
return 0;
}
static int t8l8v_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_tlv_tag_inst *ti, size_t len,
struct osmo_tlv_put *tlv)
{
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_tlv_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_tlv_load *tlv, const uint8_t *src_data, size_t src_data_len)
{
/* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 4. */
tlv->ti.tag = osmo_load16be(src_data);
tlv->len = osmo_load16be(src_data + 2);
tlv->val = src_data + 4;
return 0;
}
static int t16l16v_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_tlv_tag_inst *ti, size_t len,
struct osmo_tlv_put *tlv)
{
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_tlv_cfg osmo_t16l16v_cfg = {
.tl_min_size = 4,
.load_tl = t16l16v_load_tl,
.store_tl = t16l16v_store_tl,
};

View File

@@ -0,0 +1,525 @@
/*
* (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/tlv/tlv_dec_enc.h>
/* Reverse offsetof(): return the address of the struct member for a given osmo_tlv_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(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(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 '{0}' 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 occurence 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] tlv TLV data to parse, as given in tlv->msg.*. Must be ready for osmo_tlv_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] 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_tlvs_decode_unordered(void *decoded_struct, unsigned int obj_ofs, struct osmo_tlv_load *tlv,
const struct osmo_tlv_coding *ie_coding,
osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)
{
void *obj = MEMB(decoded_struct, obj_ofs);
const struct osmo_tlv_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] = {0};
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, tlv->ti, \
"Too many IE definitions for decoding an unordered TLV structure"); \
seen_p = &seen_ie_coding_entries[ie_coding_idx]; \
} while (0)
osmo_tlv_load_start(tlv);
/* 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_tlv_load_next(tlv);
if (rc)
RETURN_ERROR(rc, tlv->ti, "Decoding IEs failed on or after this tag");
if (!tlv->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_tlv_coding_end(iec) && osmo_tlv_tag_inst_cmp(&iec->ti, &tlv->ti);
iec++);
/* No such IE coding found. */
if (osmo_tlv_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_tlv_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, tlv->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_tlv_load inner_tlv = {
.cfg = iec->nested_ies_cfg ? : tlv->cfg,
.src = {
.data = tlv->val,
.len = tlv->len,
}
};
bool ordered;
switch (iec->nested_ies_ordered) {
case OSMO_TLV_NESTED_IES_ORDERED:
ordered = true;
break;
case OSMO_TLV_NESTED_IES_ORDERED_SAME:
case OSMO_TLV_NESTED_IES_UNORDERED:
ordered = false;
break;
default:
OSMO_ASSERT(0);
}
rc = osmo_tlvs_decode(decoded_struct, obj_ofs + memb_ofs, &inner_tlv, ordered, iec->nested_ies,
err_cb, iei_strs);
if (rc)
RETURN_ERROR(rc, tlv->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, tlv->ti, "IE definition lacks a dec_func()");
rc = iec->dec_func(decoded_struct, MEMB(obj, memb_ofs), tlv);
if (rc)
RETURN_ERROR(rc, tlv->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_tlv_msg.ies.*.
* For example, multi_count_p points at osmo_tlv_msg_session_est_req.create_pdr_count,
* and memb_ofs points at osmo_tlv_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_tlv_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_madatory)
RETURN_ERROR(-EINVAL, iec->ti, "%u instances of this IE are mandatory, got %u",
iec->count_madatory, *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 '{0}' 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 occurence 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] tlv TLV data to parse, as given in tlv->msg.*. Must be ready for osmo_tlv_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] 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_tlvs_decode_ordered(void *decoded_struct, unsigned int obj_ofs, struct osmo_tlv_load *tlv,
const struct osmo_tlv_coding *ie_coding,
osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)
{
void *obj = MEMB(decoded_struct, obj_ofs);
osmo_tlv_load_start(tlv);
for (; !osmo_tlv_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_tlv_tag_inst peek_ti;
rc = osmo_tlv_load_next_by_tag_inst(tlv, &ie_coding->ti);
switch (rc) {
case 0:
break;
case -ENOENT:
if (!presence_flag && (!multi_count || *multi_count < ie_coding->count_madatory))
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_tlv_load inner_tlv = {
.cfg = ie_coding->nested_ies_cfg ? : tlv->cfg,
.src = {
.data = tlv->val,
.len = tlv->len,
}
};
bool ordered;
switch (ie_coding->nested_ies_ordered) {
case OSMO_TLV_NESTED_IES_ORDERED_SAME:
case OSMO_TLV_NESTED_IES_ORDERED:
ordered = true;
break;
case OSMO_TLV_NESTED_IES_UNORDERED:
ordered = false;
break;
default:
OSMO_ASSERT(0);
}
rc = osmo_tlvs_decode(decoded_struct, obj_ofs + memb_ofs, &inner_tlv, ordered,
ie_coding->nested_ies, err_cb, 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), tlv);
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_tlv_load_peek_tag(tlv, &peek_ti)
|| osmo_tlv_tag_inst_cmp(&peek_ti, &tlv->ti)) {
/* Next tag is a different IE, end the repetition. */
break;
}
/* continue, parsing the next repetition of this tag. */
rc = osmo_tlv_load_next(tlv);
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 '{0}' 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] tlv TLV data to parse, as given in tlv->msg.*. Must be ready for osmo_tlv_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] 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_tlvs_decode(void *decoded_struct, unsigned int obj_ofs, struct osmo_tlv_load *tlv, bool tlv_ordered,
const struct osmo_tlv_coding *ie_coding,
osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)
{
if (!ie_coding)
return -ENOTSUP;
if (tlv_ordered)
return osmo_tlvs_decode_ordered(decoded_struct, obj_ofs, tlv, ie_coding, err_cb, iei_strs);
else
return osmo_tlvs_decode_unordered(decoded_struct, obj_ofs, tlv, ie_coding, err_cb, 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 '{0}' entry.
* The IEs will be encoded in the order they appear in ie_coding.
* \param[out] tlv Write data using this TLV definition to tlv->dst.
* \param[in] decoded_struct C struct data to encode.
* \param[in] obj_ofs Nesting offset, pass as 0.
* \param[in] ie_coding A {0} 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] 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_tlvs_encode(struct osmo_tlv_put *tlv, void *decoded_struct, unsigned int obj_ofs,
const struct osmo_tlv_coding *ie_coding,
osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)
{
void *obj = MEMB(decoded_struct, obj_ofs);
if (!ie_coding)
return -ENOTSUP;
for (; !osmo_tlv_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_tlv_put_tli(tlv, &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_tlv_put nested_tlv = {
.cfg = ie_coding->nested_ies_cfg ? : tlv->cfg,
.dst = tlv->dst,
};
rc = osmo_tlvs_encode(&nested_tlv, decoded_struct, obj_ofs + memb_ofs,
ie_coding->nested_ies, err_cb, 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(tlv, decoded_struct, MEMB(obj, memb_ofs));
if (rc)
RETURN_ERROR(rc, ie_coding->ti, "Error while encoding this IE");
}
osmo_tlv_put_update_tl(tlv);
}
}
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 '{0}' 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 {0} 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_tlvs_encode_to_str_buf(char *buf, size_t buflen, const void *decoded_struct, unsigned int obj_ofs,
const struct osmo_tlv_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_tlv_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_tlvs_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_tlvs_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 {0} 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_tlvs_encode_to_str_c(void *ctx, const void *decoded_struct, unsigned int obj_ofs,
const struct osmo_tlv_coding *ie_coding, const struct value_string *iei_strs)
{
OSMO_NAME_C_IMPL(ctx, 256, "ERROR", osmo_tlvs_encode_to_str_buf, decoded_struct, obj_ofs, ie_coding, iei_strs)
}

393
src/libosmo-tlv/tlv_gen.c Normal file
View File

@@ -0,0 +1,393 @@
/* Write h and c source files for TLV protocol definitions, based on very sparse TLV definitions.
* For a usage example see tests/libosmo-tlv/test_tlv_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 Affero 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/tlv/tlv_gen.h>
static const struct osmo_tlv_gen_cfg *g_cfg = NULL;
/* Helps avoid redundant defintions of the same type. */
struct seen_entry {
struct llist_head entry;
char str[256];
};
static LLIST_HEAD(seen_list);
static bool seen(const char *str)
{
struct seen_entry *s;
llist_for_each_entry(s, &seen_list, entry) {
if (!strcmp(s->str, str))
return true;
}
s = talloc_zero(NULL, struct seen_entry);
OSMO_STRLCPY_ARRAY(s->str, str);
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_tlv_gen_ie *ie)
{
static char b[255];
if (ie->decoded_type)
return ie->decoded_type;
/* "struct foo_ie_" + "bar" = struct foo_ie_bar*/
snprintf(b, sizeof(b), "%s%s", g_cfg->decoded_type_prefix, ie->tag_name ? : ie->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_tlv_gen_ie_o ies[])
{
for (; ies->ie; ies++) {
const struct osmo_tlv_gen_ie *ie = ies->ie;
if (ies->optional)
printf("\tbool %s_present;\n", ie->name);
printf("\t%s %s", decoded_type(ie), ie->name);
if (ies->multi) {
printf("[%u];\n", ies->multi);
printf("\tunsigned int %s_count", ie->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_tlv_gen_ie_o ies[])
{
if (!ies)
return;
for (; ies->ie; ies++) {
const struct osmo_tlv_gen_ie *ie = ies->ie;
if (!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)))
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));
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_tlv_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_tlv_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/tlv/tlv_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_tlv_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_tlv_load *tlv, bool tlv_ordered,\n"
" %s message_type, osmo_tlv_err_cb err_cb, 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_tlv_put *tlv, union %s_ies *src,\n"
" %s message_type, osmo_tlv_err_cb err_cb, 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_tlv_gen_ie_o *ies)
{
for (; ies->ie; ies++) {
const struct osmo_tlv_gen_ie *ie = ies->ie;
const char *dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie->name);
if (ie->nested_ies) {
write_extern_dec_enc(ie->nested_ies);
continue;
}
if (seen(dec_enc))
continue;
printf("extern int %s_dec_%s(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv);\n",
g_cfg->proto_name, dec_enc);
printf("extern int %s_enc_%s(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from);\n",
g_cfg->proto_name, dec_enc);
if (ie->to_str)
printf("extern int %s_enc_to_str_%s(char *buf, size_t buflen, void *encode_from);\n",
g_cfg->proto_name, ie->to_str);
}
}
/* For a nested IE, write the struct osmo_tlv_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_tlv_gen_ie_o *ies, const char *obj_type, const char *substruct)
{
#define printi(FMT, ARGS...) printf("%s" FMT, indent, ##ARGS)
const struct osmo_tlv_gen_ie_o *ie_o;
for (ie_o = ies; ie_o->ie; ie_o++) {
const struct osmo_tlv_gen_ie *ie = ie_o->ie;
const char *tag_name = ie->tag_name ? : ie->name;
printi("{ { %s%s", g_cfg->tag_prefix, osmo_str_toupper(tag_name));
if (ie_o->instance)
printi(", true, %s", ie_o->instance);
printi(" },\n");
printi(" .memb_ofs = offsetof(%s, %s%s),\n", obj_type, substruct, ie->name);
if (ie->nested_ies) {
printi(" .nested_ies = ies_in_%s,\n", ie->name);
} else {
const char *dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie->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 (ie->to_str)
printi(" .enc_to_str_func = %s_enc_to_str_%s,\n", g_cfg->proto_name, ie->to_str);
}
if (ie_o->multi) {
printi(" .memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(%s, %s%s),\n",
obj_type, substruct, ie->name);
printi(" .has_count = true, .count_max = %u,\n", ie_o->multi);
printi(" .count_madatory = %u,\n", ie_o->multi_mandatory);
printi(" .count_ofs = offsetof(%s, %s%s_count),\n", obj_type, substruct, ie->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->name);
}
if (ie_o->instance) {
printi(" .has_instance = true,\n");
printi(" .instance = %s,\n", ie_o->instance);
}
printi("},\n");
}
}
/* For a nested IE, write the struct osmo_tlv_coding array of the inner IEs.
* static const struct osmo_tlv_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_tlv_gen_ie_o *ies)
{
const char *indent = "\t";
for (; ies->ie; ies++) {
const struct osmo_tlv_gen_ie *ie = ies->ie;
if (!ie->nested_ies)
continue;
write_nested_ies_array(ie->nested_ies);
if (seen(ie->name))
continue;
printf("\nstatic const struct osmo_tlv_coding ies_in_%s[] = {\n", ie->name);
write_ies_array(indent, ie->nested_ies, decoded_type(ie), "");
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_tlv_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/tlv/tlv.h>\n");
printf("#include <osmocom/tlv/tlv_dec_enc.h>\n");
printf("#include <osmocom/tlv/tlv_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_tlv_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_tlv_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_tlv_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_tlv_load *tlv, bool tlv_ordered,\n"
" %s message_type,\n"
" osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)\n"
"{\n"
" return osmo_tlvs_decode(dst, 0, tlv, tlv_ordered, %s_get_msg_coding(message_type), err_cb, 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_tlv_put *tlv, union %s_ies *src,\n"
" %s message_type, osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)\n"
"{\n"
" return osmo_tlvs_encode(tlv, src, 0, %s_get_msg_coding(message_type), err_cb, 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_tlv_gen_main(const struct osmo_tlv_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;
}

View File

@@ -0,0 +1,41 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
-I$(top_builddir)/include \
-I$(top_builddir) \
$(NULL)
AM_CFLAGS = \
-Wall \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOCTRL_CFLAGS) \
$(COVERAGE_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
$(COVERAGE_LDFLAGS) \
$(NULL)
noinst_HEADERS = \
pfcp_tool.h \
$(NULL)
bin_PROGRAMS = \
osmo-pfcp-tool \
$(NULL)
osmo_pfcp_tool_SOURCES = \
osmo_pfcp_tool_main.c \
pfcp_tool.c \
pfcp_tool_vty.c \
$(NULL)
osmo_pfcp_tool_LDADD = \
$(top_builddir)/src/libosmo-pfcp/libosmo-pfcp.a \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(COVERAGE_LDFLAGS) \
$(NULL)

View File

@@ -0,0 +1,368 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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/lienses/>.
*
*/
#include <osmocom/core/application.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/msgb.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/cpu_sched_vty.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/ports.h>
#include <osmocom/vty/tdef_vty.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ctrl/control_vty.h>
#include <osmocom/ctrl/ports.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include "pfcp_tool.h"
#define _GNU_SOURCE
#include <getopt.h>
/* build switches from the configure script */
#include "config.h"
#include <signal.h>
#include <stdio.h>
#include <string.h>
extern void *tall_vty_ctx;
void *tall_pfcp_tool_ctx = NULL;
static int quit = 0;
static struct {
const char *config_file;
int daemonize;
enum vty_ref_gen_mode vty_ref_gen_mode;
const char *command_file;
} pfcp_tool_cmdline_config = {
.config_file = "osmo-pfcp-tool.cfg",
.vty_ref_gen_mode = VTY_REF_GEN_MODE_DEFAULT,
};
static void print_usage()
{
printf("Usage: osmo-pfcp-tool [command-file.vty]\n telnet localhost %d\n", OSMO_VTY_PORT_PFCP_TOOL);
}
static void print_help()
{
const struct value_string *vty_ref_gen_mode_name;
printf("Some useful options:\n");
printf(" -h --help This text.\n");
printf(" -D --daemonize Fork the process into a background daemon.\n");
printf(" -c --config-file filename The config file to use, for logging etc.\n");
printf(" -V --version Print the version of OsmoMSC.\n");
printf("\nVTY reference generation:\n");
printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n");
printf(" --vty-ref-mode MODE Mode for --vty-ref-xml:\n");
/* List all VTY ref gen modes */
for (vty_ref_gen_mode_name = vty_ref_gen_mode_names; vty_ref_gen_mode_name->str; vty_ref_gen_mode_name++)
printf(" %s: %s\n",
vty_ref_gen_mode_name->str,
get_value_string(vty_ref_gen_mode_desc, vty_ref_gen_mode_name->value));
}
static void handle_long_options(const char *prog_name, const int long_option)
{
switch (long_option) {
case 1:
pfcp_tool_cmdline_config.vty_ref_gen_mode = get_string_value(vty_ref_gen_mode_names, optarg);
if (pfcp_tool_cmdline_config.vty_ref_gen_mode < 0) {
fprintf(stderr, "%s: Unknown VTY reference generation mode: '%s'\n", prog_name, optarg);
exit(2);
}
break;
case 2:
fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
get_value_string(vty_ref_gen_mode_names, pfcp_tool_cmdline_config.vty_ref_gen_mode),
get_value_string(vty_ref_gen_mode_desc, pfcp_tool_cmdline_config.vty_ref_gen_mode));
vty_dump_xml_ref_mode(stdout, pfcp_tool_cmdline_config.vty_ref_gen_mode);
exit(0);
default:
fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
exit(2);
}
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static int long_option = 0;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"daemonize", 0, 0, 'D'},
{"config-file", 1, 0, 'c'},
{"version", 0, 0, 'V' },
{"vty-ref-mode", 1, &long_option, 1},
{"vty-ref-xml", 0, &long_option, 2},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "hDc:V", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_usage();
print_help();
exit(0);
case 0:
handle_long_options(argv[0], long_option);
break;
case 'D':
pfcp_tool_cmdline_config.daemonize = 1;
break;
case 'c':
pfcp_tool_cmdline_config.config_file = optarg;
break;
case 'V':
print_version(1);
exit(0);
break;
default:
/* catch unknown options *as well as* missing arguments. */
fprintf(stderr, "%s: Error in command line options. Exiting.\n", argv[0]);
exit(-1);
}
}
if (argc > optind) {
pfcp_tool_cmdline_config.command_file = argv[optind];
optind++;
}
if (argc > optind) {
fprintf(stderr, "%s: Unsupported positional arguments on command line\n", argv[optind]);
exit(2);
}
}
static void signal_handler(int signum)
{
fprintf(stdout, "signal %u received\n", signum);
switch (signum) {
case SIGINT:
case SIGTERM:
LOGP(DLGLOBAL, LOGL_NOTICE, "Terminating due to signal %d\n", signum);
quit++;
break;
case SIGABRT:
osmo_generate_backtrace();
/* in case of abort, we want to obtain a talloc report and
* then run default SIGABRT handler, who will generate coredump
* and abort the process. abort() should do this for us after we
* return, but program wouldn't exit if an external SIGABRT is
* received.
*/
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(tall_pfcp_tool_ctx, stderr);
signal(SIGABRT, SIG_DFL);
raise(SIGABRT);
break;
case SIGUSR1:
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(tall_pfcp_tool_ctx, stderr);
break;
case SIGUSR2:
talloc_report_full(tall_vty_ctx, stderr);
break;
default:
break;
}
}
static 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";
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[] = {
};
const struct log_info log_info = {
.cat = pfcp_tool_default_categories,
.num_cat = ARRAY_SIZE(pfcp_tool_default_categories),
};
int pfcp_tool_mainloop()
{
log_reset_context();
osmo_select_main_ctx(0);
/* If the user hits Ctrl-C the third time, just terminate immediately. */
if (quit >= 3)
return 1;
/* Has SIGTERM been received (and not yet been handled)? */
if (quit && !osmo_select_shutdown_requested()) {
osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);
/* Request write-only mode in osmo_select_main_ctx() */
osmo_select_shutdown_request();
/* continue the main select loop until all write queues are serviced. */
}
return 0;
}
int main(int argc, char **argv)
{
int rc;
/* Track the use of talloc NULL memory contexts */
talloc_enable_null_tracking();
osmo_fsm_set_dealloc_ctx(OTC_SELECT);
tall_pfcp_tool_ctx = talloc_named_const(NULL, 1, "osmo-pfcp-tool");
pfcp_tool_vty_app_info.tall_ctx = tall_pfcp_tool_ctx;
msgb_talloc_ctx_init(tall_pfcp_tool_ctx, 0);
osmo_signal_talloc_ctx_init(tall_pfcp_tool_ctx);
osmo_init_logging2(tall_pfcp_tool_ctx, &log_info);
log_set_print_category_hex(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 1);
log_set_print_level(osmo_stderr_target, 1);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
log_set_print_extended_timestamp(osmo_stderr_target, 1);
osmo_fsm_log_timeouts(true);
osmo_fsm_log_addr(true);
osmo_stats_init(tall_pfcp_tool_ctx);
g_pfcp_tool_alloc(tall_pfcp_tool_ctx);
/* For --version, vty_init() must be called before handling options */
vty_init(&pfcp_tool_vty_app_info);
ctrl_vty_init(tall_pfcp_tool_ctx);
logging_vty_add_cmds();
osmo_talloc_vty_add_cmds();
osmo_cpu_sched_vty_init(tall_pfcp_tool_ctx);
osmo_fsm_vty_add_cmds();
osmo_tdef_vty_groups_init(CONFIG_NODE, g_pfcp_tool_tdef_groups);
pfcp_tool_vty_init_cfg();
/* Parse options */
handle_options(argc, argv);
if (pfcp_tool_cmdline_config.config_file) {
rc = vty_read_config_file(pfcp_tool_cmdline_config.config_file, NULL);
if (rc < 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "Failed to parse the config file: '%s'\n",
pfcp_tool_cmdline_config.config_file);
}
}
/* start telnet, 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_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;
}
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
if (pfcp_tool_cmdline_config.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
return 6;
}
}
pfcp_tool_mainloop();
pfcp_tool_vty_init_cmds();
if (pfcp_tool_cmdline_config.command_file) {
printf("Reading '%s'\n", pfcp_tool_cmdline_config.command_file);
rc = vty_read_config_file(pfcp_tool_cmdline_config.command_file, NULL);
if (rc < 0) {
LOGP(DLGLOBAL, LOGL_FATAL, "Failed to parse the command file: '%s'\n",
pfcp_tool_cmdline_config.command_file);
return 1;
}
printf("Done reading '%s', waiting for retransmission queue...\n",
pfcp_tool_cmdline_config.command_file);
do {
if (pfcp_tool_mainloop())
break;
} while (!llist_empty(&g_pfcp_tool->ep->retrans_queue));
printf("Done'\n");
} else {
printf("Listening for commands on VTY...\n");
do {
if (pfcp_tool_mainloop())
break;
} while (!osmo_select_shutdown_done());
}
osmo_pfcp_endpoint_free(&g_pfcp_tool->ep);
log_fini();
/* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */
//talloc_report_full(tall_pfcp_tool_ctx, stderr);
talloc_free(tall_pfcp_tool_ctx);
//talloc_report_full(tall_vty_ctx, stderr);
talloc_free(tall_vty_ctx);
//talloc_report_full(NULL, stderr);
talloc_disable_null_tracking();
return 0;
}

View File

@@ -0,0 +1,160 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/utils.h>
#include <osmocom/core/talloc.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include "pfcp_tool.h"
struct g_pfcp_tool *g_pfcp_tool = NULL;
struct osmo_tdef_group g_pfcp_tool_tdef_groups[] = {
{ .name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP" },
{0}
};
void g_pfcp_tool_alloc(void *ctx)
{
OSMO_ASSERT(g_pfcp_tool == NULL);
g_pfcp_tool = talloc_zero(ctx, struct g_pfcp_tool);
*g_pfcp_tool = (struct g_pfcp_tool){
.vty_cfg = {
.local_ip = talloc_strdup(g_pfcp_tool, "0.0.0.0"),
.local_port = OSMO_PFCP_PORT,
},
};
INIT_LLIST_HEAD(&g_pfcp_tool->peers);
}
struct pfcp_tool_peer *pfcp_tool_peer_find(const struct osmo_sockaddr *remote_addr)
{
struct pfcp_tool_peer *peer;
llist_for_each_entry(peer, &g_pfcp_tool->peers, entry) {
if (osmo_sockaddr_cmp(&peer->remote_addr, remote_addr) == 0)
return peer;
}
return NULL;
}
struct pfcp_tool_peer *pfcp_tool_peer_find_or_create(const struct osmo_sockaddr *remote_addr)
{
struct pfcp_tool_peer *peer = pfcp_tool_peer_find(remote_addr);
if (peer)
return peer;
peer = talloc_zero(g_pfcp_tool, struct pfcp_tool_peer);
peer->remote_addr = *remote_addr;
peer->next_seid_state = 0x1234567;
INIT_LLIST_HEAD(&peer->sessions);
llist_add(&peer->entry, &g_pfcp_tool->peers);
return peer;
}
struct pfcp_tool_session *pfcp_tool_session_find(struct pfcp_tool_peer *peer, uint64_t cp_seid)
{
struct pfcp_tool_session *session;
llist_for_each_entry(session, &peer->sessions, entry) {
if (session->cp_seid == cp_seid)
return session;
}
return NULL;
}
struct pfcp_tool_session *pfcp_tool_session_find_or_create(struct pfcp_tool_peer *peer, uint64_t cp_seid)
{
struct pfcp_tool_session *session = pfcp_tool_session_find(peer, cp_seid);
if (session)
return session;
session = talloc(peer, struct pfcp_tool_session);
*session = (struct pfcp_tool_session){
.peer = peer,
.cp_seid = cp_seid,
};
llist_add(&session->entry, &peer->sessions);
return session;
}
static void rx_assoc_setup_resp(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
{
if (m->ies.assoc_setup_resp.up_function_features_present)
OSMO_LOG_PFCP_MSG(m, LOGL_NOTICE, "Associated. UP Peer features: %s\n",
osmo_pfcp_bits_to_str_c(OTC_SELECT,
m->ies.assoc_setup_resp.up_function_features.bits,
osmo_pfcp_up_feature_strs));
if (m->ies.assoc_setup_resp.cp_function_features_present)
OSMO_LOG_PFCP_MSG(m, LOGL_NOTICE, "Associated. CP Peer features: %s\n",
osmo_pfcp_bits_to_str_c(OTC_SELECT,
m->ies.assoc_setup_resp.cp_function_features.bits,
osmo_pfcp_cp_feature_strs));
}
static void rx_session_est_resp(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
{
struct pfcp_tool_peer *peer;
struct pfcp_tool_session *session;
if (!m->h.seid_present) {
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response should contain a SEID\n");
return;
}
if (!m->ies.session_est_resp.up_f_seid_present) {
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Session Establishment Response without UP F-SEID\n");
return;
}
peer = pfcp_tool_peer_find(&m->remote_addr);
if (!peer)
return;
session = pfcp_tool_session_find(peer, m->h.seid);
if (!session)
return;
session->up_f_seid = m->ies.session_est_resp.up_f_seid;
}
void pfcp_tool_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
{
switch (m->h.message_type) {
case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP:
rx_assoc_setup_resp(ep, m);
break;
case OSMO_PFCP_MSGT_SESSION_EST_RESP:
rx_session_est_resp(ep, m);
break;
default: break;
}
}
int peer_tx(struct pfcp_tool_peer *peer, struct osmo_pfcp_msg *m)
{
int rc;
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;
}
uint64_t peer_new_seid(struct pfcp_tool_peer *peer)
{
return peer->next_seid_state++;
}

View File

@@ -0,0 +1,61 @@
/* Global definitions for osmo-pfcp-tool */
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/socket.h>
#include <osmocom/pfcp/pfcp_msg.h>
struct osmo_tdef;
struct ctrl_handle;
extern struct osmo_tdef g_pfcp_tool_tdefs[];
extern struct osmo_tdef_group g_pfcp_tool_tdef_groups[];
struct pfcp_tool_peer {
struct llist_head entry;
struct osmo_sockaddr remote_addr;
struct osmo_pfcp_msg last_req;
struct osmo_pfcp_msg last_resp;
uint64_t next_seid_state;
struct llist_head sessions;
};
struct pfcp_tool_session {
struct llist_head entry;
struct pfcp_tool_peer *peer;
uint64_t cp_seid;
struct osmo_pfcp_ie_f_seid up_f_seid;
};
struct g_pfcp_tool {
struct ctrl_handle *ctrl;
struct {
char *local_ip;
uint16_t local_port;
} vty_cfg;
struct osmo_pfcp_endpoint *ep;
struct llist_head peers;
};
extern struct g_pfcp_tool *g_pfcp_tool;
void g_pfcp_tool_alloc(void *ctx);
void pfcp_tool_vty_init_cfg();
void pfcp_tool_vty_init_cmds();
int pfcp_tool_mainloop();
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 seid);
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);

View File

@@ -0,0 +1,500 @@
/* osmo-pfcp-tool interface to quagga VTY */
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/socket.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_msg.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include "pfcp_tool.h"
enum pfcp_tool_vty_node {
PEER_NODE = _LAST_OSMOVTY_NODE + 1,
SESSION_NODE,
};
DEFUN(c_local_addr, c_local_addr_cmd,
"local-addr IP_ADDR",
"Set the local IP address to bind on for PFCP; see also 'listen'\n"
"IP address\n")
{
if (g_pfcp_tool->ep != NULL) {
vty_out(vty, "Already listening on %s%s",
osmo_sockaddr_to_str_c(OTC_SELECT, &g_pfcp_tool->ep->cfg.local_addr),
VTY_NEWLINE);
return CMD_WARNING;
}
osmo_talloc_replace_string(g_pfcp_tool, &g_pfcp_tool->vty_cfg.local_ip, argv[0]);
return CMD_SUCCESS;
}
DEFUN(c_listen, c_listen_cmd,
"listen",
"Bind local PFCP port and listen; see also 'local-addr'\n")
{
struct osmo_sockaddr_str local_addr;
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, &g_pfcp_tool->ep->cfg.local_addr),
VTY_NEWLINE);
return CMD_WARNING;
}
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, &g_pfcp_tool->ep->cfg.local_addr.u.sas);
/* 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\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;
}
DEFUN(c_sleep, c_sleep_cmd,
"sleep <0-999999> [<0-999>]",
"Let some time pass\n"
"Seconds to wait\n")
{
int secs = atoi(argv[0]);
int msecs = 0;
struct osmo_timer_list t = {0};
if (argc > 1)
msecs = atoi(argv[1]);
vty_out(vty, "zzZ %d.%03ds...%s", secs, msecs, VTY_NEWLINE);
vty_flush(vty);
osmo_timer_setup(&t, NULL, NULL);
osmo_timer_schedule(&t, secs, msecs * 1000);
/* Still operate the message pump while waiting for time to pass */
while (t.active && !osmo_select_shutdown_done()) {
if (pfcp_tool_mainloop())
break;
}
osmo_timer_del(&t);
vty_out(vty, "...zzZ %d%s", secs, VTY_NEWLINE);
vty_flush(vty);
return CMD_SUCCESS;
}
static struct cmd_node peer_node = {
PEER_NODE,
"%s(peer)# ",
1,
};
DEFUN(peer, peer_cmd,
"peer REMOTE_ADDR",
"Enter the 'peer' node for the given remote address\n"
"Remote PFCP peer's IP address\n")
{
struct pfcp_tool_peer *peer;
struct osmo_sockaddr_str remote_addr_str;
struct osmo_sockaddr remote_addr;
osmo_sockaddr_str_from_str(&remote_addr_str, argv[0], OSMO_PFCP_PORT);
osmo_sockaddr_str_to_sockaddr(&remote_addr_str, (struct sockaddr_storage*)&remote_addr);
peer = pfcp_tool_peer_find_or_create(&remote_addr);
vty->index = peer;
vty->node = PEER_NODE;
return CMD_SUCCESS;
}
#define TX_STR "Send a PFCP message to a peer\n"
DEFUN(peer_tx_heartbeat, peer_tx_heartbeat_cmd,
"tx heartbeat",
TX_STR "Send a Heartbeat Request\n")
{
struct pfcp_tool_peer *peer = vty->index;
int rc;
if (!g_pfcp_tool->ep) {
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
return CMD_WARNING;
}
vty_out(vty, "Tx Heartbeat Request to %s%s",
osmo_sockaddr_to_str_c(OTC_SELECT, &peer->remote_addr), VTY_NEWLINE);
rc = osmo_pfcp_endpoint_tx_heartbeat_req(g_pfcp_tool->ep, &peer->remote_addr);
if (rc) {
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(peer_tx_assoc_setup_req, peer_tx_assoc_setup_req_cmd,
"tx assoc-setup-req",
TX_STR "Send an Association Setup Request\n")
{
struct pfcp_tool_peer *peer = vty->index;
int rc;
struct osmo_pfcp_msg *m;
if (!g_pfcp_tool->ep) {
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
return CMD_WARNING;
}
m = osmo_pfcp_msg_alloc_tx(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);
rc = peer_tx(peer, m);
if (rc) {
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(peer_retrans_req, peer_retrans_req_cmd,
"retrans (req|resp)",
"Retransmit the last sent message\n" "Retransmit the last sent PFCP Request\n"
"Retransmit the last sent PFCP Response\n")
{
struct pfcp_tool_peer *peer = vty->index;
int rc;
struct osmo_pfcp_msg *m;
if (!g_pfcp_tool->ep) {
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
return CMD_WARNING;
}
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_DEBUG, "retrans %s\n", argv[0]);
rc = osmo_pfcp_endpoint_tx_data(g_pfcp_tool->ep, m);
if (rc) {
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
static struct cmd_node session_node = {
SESSION_NODE,
"%s(session)# ",
1,
};
DEFUN(session, session_cmd,
"session [<0-18446744073709551615>]",
"Enter the 'session' node for the given SEID\n"
"local Session Endpoint ID\n")
{
struct pfcp_tool_peer *peer = vty->index;
struct pfcp_tool_session *session;
if (argc > 0)
session = pfcp_tool_session_find_or_create(peer, atoll(argv[0]));
else
session = pfcp_tool_session_find_or_create(peer, peer_new_seid(peer));
vty->index = session;
vty->node = SESSION_NODE;
return CMD_SUCCESS;
}
DEFUN(session_tx_est_req, session_tx_est_req_cmd,
"tx session-est-req (forw|drop)",
TX_STR "Send a Session Establishment Request\n"
"Set FAR to FORW = 1\n"
"Set FAR to DROP = 1\n")
{
struct pfcp_tool_session *session = vty->index;
struct pfcp_tool_peer *peer = session->peer;
int rc;
struct osmo_pfcp_msg *m;
unsigned int apply_action_bit;
if (!g_pfcp_tool->ep) {
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (!strcmp("forw", argv[0]))
apply_action_bit = OSMO_PFCP_APPLY_ACTION_FORW;
else
apply_action_bit = OSMO_PFCP_APPLY_ACTION_DROP;
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;
m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){
.node_id = m->ies.session_est_req.node_id,
.cp_f_seid_present = true,
.cp_f_seid = {
.seid = session->cp_seid,
.ip_addr.v4_present = true,
.ip_addr.v4 = g_pfcp_tool->ep->cfg.local_addr,
},
.create_pdr_count = 2,
.create_pdr = {
{
.pdr_id = 1,
.precedence = 255,
.pdi = {
.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
.ue_ip_address_present = true,
.ue_ip_address = {
.ip_is_destination = true,
.ip_addr = {
.v4_present = true,
.v4 = {
.u.sin = {
.sin_family = AF_INET,
.sin_addr = { 0x022aa8c0 },
},
},
},
},
},
.far_id_present = true,
.far_id = 1,
},
{
.pdr_id = 2,
.precedence = 255,
.pdi = {
.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
.local_f_teid_present = true,
.local_f_teid = {
.choose_flag = true,
},
},
.outer_header_removal_present = true,
.outer_header_removal = {
.desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4,
},
.far_id_present = true,
.far_id = 2,
},
},
.create_far_count = 2,
.create_far = {
{
.far_id = 1,
.forw_params_present = true,
.forw_params = {
.destination_iface = OSMO_PFCP_DEST_IFACE_ACCESS,
.outer_header_creation_present = true,
.outer_header_creation = {
.teid_present = true,
.teid = 0x423423,
.ip_addr.v4_present = true,
.ip_addr.v4.u.sin = {
.sin_family = AF_INET,
.sin_addr = { 0x1700007f },
},
},
},
},
{
.far_id = 2,
.forw_params_present = true,
.forw_params = {
.destination_iface = OSMO_PFCP_DEST_IFACE_CORE,
},
},
},
};
osmo_pfcp_ip_addrs_set(&m->ies.session_est_req.cp_f_seid.ip_addr, &g_pfcp_tool->ep->cfg.local_addr);
osmo_pfcp_bits_set(m->ies.session_est_req.create_far[0].apply_action.bits, apply_action_bit, true);
osmo_pfcp_bits_set(m->ies.session_est_req.create_far[0].forw_params.outer_header_creation.desc_bits,
OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
osmo_pfcp_bits_set(m->ies.session_est_req.create_far[1].apply_action.bits, apply_action_bit, true);
rc = peer_tx(peer, m);
if (rc) {
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(session_tx_mod_req, session_tx_mod_req_cmd,
"tx session-mod-req (forw|drop)",
TX_STR "Send a Session Modification Request\n"
"Set FAR to FORW = 1\n"
"Set FAR to DROP = 1\n")
{
struct pfcp_tool_session *session = vty->index;
struct pfcp_tool_peer *peer = session->peer;
int rc;
struct osmo_pfcp_msg *m;
unsigned int apply_action_bit;
if (!g_pfcp_tool->ep) {
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (!strcmp("forw", argv[0]))
apply_action_bit = OSMO_PFCP_APPLY_ACTION_FORW;
else
apply_action_bit = OSMO_PFCP_APPLY_ACTION_DROP;
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){
.cp_f_seid_present = true,
.cp_f_seid = {
.seid = session->cp_seid,
.ip_addr.v4_present = true,
.ip_addr.v4 = g_pfcp_tool->ep->cfg.local_addr,
},
.upd_far_count = 2,
.upd_far = {
{
.far_id = 1,
.apply_action_present = true,
},
{
.far_id = 2,
.apply_action_present = true,
},
},
};
osmo_pfcp_bits_set(m->ies.session_mod_req.upd_far[0].apply_action.bits, apply_action_bit, true);
osmo_pfcp_bits_set(m->ies.session_mod_req.upd_far[1].apply_action.bits, apply_action_bit, true);
rc = peer_tx(peer, m);
if (rc) {
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(session_tx_del_req, session_tx_del_req_cmd,
"tx session-del-req",
TX_STR "Send a Session Deletion Request\n")
{
struct pfcp_tool_session *session = vty->index;
struct pfcp_tool_peer *peer = session->peer;
int rc;
struct osmo_pfcp_msg *m;
if (!g_pfcp_tool->ep) {
vty_out(vty, "Endpoint not configured%s", VTY_NEWLINE);
return CMD_WARNING;
}
m = osmo_pfcp_msg_alloc_tx(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;
rc = peer_tx(peer, m);
if (rc) {
vty_out(vty, "Failed to transmit: %s%s", strerror(-rc), VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
static void install_ve_and_config(struct cmd_element *cmd)
{
install_element_ve(cmd);
install_element(CONFIG_NODE, cmd);
}
void pfcp_tool_vty_init_cfg()
{
OSMO_ASSERT(g_pfcp_tool != NULL);
install_ve_and_config(&c_local_addr_cmd);
install_ve_and_config(&c_listen_cmd);
}
void pfcp_tool_vty_init_cmds()
{
OSMO_ASSERT(g_pfcp_tool != NULL);
install_ve_and_config(&c_sleep_cmd);
install_ve_and_config(&peer_cmd);
install_node(&peer_node, NULL);
install_element(PEER_NODE, &c_sleep_cmd);
install_element(PEER_NODE, &peer_tx_heartbeat_cmd);
install_element(PEER_NODE, &peer_tx_assoc_setup_req_cmd);
install_element(PEER_NODE, &peer_retrans_req_cmd);
install_element(PEER_NODE, &session_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);
}

View File

@@ -1,6 +1,7 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
-I$(top_builddir)/include \
-I$(top_builddir) \
$(NULL)
@@ -9,10 +10,16 @@ AM_CFLAGS = \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOCTRL_CFLAGS) \
$(LIBGTPNL_CFLAGS) \
$(LIBNFTNL_CFLAGS) \
$(LIBNFTABLES_CFLAGS) \
$(COVERAGE_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
$(LIBGTPNL_LDFLAGS) \
$(LIBNFTNL_LDFLAGS) \
$(LIBNFTABLES_LDFLAGS) \
$(COVERAGE_LDFLAGS) \
$(NULL)
@@ -22,12 +29,24 @@ bin_PROGRAMS = \
osmo_upf_SOURCES = \
osmo_upf_main.c \
up_endpoint.c \
up_gtp_action.c \
up_peer.c \
up_session.c \
upf.c \
upf_gtp.c \
upf_nft.c \
upf_vty.c \
$(NULL)
osmo_upf_LDADD = \
$(top_builddir)/src/libosmo-pfcp/libosmo-pfcp.a \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBGTPNL_LIBS) \
$(LIBNFTNL_LIBS) \
$(LIBNFTABLES_LIBS) \
$(COVERAGE_LDFLAGS) \
$(NULL)

View File

@@ -31,11 +31,16 @@
#include <osmocom/vty/cpu_sched_vty.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/ports.h>
#include <osmocom/vty/tdef_vty.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ctrl/control_vty.h>
#include <osmocom/ctrl/ports.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/upf/upf.h>
#include <osmocom/upf/up_endpoint.h>
#include <osmocom/upf/upf_gtp.h>
#define _GNU_SOURCE
#include <getopt.h>
@@ -45,6 +50,7 @@
#include <signal.h>
#include <stdio.h>
#include <string.h>
extern void *tall_vty_ctx;
@@ -175,7 +181,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);
@@ -204,6 +210,36 @@ static struct vty_app_info upf_vty_app_info = {
};
static const struct log_info_cat upf_default_categories[] = {
[DREF] = {
.name = "DREF",
.description = "Reference Counting",
.enabled = 0, .loglevel = LOGL_NOTICE,
.color = OSMO_LOGCOLOR_DARKGREY,
},
[DPEER] = {
.name = "DPEER",
.description = "PFCP peer association",
.enabled = 0, .loglevel = LOGL_NOTICE,
.color = OSMO_LOGCOLOR_YELLOW,
},
[DSESSION] = {
.name = "DSESSION",
.description = "PFCP sessions",
.enabled = 0, .loglevel = LOGL_NOTICE,
.color = OSMO_LOGCOLOR_BLUE,
},
[DGTP] = {
.name = "DGTP",
.description = "GTP tunneling",
.enabled = 0, .loglevel = LOGL_NOTICE,
.color = OSMO_LOGCOLOR_PURPLE,
},
[DNFT] = {
.name = "DNFT",
.description = "GTP forwarding rules via linux netfilter",
.enabled = 0, .loglevel = LOGL_NOTICE,
.color = OSMO_LOGCOLOR_PURPLE,
},
};
const struct log_info log_info = {
@@ -214,19 +250,27 @@ const struct log_info log_info = {
int main(int argc, char **argv)
{
int rc;
void *tall_infra_ctx;
/* Track the use of talloc NULL memory contexts */
talloc_enable_null_tracking();
osmo_fsm_set_dealloc_ctx(OTC_SELECT);
tall_upf_ctx = talloc_named_const(NULL, 1, "osmo-upf");
tall_infra_ctx = talloc_named_const(NULL, 1, "osmo-upf");
tall_upf_ctx = talloc_named_const(tall_infra_ctx, 1, "osmo-upf-main");
upf_vty_app_info.tall_ctx = tall_upf_ctx;
msgb_talloc_ctx_init(tall_upf_ctx, 0);
osmo_signal_talloc_ctx_init(tall_upf_ctx);
osmo_init_logging2(tall_upf_ctx, &log_info);
osmo_init_logging2(tall_infra_ctx, &log_info);
log_set_print_category_hex(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 1);
log_set_print_level(osmo_stderr_target, 1);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
log_set_print_extended_timestamp(osmo_stderr_target, 1);
osmo_fsm_log_timeouts(true);
osmo_fsm_log_addr(true);
@@ -243,6 +287,9 @@ int main(int argc, char **argv)
osmo_talloc_vty_add_cmds();
osmo_cpu_sched_vty_init(tall_upf_ctx);
upf_vty_init();
osmo_tdef_vty_groups_init(CONFIG_NODE, g_upf_tdef_groups);
/* Parse options */
handle_options(argc, argv);
@@ -280,12 +327,21 @@ int main(int argc, char **argv)
}
}
if (upf_gtp_genl_open())
return -1;
if (upf_gtp_devs_open())
return -1;
if (upf_pfcp_listen())
return -1;
do {
log_reset_context();
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)? */
@@ -298,16 +354,24 @@ int main(int argc, char **argv)
}
} while (!osmo_select_shutdown_done());
log_fini();
up_endpoint_free(&g_upf->pfcp.ep);
upf_gtp_devs_close();
upf_gtp_genl_close();
/* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */
talloc_report_full(tall_upf_ctx, stderr);
talloc_free(tall_upf_ctx);
talloc_report_full(tall_vty_ctx, stderr);
log_fini();
talloc_report_full(tall_infra_ctx, stderr);
talloc_free(tall_infra_ctx);
//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;
}

353
src/osmo-upf/up_endpoint.c Normal file
View File

@@ -0,0 +1,353 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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/lienses/>.
*
*/
#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 up_endpoint *up_ep = ep->priv;
struct up_peer *peer;
if (!m->ctx.peer_fi) {
peer = up_peer_find(up_ep, &m->remote_addr);
if (peer) {
up_peer_set_msg_ctx(peer, m);
}
} else {
peer = m->ctx.peer_fi->priv;
}
/* Find a session, if the header is parsed yet and contains a SEID */
if (peer && !m->ctx.session_fi && m->h.seid_present) {
struct up_session *session;
session = up_session_find_by_up_seid(peer, m->h.seid);
if (session) {
up_session_set_msg_ctx(session, m);
}
}
}
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,
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(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, 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)
{
struct up_peer *peer = m->ctx.peer_fi ? m->ctx.peer_fi->priv : NULL;
if (!peer) {
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);
}
static void up_ep_rx_assoc_upd_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
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(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);
}
static void up_ep_rx_assoc_rel_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
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(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);
}
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, 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,
&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)
{
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(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);
}
static void up_ep_rx_session_mod_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
if (!m->ctx.session_fi) {
/* Session not found. */
struct osmo_pfcp_msg *tx;
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");
tx->ies.session_mod_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
} else {
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR,
"No established session with SEID=0x%"PRIx64", cannot modify\n",
m->h.seid);
tx->ies.session_mod_resp.cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND;
}
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);
}
static void up_ep_rx_session_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
if (!m->ctx.session_fi) {
/* Session not found. */
struct osmo_pfcp_msg *tx;
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");
tx->ies.session_del_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
} else {
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR,
"No established session with SEID=0x%"PRIx64", cannot delete\n",
m->h.seid);
tx->ies.session_del_resp.cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND;
}
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);
}
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, NULL, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
}
static void up_endpoint_rx_cb(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m)
{
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;
case OSMO_PFCP_MSGT_ASSOC_SETUP_REQ:
up_ep_rx_assoc_setup_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_ASSOC_UPDATE_REQ:
up_ep_rx_assoc_upd_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_ASSOC_RELEASE_REQ:
up_ep_rx_assoc_rel_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_NODE_REPORT_REQ:
up_ep_rx_node_report_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_SET_DEL_REQ:
up_ep_rx_session_set_del_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_EST_REQ:
up_ep_rx_session_est_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_MOD_REQ:
up_ep_rx_session_mod_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_DEL_REQ:
up_ep_rx_session_del_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_REP_REQ:
up_ep_rx_session_rep_req(up_ep, m);
return;
default:
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Unknown message type\n");
return;
}
}
struct up_endpoint *up_endpoint_init(void *ctx, const struct osmo_sockaddr *local_addr)
{
int rc;
struct up_endpoint *up_ep;
up_ep = talloc_zero(ctx, struct up_endpoint);
INIT_LLIST_HEAD(&up_ep->peers);
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->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;
}
static struct up_session *up_endpoint_find_session(struct up_endpoint *ep, uint64_t up_seid)
{
struct up_peer *peer;
llist_for_each_entry(peer, &ep->peers, entry) {
struct up_session *session = up_session_find_by_up_seid(peer, up_seid);
if (session)
return session;
}
return NULL;
}
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 = up_endpoint_inc_seid(ep);
if (up_endpoint_find_session(ep, next_seid))
continue;
return next_seid;
}
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;
struct up_endpoint *ep = *_ep;
while ((peer = llist_first_entry_or_null(&ep->peers, struct up_peer, entry)))
up_peer_free(peer);
osmo_pfcp_endpoint_free(&ep->pfcp_ep);
*_ep = NULL;
}

View File

@@ -0,0 +1,160 @@
/*
* (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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <errno.h>
#include <string.h>
#include <osmocom/core/utils.h>
#include <osmocom/upf/upf.h>
#include <osmocom/upf/up_gtp_action.h>
#include <osmocom/upf/up_peer.h>
#include <osmocom/upf/up_session.h>
int up_gtp_action_cmp(const struct up_gtp_action *a, const struct up_gtp_action *b)
{
int cmp;
if (a == b)
return 0;
if (!a)
return -1;
if (!b)
return 1;
#define CMP_RET(MEMB) do { \
int _cmp = OSMO_CMP(a->MEMB, b->MEMB); \
if (_cmp) \
return _cmp; \
} while (0)
CMP_RET(kind);
switch (a->kind) {
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->endecaps.ue_addr, &b->endecaps.ue_addr);
if (cmp)
return cmp;
break;
case UP_GTP_U_FORW:
CMP_RET(forw.access.local_teid);
CMP_RET(forw.access.remote_teid);
CMP_RET(forw.core.local_teid);
CMP_RET(forw.core.remote_teid);
break;
default:
break;
}
return 0;
}
static int up_gtp_action_enable_disable(struct up_gtp_action *a, bool enable)
{
struct upf_gtp_dev *gtp_dev;
int rc;
switch (a->kind) {
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, cannot %s\n", enable ? "enable" : "disable");
return -EIO;
}
if (enable)
rc = upf_gtp_dev_tunnel_add(gtp_dev, &a->endecaps);
else
rc = upf_gtp_dev_tunnel_del(gtp_dev, &a->endecaps);
if (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 GTP tunnel\n", enable ? "Enabled" : "Disabled");
return 0;
case UP_GTP_U_FORW:
LOG_UP_GTP_ACTION(a, LOGL_ERROR, "TEID translation not yet implemented\n");
return -ENOTSUP;
default:
LOG_UP_GTP_ACTION(a, LOGL_ERROR, "Invalid action\n");
return -ENOTSUP;
}
}
int up_gtp_action_enable(struct up_gtp_action *a)
{
return up_gtp_action_enable_disable(a, true);
}
int up_gtp_action_disable(struct up_gtp_action *a)
{
return up_gtp_action_enable_disable(a, false);
}
int up_gtp_action_to_str_buf(char *buf, size_t buflen, const struct up_gtp_action *a)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
switch (a->kind) {
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_FORW:
OSMO_STRBUF_PRINTF(sb, "GTP:forw GTP-access:");
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->forw.access.gtp_remote_addr);
OSMO_STRBUF_PRINTF(sb, " TEID-access-r:0x%"PRIx32" TEID-access-l:0x%"PRIx32" GTP-core:",
a->forw.access.remote_teid, a->forw.access.local_teid);
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->forw.core.gtp_remote_addr);
OSMO_STRBUF_PRINTF(sb, " TEID-core-r:0x%"PRIx32" TEID-core-l:0x%"PRIx32,
a->forw.core.remote_teid, a->forw.core.local_teid);
break;
case UP_GTP_DROP:
OSMO_STRBUF_PRINTF(sb, "GTP:drop");
break;
default:
OSMO_STRBUF_PRINTF(sb, "GTP:?");
break;
}
if (a->session)
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;
}
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)
}

550
src/osmo-upf/up_peer.c Normal file
View File

@@ -0,0 +1,550 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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/lienses/>.
*
*/
#include <errno.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/utils.h>
#include <osmocom/pfcp/pfcp_msg.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/upf/upf.h>
#include <osmocom/upf/up_peer.h>
#include <osmocom/upf/up_endpoint.h>
#include <osmocom/upf/up_session.h>
enum up_peer_fsm_state {
UP_PEER_ST_NOT_ASSOCIATED,
UP_PEER_ST_ASSOCIATED,
UP_PEER_ST_GRACEFUL_RELEASE,
UP_PEER_ST_WAIT_USE_COUNT,
};
static const struct value_string up_peer_fsm_event_names[] = {
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_SETUP_REQ),
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_UPD_REQ),
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_REL_REQ),
OSMO_VALUE_STRING(UP_PEER_EV_RX_SESSION_EST_REQ),
OSMO_VALUE_STRING(UP_PEER_EV_HEARTBEAT_FAILURE),
OSMO_VALUE_STRING(UP_PEER_EV_USE_COUNT_ZERO),
OSMO_VALUE_STRING(UP_PEER_EV_SESSION_TERM),
{0}
};
static struct osmo_fsm up_peer_fsm;
static const struct osmo_tdef_state_timeout up_peer_fsm_timeouts[32] = {
[UP_PEER_ST_GRACEFUL_RELEASE] = { .T = -21 },
};
/* Transition to a state, using the T timer defined in up_peer_fsm_timeouts.
* Assumes local variable fi exists. */
#define up_peer_fsm_state_chg(state) \
osmo_tdef_fsm_inst_state_chg(fi, state, \
up_peer_fsm_timeouts, \
osmo_pfcp_tdefs, \
5)
static int up_peer_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
{
struct up_peer *peer = e->use_count->talloc_object;
int32_t total;
int level;
if (!e->use)
return -EINVAL;
total = osmo_use_count_total(&peer->use_count);
if (total == 0
|| (total == 1 && old_use_count == 0 && e->count == 1))
level = LOGL_INFO;
else
level = LOGL_DEBUG;
LOGPFSMSLSRC(peer->fi, DREF, level, file, line,
"%s %s: now used by %s\n",
(e->count - old_use_count) > 0? "+" : "-", e->use,
osmo_use_count_to_str_c(OTC_SELECT, &peer->use_count));
if (e->count < 0)
return -ERANGE;
if (total == 0)
osmo_fsm_inst_dispatch(peer->fi, UP_PEER_EV_USE_COUNT_ZERO, NULL);
return 0;
}
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);
}
static void up_peer_update_id(struct up_peer *peer)
{
osmo_fsm_inst_update_id_f_sanitize(peer->fi, '-', "%s", up_peer_remote_addr_str(peer));
LOGPFSML(peer->fi, LOGL_DEBUG, "Updated id\n");
}
static struct up_peer *up_peer_add(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
{
struct up_peer *peer;
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&up_peer_fsm, up_endpoint, NULL, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
peer = talloc(fi, struct up_peer);
OSMO_ASSERT(peer);
fi->priv = peer;
*peer = (struct up_peer) {
.fi = fi,
.up_endpoint = up_endpoint,
.remote_addr = *remote_addr,
.heartbeat_fi = NULL /* FIXME */,
.use_count = {
.talloc_object = peer,
.use_cb = up_peer_use_cb,
},
};
osmo_use_count_make_static_entries(&peer->use_count, peer->use_count_buf, ARRAY_SIZE(peer->use_count_buf));
hash_init(peer->sessions_by_up_seid);
hash_init(peer->sessions_by_cp_seid);
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_BUNDL, true);
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_RTTL, true);
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_FTUP, true);
up_peer_update_id(peer);
llist_add(&peer->entry, &up_endpoint->peers);
return peer;
}
struct up_peer *up_peer_find(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
{
struct up_peer *peer;
llist_for_each_entry(peer, &up_endpoint->peers, entry) {
if (osmo_sockaddr_cmp(&peer->remote_addr, remote_addr))
continue;
return peer;
}
return NULL;
}
struct up_peer *up_peer_find_or_add(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
{
struct up_peer *peer = up_peer_find(up_endpoint, remote_addr);
if (peer)
return peer;
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;
/* Return 1 to terminate FSM instance, 0 to keep running */
return 1;
}
void up_peer_set_msg_ctx(struct up_peer *peer, struct osmo_pfcp_msg *m)
{
OSMO_ASSERT(!m->ctx.peer_fi);
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_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 = 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;
}
static int up_peer_tx_assoc_setup_resp(struct up_peer *peer, struct osmo_pfcp_msg *m, enum osmo_pfcp_cause cause)
{
struct osmo_pfcp_msg *resp;
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_ASSOC_SETUP_RESP);
resp->ies.assoc_setup_resp = (struct osmo_pfcp_msg_assoc_setup_resp) {
.cause = cause,
.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(resp, LOGL_ERROR, "Error sending response, cannot associate with peer\n");
return -EIO;
}
return 0;
}
static int up_peer_tx_assoc_rel_resp(struct up_peer *peer, struct osmo_pfcp_msg *m, enum osmo_pfcp_cause cause)
{
struct osmo_pfcp_msg *resp;
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP);
resp->ies.assoc_release_resp = (struct osmo_pfcp_msg_assoc_release_resp) {
.cause = cause,
};
if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp)) {
OSMO_LOG_PFCP_MSG(resp, LOGL_ERROR, "Error sending response\n");
return -EIO;
}
return 0;
}
static void up_peer_clear_sessions(struct up_peer *peer)
{
struct up_session *session;
int bkt;
struct hlist_node *tmp;
int count = 0;
hash_for_each_safe(peer->sessions_by_up_seid, bkt, tmp, session, node_by_up_seid) {
count += up_session_discard(session);
}
if (count)
LOGPFSML(peer->fi, LOGL_NOTICE, "terminated %d sessions\n", count);
}
static void up_peer_rx_assoc_setup_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
{
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
* association. Continue. */
/* If the peer has restarted, it has forgotten about all sessions. */
if (peer->remote_recovery_timestamp != m->ies.assoc_setup_req.recovery_time_stamp) {
LOGPFSML(fi, LOGL_NOTICE, "another Association Setup Request, with different Recovery Timestamp."
" Clearing sessions, sending ACK.\n");
up_peer_clear_sessions(peer);
} else {
LOGPFSML(fi, LOGL_NOTICE, "another Association Setup Request, with same Recovery Timestamp."
" Keeping sessions, sending ACK.\n");
}
}
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 {
/* 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);
}
static void up_peer_rx_assoc_rel_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
{
struct osmo_fsm_inst *fi = peer->fi;
up_peer_tx_assoc_rel_resp(peer, m, OSMO_PFCP_CAUSE_REQUEST_ACCEPTED);
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
}
static void up_peer_rx_session_est_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
{
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, NULL);
if (!session) {
cause = OSMO_PFCP_CAUSE_NO_RESOURCES_AVAILABLE;
goto nack_response;
}
up_session_set_msg_ctx(session, m);
if (osmo_fsm_inst_dispatch(session->fi, UP_SESSION_EV_RX_SESSION_EST_REQ, m)) {
cause = OSMO_PFCP_CAUSE_REQUEST_REJECTED;
goto nack_response;
}
return;
nack_response:
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_SESSION_EST_RESP);
resp->h.seid = m->ies.session_est_req.cp_f_seid.seid;
resp->h.seid_present = true;
resp->ies.session_est_resp = (struct osmo_pfcp_msg_session_est_resp){
.cause = cause,
};
osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp);
}
static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct up_peer *peer = fi->priv;
switch (event) {
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
up_peer_rx_assoc_setup_req(peer, data);
break;
case UP_PEER_EV_USE_COUNT_ZERO:
/* Not associated and no pending messages. discard peer. */
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
return;
default:
OSMO_ASSERT(false);
}
}
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;
switch (event) {
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
up_peer_rx_assoc_setup_req(peer, data);
break;
case UP_PEER_EV_RX_ASSOC_UPD_REQ:
// FIXME
break;
case UP_PEER_EV_RX_SESSION_EST_REQ:
up_peer_rx_session_est_req(peer, data);
break;
case UP_PEER_EV_HEARTBEAT_FAILURE:
// FIXME
break;
case UP_PEER_EV_USE_COUNT_ZERO:
/* Stay associated. */
return;
default:
OSMO_ASSERT(false);
}
}
static void up_peer_associated_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
{
if (next_state != UP_PEER_ST_ASSOCIATED)
LOGPFSML(fi, LOGL_NOTICE, "Peer released\n");
}
static void up_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
//struct up_peer *peer = fi->priv;
// FIXME
}
static void up_peer_graceful_release_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct up_peer *peer = fi->priv;
switch (event) {
case UP_PEER_EV_HEARTBEAT_FAILURE:
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
break;
case UP_PEER_EV_USE_COUNT_ZERO:
/* When there are still sessions, stay around. */
if (!hash_empty(peer->sessions_by_up_seid))
return;
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
return;
default:
OSMO_ASSERT(false);
}
}
static void up_peer_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct up_peer *peer = fi->priv;
up_peer_clear_sessions(peer);
if (!osmo_use_count_total(&peer->use_count))
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
static void up_peer_wait_use_count_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct up_peer *peer = fi->priv;
switch (event) {
case UP_PEER_EV_USE_COUNT_ZERO:
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
return;
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
up_peer_rx_assoc_setup_req(peer, data);
break;
default:
OSMO_ASSERT(false);
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state up_peer_fsm_states[] = {
[UP_PEER_ST_NOT_ASSOCIATED] = {
.name = "NOT_ASSOCIATED",
.in_event_mask = 0
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
| S(UP_PEER_EV_USE_COUNT_ZERO)
,
.out_state_mask = 0
| S(UP_PEER_ST_ASSOCIATED)
| S(UP_PEER_ST_WAIT_USE_COUNT)
,
.action = up_peer_not_associated_action,
},
[UP_PEER_ST_ASSOCIATED] = {
.name = "ASSOCIATED",
.in_event_mask = 0
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
| S(UP_PEER_EV_RX_ASSOC_UPD_REQ)
| S(UP_PEER_EV_RX_SESSION_EST_REQ)
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
| S(UP_PEER_EV_USE_COUNT_ZERO)
,
.out_state_mask = 0
| S(UP_PEER_ST_ASSOCIATED)
| 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,
},
[UP_PEER_ST_GRACEFUL_RELEASE] = {
.name = "GRACEFUL_RELEASE",
.in_event_mask = 0
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
| S(UP_PEER_EV_USE_COUNT_ZERO)
,
.out_state_mask = 0
| S(UP_PEER_ST_WAIT_USE_COUNT)
,
.onenter = up_peer_graceful_release_onenter,
.action = up_peer_graceful_release_action,
},
[UP_PEER_ST_WAIT_USE_COUNT] = {
.name = "WAIT_USE_COUNT",
.in_event_mask = 0
| S(UP_PEER_EV_USE_COUNT_ZERO)
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
,
.out_state_mask = 0
| S(UP_PEER_ST_ASSOCIATED)
,
.onenter = up_peer_wait_use_count_onenter,
.action = up_peer_wait_use_count_action,
},
};
void up_peer_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct up_peer *peer = fi->priv;
LOGPFSML(fi, LOGL_NOTICE, "Peer removed\n");
llist_del(&peer->entry);
}
static void up_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case UP_PEER_EV_SESSION_TERM:
/* ignore */
return;
case UP_PEER_EV_RX_ASSOC_REL_REQ:
up_peer_rx_assoc_rel_req(fi->priv, data);
return;
default:
OSMO_ASSERT(false);
}
}
static struct osmo_fsm up_peer_fsm = {
.name = "up_peer",
.log_subsys = DPEER,
.states = up_peer_fsm_states,
.num_states = ARRAY_SIZE(up_peer_fsm_states),
.event_names = up_peer_fsm_event_names,
.timer_cb = up_peer_fsm_timer_cb,
.cleanup = up_peer_fsm_cleanup,
.allstate_event_mask = 0
| S(UP_PEER_EV_RX_ASSOC_REL_REQ)
| S(UP_PEER_EV_SESSION_TERM)
,
.allstate_action = up_peer_allstate_action,
};
static __attribute__((constructor)) void up_peer_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&up_peer_fsm) == 0);
}
void up_peer_free(struct up_peer *peer)
{
osmo_fsm_inst_term(peer->fi, OSMO_FSM_TERM_REGULAR, NULL);
}

193
src/osmo-upf/up_peer_fsm.c Normal file
View File

@@ -0,0 +1,193 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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/lienses/>.
*
*/
#include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/upf/up_peer.h>
enum up_peer_fsm_state {
UP_PEER_ST_NOT_ASSOCIATED,
UP_PEER_ST_ASSOCIATED,
UP_PEER_ST_GRACEFUL_RELEASE,
};
static const struct value_string up_peer_fsm_event_names[] = {
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_SETUP_REQ),
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_UPD_REQ),
OSMO_VALUE_STRING(UP_PEER_EV_RX_SESSION_EST_REQ),
OSMO_VALUE_STRING(UP_PEER_EV_HEARTBEAT_FAILURE),
{0}
};
static struct osmo_fsm up_peer_fsm;
static const struct osmo_tdef_state_timeout up_peer_fsm_timeouts[32] = {
[UP_PEER_ST_GRACEFUL_RELEASE] = { .T = -21 },
};
/* Transition to a state, using the T timer defined in up_peer_fsm_timeouts.
* Assumes local variable fi exists. */
#define up_peer_fsm_state_chg(state) \
osmo_tdef_fsm_inst_state_chg(fi, state, \
up_peer_fsm_timeouts, \
g_upf_tdefs, \
5)
struct up_peer *up_peer_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
{
struct up_peer *up_peer;
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&up_peer_fsm, parent_fi, parent_event_term);
OSMO_ASSERT(fi);
up_peer = talloc(fi, struct up_peer);
OSMO_ASSERT(up_peer);
fi->priv = up_peer;
*up_peer = (struct up_peer){
.fi = fi,
};
return up_peer;
}
static int up_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
//struct up_peer *up_peer = fi->priv;
/* Return 1 to terminate FSM instance, 0 to keep running */
return 1;
}
static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
//struct up_peer *up_peer = fi->priv;
switch (event) {
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
// FIXME
break;
default:
OSMO_ASSERT(false);
}
}
static void up_peer_associated_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
//struct up_peer *up_peer = fi->priv;
// FIXME
}
static void up_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
//struct up_peer *up_peer = fi->priv;
switch (event) {
case UP_PEER_EV_RX_ASSOC_UPD_REQ:
// FIXME
break;
case UP_PEER_EV_RX_SESSION_EST_REQ:
// FIXME
break;
case UP_PEER_EV_HEARTBEAT_FAILURE:
// FIXME
break;
default:
OSMO_ASSERT(false);
}
}
static void up_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
//struct up_peer *up_peer = fi->priv;
// FIXME
}
static void up_peer_graceful_release_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
//struct up_peer *up_peer = fi->priv;
switch (event) {
case UP_PEER_EV_HEARTBEAT_FAILURE:
// FIXME
break;
default:
OSMO_ASSERT(false);
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state up_peer_fsm_states[] = {
[UP_PEER_ST_NOT_ASSOCIATED] = {
.name = "not_associated",
.in_event_mask = 0
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
,
.out_state_mask = 0
| S(UP_PEER_ST_ASSOCIATED)
,
.action = up_peer_not_associated_action,
},
[UP_PEER_ST_ASSOCIATED] = {
.name = "associated",
.in_event_mask = 0
| S(UP_PEER_EV_RX_ASSOC_UPD_REQ)
| S(UP_PEER_EV_RX_SESSION_EST_REQ)
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
,
.out_state_mask = 0
| S(UP_PEER_ST_GRACEFUL_RELEASE)
,
.onenter = up_peer_associated_onenter,
.action = up_peer_associated_action,
},
[UP_PEER_ST_GRACEFUL_RELEASE] = {
.name = "graceful_release",
.in_event_mask = 0
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
,
.out_state_mask = 0
,
.onenter = up_peer_graceful_release_onenter,
.action = up_peer_graceful_release_action,
},
};
static struct osmo_fsm up_peer_fsm = {
.name = "up_peer",
.states = up_peer_fsm_states,
.num_states = ARRAY_SIZE(up_peer_fsm_states),
.log_subsys = DSESSION,
.event_names = up_peer_fsm_event_names,
.timer_cb = up_peer_fsm_timer_cb,
};
static __attribute__((constructor)) void up_peer_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&up_peer_fsm) == 0);
}

1352
src/osmo-upf/up_session.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -18,13 +18,74 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/upf/upf.h>
#include <osmocom/upf/up_endpoint.h>
#include <osmocom/upf/upf_gtp.h>
struct g_upf *g_upf = NULL;
struct osmo_tdef_group g_upf_tdef_groups[] = {
{ "pfcp", "PFCP endpoint timers", osmo_pfcp_tdefs, },
{0}
};
void g_upf_alloc(void *ctx)
{
OSMO_ASSERT(g_upf == NULL);
g_upf = talloc_zero(ctx, struct g_upf);
*g_upf = (struct g_upf){
.pfcp = {
.vty_cfg = {
.local_addr = talloc_strdup(g_upf, UPF_PFCP_LISTEN_DEFAULT),
.local_port = OSMO_PFCP_PORT,
},
},
};
INIT_LLIST_HEAD(&g_upf->gtp.vty_cfg.devs);
INIT_LLIST_HEAD(&g_upf->gtp.devs);
}
int upf_pfcp_listen()
{
struct osmo_sockaddr_str local_addr_str;
struct osmo_sockaddr local_addr;
OSMO_ASSERT(g_upf);
OSMO_ASSERT(g_upf->pfcp.ep == NULL);
/* 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_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_init(g_upf, &local_addr);;
if (!g_upf->pfcp.ep) {
fprintf(stderr, "Failed to allocate PFCP endpoint.\n");
return -1;
}
return 0;
}
int upf_gtp_devs_open()
{
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 (d->create) {
if (!upf_gtp_dev_create(d->dev_name, d->local_addr, false, false))
return -1;
} else {
if (!upf_gtp_dev_use(d->dev_name))
return -1;
}
}
return 0;
}

454
src/osmo-upf/upf_gtp.c Normal file
View File

@@ -0,0 +1,454 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <net/if.h>
#include <linux/gtp.h>
#include <libgtpnl/gtpnl.h>
#include <libgtpnl/gtp.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/upf/upf.h>
#include <osmocom/upf/upf_gtp.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 LOG_GTP_TUN(TUN, LEVEL, FMT, 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)
{
uint16_t v0_port;
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
OSMO_STRBUF_PRINTF(sb, "%s", dev->name ? : "null");
if (dev->name && dev->ifidx)
OSMO_STRBUF_PRINTF(sb, " [%u]", dev->ifidx);
if (dev->sgsn_mode)
OSMO_STRBUF_PRINTF(sb, " (SGSN)");
OSMO_STRBUF_PRINTF(sb, " ");
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &dev->gtpv1.local_addr);
v0_port = osmo_sockaddr_port(&dev->gtpv0.local_addr.u.sa);
if (dev->gtpv0.enabled && v0_port)
OSMO_STRBUF_PRINTF(sb, "/%u", v0_port);
return sb.chars_needed;
}
char *upf_gtp_dev_to_str_c(void *ctx, const struct upf_gtp_dev *dev)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_dev_to_str_buf, 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->gtp.devs, entry) {
if (!strcmp(name, dev->name))
return dev;
}
return NULL;
}
struct upf_gtp_dev *upf_gtp_dev_first()
{
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()). */
static int upf_gtp_dev_delete(struct upf_gtp_dev *dev)
{
int rc;
if (!dev->name)
return 0;
rc = gtp_dev_destroy(dev->name);
if (rc < 0) {
LOG_GTP_DEV(dev, LOGL_ERROR, "Error while deleting device: %s\n", strerror(errno));
return rc;
}
LOG_GTP_DEV(dev, LOGL_NOTICE, "Deleted GTP device\n");
dev->name = NULL;
return 0;
}
static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev);
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);
struct osmo_sockaddr_str addr_conv;
local_addr = local_addr ? : "0.0.0.0";
if (dev) {
LOG_GTP_DEV(dev, LOGL_ERROR, "Device already exists. Cannot create %s %s\n", name, local_addr);
return NULL;
}
dev = talloc(g_upf, struct upf_gtp_dev);
*dev = (struct upf_gtp_dev){
.name = talloc_strdup(dev, name),
.gtpv0.ofd.fd = -1,
.gtpv1.ofd.fd = -1,
};
INIT_LLIST_HEAD(&dev->tunnels);
osmo_sockaddr_str_from_str(&addr_conv, local_addr, PORT_GTP0_U);
osmo_sockaddr_str_to_sockaddr(&addr_conv, &dev->gtpv0.local_addr.u.sas);
addr_conv.port = PORT_GTP1_U;
osmo_sockaddr_str_to_sockaddr(&addr_conv, &dev->gtpv1.local_addr.u.sas);
llist_add(&dev->entry, &g_upf->gtp.devs);
talloc_set_destructor(dev, upf_gtp_dev_destruct);
return dev;
}
static struct upf_gtp_dev *dev_resolve_ifidx(struct upf_gtp_dev *dev)
{
int rc;
dev->ifidx = if_nametoindex(dev->name);
if (dev->ifidx == 0) {
LOG_GTP_DEV(dev, LOGL_ERROR, "No such device: '%s'\n", dev->name);
talloc_free(dev);
return NULL;
}
/* Let's try something to see if talking to the device works. */
errno = 0;
rc = gtp_list_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl);
if (errno)
rc = -errno;
else if (rc)
rc = -EINVAL;
if (rc) {
LOG_GTP_DEV(dev, LOGL_ERROR, "Failed to open GTP device: %s\n", strerror(-rc));
talloc_free(dev);
return NULL;
}
LOG_GTP_DEV(dev, LOGL_NOTICE, "GTP device ready (ifidx=%u)\n", dev->ifidx);
return dev;
}
/* To clean up and deallocate, just call talloc_free() on the returned upf_gtp_dev*. */
struct upf_gtp_dev *upf_gtp_dev_create(const char *name, const char *local_addr, bool listen_for_gtpv0, bool sgsn_mode)
{
struct osmo_sockaddr any = {
.u.sin = {
.sin_family = AF_INET,
.sin_port = 0,
.sin_addr = {
.s_addr = INADDR_ANY,
},
},
};
int gtp0_fd;
int gtp1_fd;
int rc;
struct upf_gtp_dev *dev = upf_gtp_dev_alloc(name, local_addr);
if (!dev)
return NULL;
dev->sgsn_mode = sgsn_mode;
if (listen_for_gtpv0) {
dev->gtpv0.enabled = true;
rc = osmo_sock_init_osa_ofd(&dev->gtpv0.ofd, SOCK_DGRAM, 0, &dev->gtpv0.local_addr, &any,
OSMO_SOCK_F_BIND);
if (rc < 0) {
LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot bind GTPv0 on %s (rc=%d)\n",
osmo_sockaddr_to_str_c(OTC_SELECT, &dev->gtpv0.local_addr), rc);
return NULL;
}
LOG_GTP_DEV(dev, LOGL_DEBUG, "GTPv0 bound\n");
}
/* GTPv1 */
rc = osmo_sock_init_osa_ofd(&dev->gtpv1.ofd, SOCK_DGRAM, 0, &dev->gtpv1.local_addr, &any,
OSMO_SOCK_F_BIND);
if (rc < 0) {
LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot bind GTPv1 (rc=%d)\n", rc);
return NULL;
}
LOG_GTP_DEV(dev, LOGL_DEBUG, "GTPv1 bound\n");
gtp0_fd = listen_for_gtpv0 ? dev->gtpv0.ofd.fd : -1;
gtp1_fd = dev->gtpv1.ofd.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 */
dev->name = NULL;
talloc_free(dev);
return NULL;
}
LOG_GTP_DEV(dev, LOGL_NOTICE, "created GTP device\n");
dev->created = true;
return dev_resolve_ifidx(dev);
}
struct upf_gtp_dev *upf_gtp_dev_use(const char *name)
{
struct upf_gtp_dev *dev = upf_gtp_dev_alloc(name, NULL);
if (!dev)
return NULL;
return dev_resolve_ifidx(dev);
}
void upf_gtp_devs_close()
{
struct upf_gtp_dev *dev;
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->gtp.nl)
return;
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_open()
{
if (g_upf->gtp.nl && g_upf->gtp.genl_id >= 0)
return 0;
if (g_upf->gtp.nl)
upf_gtp_genl_close();
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->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;
}
LOGP(DGTP, LOGL_NOTICE, "Opened mnl_socket\n");
return 0;
}
struct upf_gtp_tun {
struct llist_head entry;
struct upf_gtp_dev *dev;
struct upf_gtp_tun_desc desc;
bool active;
};
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 };
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_tun_to_str_c(void *ctx, const struct upf_gtp_tun *tun)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_tun_to_str_buf, tun)
}
static int upf_gtp_tun_deactivate(struct upf_gtp_tun *tun);
static int upf_gtp_tun_destruct(struct upf_gtp_tun *tun)
{
if (tun->active)
upf_gtp_tun_deactivate(tun);
llist_del(&tun->entry);
return 0;
}
static struct upf_gtp_tun *upf_gtp_tun_alloc(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *desc)
{
struct upf_gtp_tun *tun = talloc(dev, struct upf_gtp_tun);
OSMO_ASSERT(tun);
*tun = (struct upf_gtp_tun){
.dev = dev,
.desc = *desc,
};
llist_add(&tun->entry, &dev->tunnels);
talloc_set_destructor(tun, upf_gtp_tun_destruct);
return tun;
}
static struct gtp_tunnel *upf_gtp_tun_to_gtp_tunnel(struct upf_gtp_tun *tun)
{
struct gtp_tunnel *t;
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;
}
t = gtp_tunnel_alloc();
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.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_tun_activate(struct upf_gtp_tun *tun)
{
int rc;
struct gtp_tunnel *t;
if (tun->active)
return -EALREADY;
t = upf_gtp_tun_to_gtp_tunnel(tun);
if (!t)
return -ENOTSUP;
errno = 0;
rc = gtp_add_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl, t);
if (errno) {
rc = -errno;
} else if (rc) {
rc = -EINVAL;
} else {
tun->active = true;
}
gtp_tunnel_free(t);
return rc;
}
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_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_tunnel_add(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
{
struct upf_gtp_tun *tun;
tun = upf_gtp_dev_tunnel_find(dev, tun_desc);
if (!tun)
tun = upf_gtp_tun_alloc(dev, tun_desc);
if (tun->active)
return 0;
return upf_gtp_tun_activate(tun);
}
int upf_gtp_dev_tunnel_del(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
{
struct upf_gtp_tun *tun;
int rc;
tun = upf_gtp_dev_tunnel_find(dev, tun_desc);
if (!tun)
return 0;
if (tun->active) {
rc = upf_gtp_tun_deactivate(tun);
if (rc)
return rc;
}
talloc_free(tun);
return 0;
}
static int upf_gtp_tun_deactivate(struct upf_gtp_tun *tun)
{
int rc;
struct gtp_tunnel *t;
if (!tun->active) {
LOG_GTP_TUN(tun, LOGL_ERROR, "Cannot deactivate, not active\n");
return -EINVAL;
}
t = upf_gtp_tun_to_gtp_tunnel(tun);
if (!t)
return -EINVAL;
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: %d %s\n", rc, strerror(rc));
else
tun->active = false;
gtp_tunnel_free(t);
return rc;
}
static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev)
{
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_tun, entry)))
talloc_free(t);
llist_del(&dev->entry);
osmo_fd_close(&dev->gtpv0.ofd);
osmo_fd_close(&dev->gtpv1.ofd);
if (dev->created)
upf_gtp_dev_delete(dev);
return 0;
}
int upf_gtp_tun_desc_cmp(const struct upf_gtp_tun_desc *a, const struct upf_gtp_tun_desc *b)
{
if (a == b)
return 0;
if (!a)
return -1;
if (!b)
return 1;
#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);
}

196
src/osmo-upf/upf_nft.c Normal file
View File

@@ -0,0 +1,196 @@
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <nftables/libnftables.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.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\n", table_name);
}
static int upf_nft_run(const char *ruleset)
{
int rc;
if (!g_upf->nft.nft_ctx) {
rc = upf_nft_init();
if (rc)
return rc;
}
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;
}
return 0;
}
int upf_nft_init()
{
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;
}
if (!g_upf->nft.table_name)
g_upf->nft.table_name = talloc_strdup(g_upf, "osmo-upf");
return upf_nft_run(upf_nft_ruleset_table_create(OTC_SELECT, g_upf->nft.table_name));
}
int upf_nft_free()
{
if (!g_upf->nft.nft_ctx)
return 0;
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;
/* The TEID that the peer sends to us in GTP packets. The peer considers this the remote TEID. */
uint32_t teid_remote;
/* The TEID that we send to the peer in GTP packets The peer considers this its own local TEID. */
uint32_t teid_local;
};
struct upf_nft_args {
/* global table name */
const char *table_name;
/* chain name for this specific tunnel forwarding */
uint32_t chain_id;
int priority;
struct upf_nft_args_peer peer_a;
struct upf_nft_args_peer peer_b;
};
#define CHAIN_NAME_PREFIX_FW "fw"
static int forwarding_single_dir(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 };
OSMO_STRBUF_PRINTF(sb, "add rule inet %s " CHAIN_NAME_PREFIX_FW "%u", args->table_name, args->chain_id);
/* Match only UDP packets */
OSMO_STRBUF_PRINTF(sb, " meta l4proto udp");
/* 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);
/* Match on the TEID in the header */
OSMO_STRBUF_PRINTF(sb, " @ih,32,32 0x%08x", from_peer->teid_remote);
/* 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);
/* 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_local);
OSMO_STRBUF_PRINTF(sb, " counter\n");
return sb.chars_needed;
}
static int upf_nft_ruleset_forward_create_buf(char *buf, size_t buflen, const struct upf_nft_args *args)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
/* Add a chain for this forwarding */
OSMO_STRBUF_PRINTF(sb, "add chain inet %s " CHAIN_NAME_PREFIX_FW "%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, forwarding_single_dir, args, &args->peer_a, &args->peer_b);
/* And from peer_b to peer_a */
OSMO_STRBUF_APPEND(sb, forwarding_single_dir, args, &args->peer_b, &args->peer_a);
return sb.chars_needed;
}
static char *upf_nft_ruleset_forward_create_c(void *ctx, const struct upf_nft_args *args)
{
OSMO_NAME_C_IMPL(ctx, 512, "ERROR", upf_nft_ruleset_forward_create_buf, args)
}
static int upf_nft_ruleset_forward_delete_buf(char *buf, size_t buflen, const struct upf_nft_args *args)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
OSMO_STRBUF_PRINTF(sb, "delete chain inet %s " CHAIN_NAME_PREFIX_FW "%u\n",
args->table_name, args->chain_id);
return sb.chars_needed;
}
static char *upf_nft_ruleset_forward_delete_c(void *ctx, const struct upf_nft_args *args)
{
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_nft_ruleset_forward_delete_buf, args)
}
static void upf_nft_args_from_forw_desc(struct upf_nft_args *args, const struct upf_nft_forw_desc *forw)
{
*args = (struct upf_nft_args){
.table_name = g_upf->nft.table_name,
.chain_id = forw->id,
.priority = g_upf->nft.priority,
.peer_a = {
.addr = &forw->access.gtp_remote_addr,
.teid_remote = forw->access.remote_teid,
.teid_local = forw->access.local_teid,
},
.peer_b = {
.addr = &forw->core.gtp_remote_addr,
.teid_remote = forw->core.remote_teid,
.teid_local = forw->core.local_teid,
},
};
}
int upf_nft_forward_create(struct upf_nft_forw_desc *forw)
{
struct upf_nft_args args;
/* Give this forwarding a new id, returned to the caller so that the forwarding can be deleted later */
g_upf->nft.next_id_state++;
forw->id = g_upf->nft.next_id_state;
upf_nft_args_from_forw_desc(&args, forw);
return upf_nft_run(upf_nft_ruleset_forward_create_c(OTC_SELECT, &args));
}
int upf_nft_forward_delete(struct upf_nft_forw_desc *forw)
{
struct upf_nft_args args;
upf_nft_args_from_forw_desc(&args, forw);
return upf_nft_run(upf_nft_ruleset_forward_delete_c(OTC_SELECT, &args));
}

281
src/osmo-upf/upf_vty.c Normal file
View File

@@ -0,0 +1,281 @@
/* OsmoUpf interface to quagga VTY */
/* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/socket.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_msg.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/upf/upf.h>
#include <osmocom/upf/upf_gtp.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>
enum upf_vty_node {
PFCP_NODE = _LAST_OSMOVTY_NODE + 1,
GTP_NODE,
};
static struct cmd_node cfg_pfcp_node = {
PFCP_NODE,
"%s(config-pfcp)# ",
1,
};
#define pfcp_vty (g_upf->pfcp.vty_cfg)
#define gtp_vty (g_upf->gtp.vty_cfg)
DEFUN(cfg_pfcp, cfg_pfcp_cmd,
"pfcp",
"Enter the PFCP configuration node\n")
{
vty->node = PFCP_NODE;
return CMD_SUCCESS;
}
static int config_write_pfcp(struct vty *vty)
{
vty_out(vty, "pfcp%s", VTY_NEWLINE);
if (strcmp(UPF_PFCP_LISTEN_DEFAULT, pfcp_vty.local_addr))
vty_out(vty, " local-addr %s%s", pfcp_vty.local_addr, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(cfg_pfcp_local_addr, cfg_pfcp_local_addr_cmd,
"local-addr IP_ADDR",
"Set the local IP address to bind on for PFCP\n"
"IP address\n")
{
osmo_talloc_replace_string(g_upf, &pfcp_vty.local_addr, argv[0]);
return CMD_SUCCESS;
}
static struct cmd_node cfg_gtp_node = {
GTP_NODE,
"%s(config-gtp)# ",
1,
};
DEFUN(cfg_gtp, cfg_gtp_cmd,
"gtp",
"Enter the GTP configuration node\n")
{
vty->node = GTP_NODE;
return CMD_SUCCESS;
}
static int config_write_gtp(struct vty *vty)
{
struct gtp_vty_cfg_dev *d;
vty_out(vty, "gtp%s", VTY_NEWLINE);
llist_for_each_entry(d, &gtp_vty.devs, entry) {
if (d->create) {
vty_out(vty, " dev create %s", d->dev_name);
if (d->local_addr)
vty_out(vty, " %s", d->local_addr);
vty_out(vty, "%s", VTY_NEWLINE);
} else {
vty_out(vty, " dev use %s%s", d->dev_name, VTY_NEWLINE);
}
}
return CMD_SUCCESS;
}
#define DEV_STR "Configure the GTP device to use for encaps/decaps.\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")
{
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, &gtp_vty.devs);
vty_out(vty, "Added GTP device %s (create new)%s", d->dev_name, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(cfg_gtp_dev_use, cfg_gtp_dev_use_cmd,
"dev use DEVNAME",
DEV_STR
"use an existing GTP device, e.g. created by 'gtp-link'\n"
"device name, e.g. 'apn0'\n")
{
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, &gtp_vty.devs);
vty_out(vty, "Added GTP device %s (use existing)%s", d->dev_name, VTY_NEWLINE);
return CMD_SUCCESS;
}
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 device if it was created here.\n"
"device name, e.g. 'apn0'\n")
{
const char *dev_name = argv[0];
struct gtp_vty_cfg_dev *d;
struct upf_gtp_dev *dev;
/* remove from VTY cfg */
llist_for_each_entry(d, &gtp_vty.devs, entry) {
if (strcmp(d->dev_name, dev_name))
continue;
llist_del(&d->entry);
break;
}
/* close device (and possibly delete from system, via talloc destructor) */
dev = upf_gtp_dev_find_by_name(dev_name);
if (dev)
talloc_free(dev);
return CMD_SUCCESS;
}
DEFUN(show_pdr, show_pdr_cmd,
"show pdr",
SHOW_STR
"List all sessions' PDR and FAR status\n")
{
struct up_peer *peer;
int active_count = 0;
int inactive_count = 0;
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
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->active) {
vty_out(vty, "%s: inactive: %s%s%s%s",
session->fi->id, pdr_to_str_c(OTC_SELECT, pdr),
pdr->inactive_reason ? ": " : "",
pdr->inactive_reason ? : "",
VTY_NEWLINE);
inactive_count++;
} else {
vty_out(vty, "%s: active: %s%s",
session->fi->id, pdr_to_str_c(OTC_SELECT, pdr),
VTY_NEWLINE);
active_count++;
}
}
}
}
vty_out(vty, "(%d of %d active)%s", active_count, active_count + inactive_count, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(show_gtp, show_gtp_cmd,
"show gtp",
SHOW_STR
"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;
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
struct up_gtp_action *a;
llist_for_each_entry(a, &session->active_gtp_actions, entry) {
vty_out(vty, "%s%s", up_gtp_action_to_str_c(OTC_SELECT, a), VTY_NEWLINE);
count++;
}
}
}
vty_out(vty, "(%d active)%s", count, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(show_session, show_session_cmd,
"show session",
SHOW_STR
"PFCP Session status\n")
{
struct up_peer *peer;
int inactive_count = 0;
int active_count = 0;
int fully_active_count = 0;
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
struct up_session *session;
int bkt;
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
vty_out(vty, "%s %s%s",
up_session_to_str_c(OTC_SELECT, session),
up_session_gtp_status(session), VTY_NEWLINE);
if (up_session_is_active(session)) {
if (up_session_is_fully_active(session, NULL, NULL))
fully_active_count++;
else
active_count++;
} else {
inactive_count++;
}
}
}
vty_out(vty, "(%d fully-active + %d partially active + %d inactive)%s",
fully_active_count, active_count, inactive_count, VTY_NEWLINE);
return CMD_SUCCESS;
}
void upf_vty_init()
{
OSMO_ASSERT(g_upf != NULL);
install_element_ve(&show_pdr_cmd);
install_element_ve(&show_gtp_cmd);
install_element_ve(&show_session_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_gtp_node, config_write_gtp);
install_element(CONFIG_NODE, &cfg_gtp_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);
}

View File

@@ -1,4 +1,5 @@
SUBDIRS = \
libosmo-tlv \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.

View File

@@ -0,0 +1,48 @@
SUBDIRS = \
test_tlv_gen \
test_tliv \
$(NULL)
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
-Wall \
$(LIBOSMOCORE_CFLAGS) \
$(NULL)
noinst_PROGRAMS = \
tlv_test \
tlv_dec_enc_test \
$(NULL)
EXTRA_DIST = \
tlv_test.ok \
tlv_dec_enc_test.ok \
$(NULL)
tlv_test_SOURCES = \
tlv_test.c \
$(NULL)
tlv_test_LDADD = \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(NULL)
tlv_dec_enc_test_SOURCES = \
tlv_dec_enc_test.c \
$(NULL)
tlv_dec_enc_test_LDADD = \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(NULL)
.PHONY: update_exp
update_exp:
$(builddir)/tlv_test >$(srcdir)/tlv_test.ok
$(builddir)/tlv_dec_enc_test >$(srcdir)/tlv_dec_enc_test.ok
$(MAKE) -C test_tlv_gen update_exp

View File

@@ -0,0 +1,60 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
-I$(bulddir) \
$(NULL)
AM_CFLAGS = \
-Wall \
$(LIBOSMOCORE_CFLAGS) \
$(NULL)
noinst_PROGRAMS = \
gen__myproto_ies_auto \
tliv_test \
$(NULL)
EXTRA_DIST = \
myproto_ies_custom.h \
tliv_test.ok \
$(NULL)
BUILT_SOURCES = \
myproto_ies_auto.h \
myproto_ies_auto.c \
$(NULL)
CLEANFILES = \
myproto_ies_auto.h \
myproto_ies_auto.c \
$(NULL)
gen__myproto_ies_auto_SOURCES = \
gen__myproto_ies_auto.c \
myproto_ies_custom.c \
$(NULL)
gen__myproto_ies_auto_LDADD = \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(NULL)
myproto_ies_auto.h: $(builddir)/gen__myproto_ies_auto
$(builddir)/gen__myproto_ies_auto h > $(builddir)/myproto_ies_auto.h
myproto_ies_auto.c: $(builddir)/gen__myproto_ies_auto
$(builddir)/gen__myproto_ies_auto c > $(builddir)/myproto_ies_auto.c
tliv_test_SOURCES = \
tliv_test.c \
myproto_ies_custom.c \
myproto_ies_auto.c \
$(NULL)
tliv_test_LDADD = \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(NULL)
.PHONY: update_exp
update_exp:
$(builddir)/tliv_test >$(srcdir)/tliv_test.ok

View File

@@ -0,0 +1,75 @@
/*
* (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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <stdbool.h>
#include <stdio.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include <osmocom/tlv/tlv_gen.h>
#define O OSMO_TLV_GEN_O
#define M OSMO_TLV_GEN_M
#define O_MULTI OSMO_TLV_GEN_O_MULTI
#define M_MULTI OSMO_TLV_GEN_M_MULTI
#define O_INST OSMO_TLV_GEN_O_INST
#define M_INST OSMO_TLV_GEN_M_INST
static const struct osmo_tlv_gen_ie bar = {
.name = "bar",
/* uses 'struct ie_bar bar;' and dec_bar()/enc_bar() */
.to_str = "bar", /* uses myproto_enc_to_str_bar() */
};
static const struct osmo_tlv_gen_ie_o ies_in_moo_msg[] = {
M_INST("5", bar),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_goo_msg[] = {
O_INST("3", bar),
{0}
};
static const struct osmo_tlv_gen_msg msg_defs[] = {
{ "moo", ies_in_moo_msg },
{ "goo", ies_in_goo_msg },
{0}
};
int main(int argc, const char **argv)
{
struct osmo_tlv_gen_cfg cfg = {
.proto_name = "myproto",
.message_type_enum = "enum myproto_msg_type",
.message_type_prefix = "MYPROTO_MSGT_",
.tag_enum = "enum myproto_iei",
.tag_prefix = "MYPROTO_IEI_",
.decoded_type_prefix = "struct myproto_ie_",
.h_header = "#include \"myproto_ies_custom.h\"",
.c_header = "#include <myproto_ies_auto.h>",
.msg_defs = msg_defs,
};
return osmo_tlv_gen_main(&cfg, argc, argv);
}

View File

@@ -0,0 +1,69 @@
/* Example for defining custom IES for tlv_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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <inttypes.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/msgb.h>
#include <osmocom/tlv/tlv.h>
#include <myproto_ies_custom.h>
int myproto_dec_bar(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct myproto_ie_bar *bar = decode_to;
if (tlv->len < 2)
return -EINVAL;
*bar = (struct myproto_ie_bar){
.a = tlv->val[0],
.b = (tlv->val[1] == 1),
};
return 0;
}
int myproto_enc_bar(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
struct myproto_ie_bar *bar = encode_from;
msgb_put_u8(tlv->dst, bar->a);
msgb_put_u8(tlv->dst, bar->b ? 1 : 0);
return 0;
}
int myproto_enc_to_str_bar(char *buf, size_t buflen, void *encode_from)
{
struct myproto_ie_bar *bar = encode_from;
return snprintf(buf, buflen, "%d,%s", bar->a, bar->b ? "true" : "false");
}
const struct value_string myproto_msg_type_names[] = {
{ MYPROTO_MSGT_MOO, "MOO" },
{ MYPROTO_MSGT_GOO, "GOO" },
{}
};
const struct value_string myproto_iei_names[] = {
{ MYPROTO_IEI_FOO, "FOO" },
{ MYPROTO_IEI_BAR, "BAR" },
{}
};

View File

@@ -0,0 +1,42 @@
/* Definitions for decoded message IEs, to be used by the auto-generated tlv_gen_test_tlv.c. */
#pragma once
#include <osmocom/core/utils.h>
enum myproto_msg_type {
MYPROTO_MSGT_MOO = 1,
MYPROTO_MSGT_GOO = 7,
};
extern const struct value_string myproto_msg_type_names[];
enum myproto_iei {
MYPROTO_IEI_FOO = 1,
MYPROTO_IEI_BAR,
};
extern const struct value_string myproto_iei_names[];
struct myproto_ie_bar {
int a;
bool b;
};
union myproto_ie_bar_instances {
struct {
struct myproto_ie_bar bar[5];
bool bar_present[5];
} arr;
struct {
struct myproto_ie_bar bar_one;
struct myproto_ie_bar bar_two;
struct myproto_ie_bar different_bar;
struct myproto_ie_bar fallback_bar;
struct myproto_ie_bar final_bar;
bool bar_one_present;
bool bar_two_present;
bool different_bar_present;
bool fallback_bar_present;
bool final_bar_present;
} inst;
};

View File

@@ -0,0 +1,142 @@
/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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/>.
*
*/
#include <stdio.h>
#include <errno.h>
#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/tlv/tlv.h>
#include <myproto_ies_auto.h>
struct myproto_msg {
enum myproto_msg_type type;
union myproto_ies ies;
};
static void err_cb(void *decoded_struct, const char *file, int line, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
//printf("ERR: %s:%d ", file, line);
printf("ERR: ");
vprintf(fmt, args);
va_end(args);
}
static int myproto_msg_enc(struct msgb *dst, const struct myproto_msg *msg, const struct osmo_tlv_cfg *cfg)
{
struct osmo_tlv_put tlv = {
.cfg = cfg,
.dst = dst,
};
msgb_put_u8(tlv.dst, msg->type);
return myproto_ies_encode(&tlv, (void*)&msg->ies, msg->type, err_cb, myproto_iei_names);
}
static int myproto_msg_dec(struct myproto_msg *msg, const uint8_t *data, size_t data_len,
const struct osmo_tlv_cfg *cfg, bool ordered)
{
struct osmo_tlv_load tlv;
if (data_len < 1)
return -EINVAL;
msg->type = data[0];
tlv = (struct osmo_tlv_load){
.cfg = cfg,
.src = { data + 1, data_len - 1 },
};
return myproto_ies_decode(&msg->ies, &tlv, ordered, msg->type, err_cb, myproto_iei_names);
}
void *ctx;
struct myproto_msg tests[] = {
{
MYPROTO_MSGT_MOO,
{
.moo = {
.bar = { 23, true },
},
},
},
};
int myproto_msg_to_str_buf(char *buf, size_t buflen, const struct myproto_msg *m)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
OSMO_STRBUF_PRINTF(sb, "%s={", get_value_string(myproto_msg_type_names, m->type));
OSMO_STRBUF_APPEND(sb, osmo_tlvs_encode_to_str_buf, &m->ies, 0, myproto_get_msg_coding(m->type),
myproto_iei_names);
OSMO_STRBUF_PRINTF(sb, " }");
return sb.chars_needed;
}
char *myproto_msg_to_str(const struct myproto_msg *m)
{
OSMO_NAME_C_IMPL(ctx, 256, "ERROR", myproto_msg_to_str_buf, m)
}
void test_enc_dec(const char *label, const struct osmo_tlv_cfg *cfg, bool ordered)
{
int i;
for (i = 0; i < ARRAY_SIZE(tests); i++) {
int rc;
const struct myproto_msg *orig = &tests[i];
struct myproto_msg parsed = {0};
struct msgb *msg;
printf("\n=== start %s %s[%d]\n", label, __func__, i);
printf("encoded: %s\n", myproto_msg_to_str(orig));
msg = msgb_alloc(1024, __func__),
rc = myproto_msg_enc(msg, orig, cfg);
printf("myproto_msg_enc() rc = %d\n", rc);
printf("%s.\n", osmo_hexdump(msg->data, msg->len));
rc = myproto_msg_dec(&parsed, msg->data, msg->len, cfg, ordered);
printf("myproto_msg_dec() rc = %d\n", rc);
printf("decoded: %s\n", myproto_msg_to_str(&parsed));
if (strcmp(myproto_msg_to_str(orig), myproto_msg_to_str(&parsed))) {
printf(" ERROR: parsed != orig\n");
exit(1);
}
msgb_free(msg);
printf("=== end %s %s[%d]\n", label, __func__, i);
}
}
int main()
{
ctx = talloc_named_const(NULL, 0, "test_gen_tlv");
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;
}

View File

@@ -0,0 +1,160 @@
=== start t8l8v ordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t8l8v ordered test_enc_dec[0]
=== start t8l8v ordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t8l8v ordered test_enc_dec[1]
=== start t8l8v ordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t8l8v ordered test_enc_dec[3]
=== start t8l8v ordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 01 02 00 11 02 05 67 6f 6f 65 69 08 1c 07 08 01 23 45 67 89 ab cd ef 06 10 01 02 00 0b 02 06 65 6c 65 76 65 6e 03 02 84 57 08 12 06 10 01 02 00 0c 02 06 74 77 65 6c 76 65 03 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t8l8v ordered test_enc_dec[4]
=== start t8l8v unordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t8l8v unordered test_enc_dec[0]
=== start t8l8v unordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t8l8v unordered test_enc_dec[1]
=== start t8l8v unordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t8l8v unordered test_enc_dec[3]
=== start t8l8v unordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 01 02 00 11 02 05 67 6f 6f 65 69 08 1c 07 08 01 23 45 67 89 ab cd ef 06 10 01 02 00 0b 02 06 65 6c 65 76 65 6e 03 02 84 57 08 12 06 10 01 02 00 0c 02 06 74 77 65 6c 76 65 03 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t8l8v unordered test_enc_dec[4]
=== start t16l16v ordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t16l16v ordered test_enc_dec[0]
=== start t16l16v ordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t16l16v ordered test_enc_dec[1]
=== start t16l16v ordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t16l16v ordered test_enc_dec[3]
=== start t16l16v ordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 00 01 00 02 00 11 00 02 00 05 67 6f 6f 65 69 00 08 00 26 00 07 00 08 01 23 45 67 89 ab cd ef 00 06 00 16 00 01 00 02 00 0b 00 02 00 06 65 6c 65 76 65 6e 00 03 00 02 84 57 00 08 00 1a 00 06 00 16 00 01 00 02 00 0c 00 02 00 06 74 77 65 6c 76 65 00 03 00 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t16l16v ordered test_enc_dec[4]
=== start t16l16v unordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t16l16v unordered test_enc_dec[0]
=== start t16l16v unordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t16l16v unordered test_enc_dec[1]
=== start t16l16v unordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t16l16v unordered test_enc_dec[3]
=== start t16l16v unordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 00 01 00 02 00 11 00 02 00 05 67 6f 6f 65 69 00 08 00 26 00 07 00 08 01 23 45 67 89 ab cd ef 00 06 00 16 00 01 00 02 00 0b 00 02 00 06 65 6c 65 76 65 6e 00 03 00 02 84 57 00 08 00 1a 00 06 00 16 00 01 00 02 00 0c 00 02 00 06 74 77 65 6c 76 65 00 03 00 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t16l16v unordered test_enc_dec[4]

View File

@@ -0,0 +1,60 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
-I$(bulddir) \
$(NULL)
AM_CFLAGS = \
-Wall \
$(LIBOSMOCORE_CFLAGS) \
$(NULL)
noinst_PROGRAMS = \
gen__myproto_ies_auto \
tlv_gen_test \
$(NULL)
EXTRA_DIST = \
myproto_ies_custom.h \
tlv_gen_test.ok \
$(NULL)
BUILT_SOURCES = \
myproto_ies_auto.h \
myproto_ies_auto.c \
$(NULL)
CLEANFILES = \
myproto_ies_auto.h \
myproto_ies_auto.c \
$(NULL)
gen__myproto_ies_auto_SOURCES = \
gen__myproto_ies_auto.c \
myproto_ies_custom.c \
$(NULL)
gen__myproto_ies_auto_LDADD = \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(NULL)
myproto_ies_auto.h: $(builddir)/gen__myproto_ies_auto
$(builddir)/gen__myproto_ies_auto h > $(builddir)/myproto_ies_auto.h
myproto_ies_auto.c: $(builddir)/gen__myproto_ies_auto
$(builddir)/gen__myproto_ies_auto c > $(builddir)/myproto_ies_auto.c
tlv_gen_test_SOURCES = \
tlv_gen_test.c \
myproto_ies_custom.c \
myproto_ies_auto.c \
$(NULL)
tlv_gen_test_LDADD = \
$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
$(LIBOSMOCORE_LIBS) \
$(NULL)
.PHONY: update_exp
update_exp:
$(builddir)/tlv_gen_test >$(srcdir)/tlv_gen_test.ok

View File

@@ -0,0 +1,136 @@
/*
* (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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <stdbool.h>
#include <stdio.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include <osmocom/tlv/tlv_gen.h>
#define O OSMO_TLV_GEN_O
#define M OSMO_TLV_GEN_M
#define O_MULTI OSMO_TLV_GEN_O_MULTI
#define M_MULTI OSMO_TLV_GEN_M_MULTI
static const struct osmo_tlv_gen_ie foo = {
.name = "foo",
.decoded_type = "int", /* adds 'int foo;' to the struct */
.dec_enc = "u16", /* uses myproto_dec_u16() and myproto_enc_u16() for the TLV value part */
.to_str = "u16", /* uses myproto_enc_to_str_u16() */
.spec_ref = "an int coded as uint16_t",
};
static const struct osmo_tlv_gen_ie bar = {
.name = "bar",
/* uses 'struct ie_bar bar;' and dec_bar()/enc_bar() */
.to_str = "bar", /* uses myproto_enc_to_str_bar() */
};
static const struct osmo_tlv_gen_ie baz = {
.name = "baz",
/* uses 'struct ie_baz baz;' and dec_baz()/enc_baz() */
.to_str = "baz",
};
static const struct osmo_tlv_gen_ie repeat_int = {
.name = "repeat_int",
.decoded_type = "int", /* adds 'int repeat_int;' to the struct */
.dec_enc = "u16", /* uses dec_u16() and enc_u16() for the TLV value part */
.to_str = "u16",
};
static const struct osmo_tlv_gen_ie repeat_struct = {
.name = "repeat_struct",
.to_str = "repeat_struct",
};
static const struct osmo_tlv_gen_ie_o ies_in_moo_nest[] = {
M(foo),
M(bar),
M(baz),
{0}
};
static const struct osmo_tlv_gen_ie moo_nest = {
.name = "moo_nest",
.nested_ies = ies_in_moo_nest,
};
static const struct osmo_tlv_gen_ie val = {
.name = "val",
.decoded_type = "uint64_t",
.dec_enc = "u64",
.to_str = "u64",
};
static const struct osmo_tlv_gen_ie_o ies_in_goo_nest[] = {
O(val),
M(moo_nest),
{0}
};
static const struct osmo_tlv_gen_ie goo_nest = {
.name = "goo_nest",
.nested_ies = ies_in_goo_nest,
};
static const struct osmo_tlv_gen_ie_o ies_in_moo_msg[] = {
M(foo),
M(bar),
O(baz),
O_MULTI(32, repeat_int),
O_MULTI(32, repeat_struct),
O(moo_nest),
{0}
};
static const struct osmo_tlv_gen_ie_o ies_in_goo_msg[] = {
M(foo),
O(bar),
O_MULTI(8, goo_nest),
{0}
};
static const struct osmo_tlv_gen_msg msg_defs[] = {
{ "moo", ies_in_moo_msg },
{ "goo", ies_in_goo_msg },
{0}
};
int main(int argc, const char **argv)
{
struct osmo_tlv_gen_cfg cfg = {
.proto_name = "myproto",
.message_type_enum = "enum myproto_msg_type",
.message_type_prefix = "MYPROTO_MSGT_",
.tag_enum = "enum myproto_iei",
.tag_prefix = "MYPROTO_IEI_",
.decoded_type_prefix = "struct myproto_ie_",
.h_header = "#include \"myproto_ies_custom.h\"",
.c_header = "#include <myproto_ies_auto.h>",
.msg_defs = msg_defs,
};
return osmo_tlv_gen_main(&cfg, argc, argv);
}

View File

@@ -0,0 +1,179 @@
/* Example for defining custom IES for tlv_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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <inttypes.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/msgb.h>
#include <osmocom/tlv/tlv.h>
#include <myproto_ies_custom.h>
int myproto_dec_u16(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
int *foo = decode_to;
if (tlv->len != 2)
return -EINVAL;
*foo = osmo_load16be(tlv->val);
return 0;
}
int myproto_enc_u16(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
int *foo = encode_from;
if (*foo > INT16_MAX)
return -EINVAL;
msgb_put_u16(tlv->dst, *foo);
return 0;
}
int myproto_enc_to_str_u16(char *buf, size_t buflen, void *encode_from)
{
int *foo = encode_from;
return snprintf(buf, buflen, "%d", *foo);
}
int myproto_dec_u64(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
uint64_t *val = decode_to;
if (tlv->len != sizeof(uint64_t))
return -EINVAL;
*val = osmo_load64be(tlv->val);
return 0;
}
int myproto_enc_u64(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
uint64_t *val = encode_from;
osmo_store64be(*val, msgb_put(tlv->dst, sizeof(*val)));
return 0;
}
int myproto_enc_to_str_u64(char *buf, size_t buflen, void *encode_from)
{
uint64_t *val = encode_from;
return snprintf(buf, buflen, "0x%"PRIx64, *val);
}
int myproto_dec_bar(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct myproto_ie_bar *bar = decode_to;
if (tlv->len > sizeof(bar->str) - 1)
return -EINVAL;
osmo_strlcpy(bar->str, (const char*)tlv->val, OSMO_MIN(tlv->len + 1, sizeof(bar->str)));
return 0;
}
int myproto_enc_bar(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
struct myproto_ie_bar *bar = encode_from;
int len = strnlen(bar->str, sizeof(bar->str));
memcpy(msgb_put(tlv->dst, len), bar, len);
return 0;
}
int myproto_enc_to_str_bar(char *buf, size_t buflen, void *encode_from)
{
struct myproto_ie_bar *bar = encode_from;
return osmo_quote_str_buf3(buf, buflen, bar->str, -1);
}
int myproto_dec_baz(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct myproto_ie_baz *baz = decode_to;
uint16_t l;
if (tlv->len != 2)
return -EINVAL;
l = osmo_load16be(tlv->val);
baz->v_int = l & 0x7fff;
baz->v_bool = (l & 0x8000) ? true : false;
return 0;
}
int myproto_enc_baz(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
struct myproto_ie_baz *baz = encode_from;
if (baz->v_int > 0x7fff)
return -EINVAL;
msgb_put_u16(tlv->dst, (baz->v_bool ? 0x8000 : 0) + (baz->v_int & 0x7fff));
return 0;
}
int myproto_enc_to_str_baz(char *buf, size_t buflen, void *encode_from)
{
struct myproto_ie_baz *baz = encode_from;
return snprintf(buf, buflen, "{%d,%s}", baz->v_int, baz->v_bool ? "true" : "false");
}
int myproto_dec_repeat_struct(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct myproto_ie_repeat_struct *repeat_struct = decode_to;
if (tlv->len != 3)
return -EINVAL;
repeat_struct->v_int = osmo_load16be(tlv->val);
repeat_struct->v_bool = tlv->val[2] & 0x80;
repeat_struct->v_enum = tlv->val[2] & 0x7f;
return 0;
}
int myproto_enc_repeat_struct(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
struct myproto_ie_repeat_struct *repeat_struct = encode_from;
msgb_put_u16(tlv->dst, repeat_struct->v_int);
msgb_put_u8(tlv->dst, (repeat_struct->v_bool ? 0x80 : 0) + (repeat_struct->v_enum & 0x7f));
return 0;
}
int myproto_enc_to_str_repeat_struct(char *buf, size_t buflen, void *encode_from)
{
struct myproto_ie_repeat_struct *repeat_struct = encode_from;
return snprintf(buf, buflen, "{%d,%s,%s}",
repeat_struct->v_int, repeat_struct->v_bool ? "true" : "false",
get_value_string(myproto_repeat_enum_names, repeat_struct->v_enum));
}
const struct value_string myproto_msg_type_names[] = {
{ MYPROTO_MSGT_MOO, "MOO" },
{ MYPROTO_MSGT_GOO, "GOO" },
{}
};
const struct value_string myproto_iei_names[] = {
{ MYPROTO_IEI_FOO, "FOO" },
{ MYPROTO_IEI_BAR, "BAR" },
{ MYPROTO_IEI_BAZ, "BAZ" },
{ MYPROTO_IEI_REPEAT_INT, "REPEAT_INT" },
{ MYPROTO_IEI_REPEAT_STRUCT, "REPEAT_STRUCT" },
{ MYPROTO_IEI_MOO_NEST, "MOO_NEST" },
{ MYPROTO_IEI_VAL, "VAL" },
{ MYPROTO_IEI_GOO_NEST, "GOO_NEST" },
{}
};
const struct value_string myproto_repeat_enum_names[] = {
OSMO_VALUE_STRING(R_A),
OSMO_VALUE_STRING(R_B),
OSMO_VALUE_STRING(R_C),
{}
};

View File

@@ -0,0 +1,47 @@
/* Definitions for decoded message IEs, to be used by the auto-generated tlv_gen_test_tlv.c. */
#pragma once
#include <osmocom/core/utils.h>
enum myproto_msg_type {
MYPROTO_MSGT_MOO = 1,
MYPROTO_MSGT_GOO = 7,
};
extern const struct value_string myproto_msg_type_names[];
enum myproto_iei {
MYPROTO_IEI_FOO = 1,
MYPROTO_IEI_BAR,
MYPROTO_IEI_BAZ,
MYPROTO_IEI_REPEAT_INT,
MYPROTO_IEI_REPEAT_STRUCT,
MYPROTO_IEI_MOO_NEST,
MYPROTO_IEI_VAL,
MYPROTO_IEI_GOO_NEST,
};
extern const struct value_string myproto_iei_names[];
struct myproto_ie_bar {
char str[23];
};
struct myproto_ie_baz {
int v_int;
bool v_bool;
};
enum myproto_repeat_enum {
R_A,
R_B,
R_C,
};
extern const struct value_string myproto_repeat_enum_names[];
struct myproto_ie_repeat_struct {
int v_int;
bool v_bool;
enum myproto_repeat_enum v_enum;
};

View File

@@ -0,0 +1,256 @@
/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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/>.
*
*/
#include <stdio.h>
#include <errno.h>
#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/tlv/tlv.h>
#include <myproto_ies_auto.h>
struct myproto_msg {
enum myproto_msg_type type;
union myproto_ies ies;
};
static void err_cb(void *decoded_struct, const char *file, int line, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
//printf("ERR: %s:%d ", file, line);
printf("ERR: ");
vprintf(fmt, args);
va_end(args);
}
static int myproto_msg_enc(struct msgb *dst, const struct myproto_msg *msg, const struct osmo_tlv_cfg *cfg)
{
struct osmo_tlv_put tlv = {
.cfg = cfg,
.dst = dst,
};
msgb_put_u8(tlv.dst, msg->type);
return myproto_ies_encode(&tlv, (void*)&msg->ies, msg->type, err_cb, myproto_iei_names);
}
static int myproto_msg_dec(struct myproto_msg *msg, const uint8_t *data, size_t data_len,
const struct osmo_tlv_cfg *cfg, bool ordered)
{
struct osmo_tlv_load tlv;
if (data_len < 1)
return -EINVAL;
msg->type = data[0];
tlv = (struct osmo_tlv_load){
.cfg = cfg,
.src = { data + 1, data_len - 1 },
};
return myproto_ies_decode(&msg->ies, &tlv, ordered, msg->type, err_cb, myproto_iei_names);
}
void *ctx;
struct myproto_msg tests[] = {
{
MYPROTO_MSGT_MOO,
{
.moo = {
.foo = 23,
.bar = { "twentythree" },
},
},
},
{
MYPROTO_MSGT_MOO,
{
.moo = {
.foo = 23,
.bar = { "twentythree" },
.baz_present = true,
.baz = {
.v_int = 2323,
.v_bool = true,
},
},
},
},
{
MYPROTO_MSGT_MOO,
{
.moo = {
.foo = 23,
.bar = { "twentythree" },
.baz_present = true,
.baz = {
.v_int = 2323,
.v_bool = true,
},
.repeat_int_count = 3,
.repeat_int = { 1, 2, 0x7fff },
},
},
},
{
MYPROTO_MSGT_MOO,
{
.moo = {
.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,
},
},
.moo_nest_present = true,
.moo_nest = {
.foo = 42,
.bar = { "fortytwo" },
.baz = {
.v_int = 4242,
.v_bool = false,
},
},
},
},
},
{
MYPROTO_MSGT_GOO,
{
.goo = {
.foo = 17,
.bar_present = true,
.bar = { "gooei" },
.goo_nest_count = 2,
.goo_nest = {
{
.val_present = true,
.val = 0x0123456789abcdef,
.moo_nest = {
.foo = 11,
.bar = { "eleven" },
.baz = {
.v_int = 1111,
.v_bool = true,
},
},
},
{
.val_present = false,
.moo_nest = {
.foo = 12,
.bar = { "twelve" },
.baz = {
.v_int = 1212,
.v_bool = false,
},
},
},
},
},
},
},
};
int myproto_msg_to_str_buf(char *buf, size_t buflen, const struct myproto_msg *m)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
OSMO_STRBUF_PRINTF(sb, "%s={", get_value_string(myproto_msg_type_names, m->type));
OSMO_STRBUF_APPEND(sb, osmo_tlvs_encode_to_str_buf, &m->ies, 0, myproto_get_msg_coding(m->type),
myproto_iei_names);
OSMO_STRBUF_PRINTF(sb, " }");
return sb.chars_needed;
}
char *myproto_msg_to_str(const struct myproto_msg *m)
{
OSMO_NAME_C_IMPL(ctx, 256, "ERROR", myproto_msg_to_str_buf, m)
}
void test_enc_dec(const char *label, const struct osmo_tlv_cfg *cfg, bool ordered)
{
int i;
for (i = 0; i < ARRAY_SIZE(tests); i++) {
int rc;
const struct myproto_msg *orig = &tests[i];
struct myproto_msg parsed = {0};
struct msgb *msg;
printf("\n=== start %s %s[%d]\n", label, __func__, i);
printf("encoded: %s\n", myproto_msg_to_str(orig));
msg = msgb_alloc(1024, __func__),
rc = myproto_msg_enc(msg, orig, cfg);
printf("myproto_msg_enc() rc = %d\n", rc);
printf("%s.\n", osmo_hexdump(msg->data, msg->len));
rc = myproto_msg_dec(&parsed, msg->data, msg->len, cfg, ordered);
printf("myproto_msg_dec() rc = %d\n", rc);
printf("decoded: %s\n", myproto_msg_to_str(&parsed));
if (strcmp(myproto_msg_to_str(orig), myproto_msg_to_str(&parsed))) {
printf(" ERROR: parsed != orig\n");
exit(1);
}
msgb_free(msg);
printf("=== end %s %s[%d]\n", label, __func__, i);
}
}
int main()
{
ctx = talloc_named_const(NULL, 0, "test_gen_tlv");
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;
}

View File

@@ -0,0 +1,160 @@
=== start t8l8v ordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t8l8v ordered test_enc_dec[0]
=== start t8l8v ordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t8l8v ordered test_enc_dec[1]
=== start t8l8v ordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t8l8v ordered test_enc_dec[3]
=== start t8l8v ordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 01 02 00 11 02 05 67 6f 6f 65 69 08 1c 07 08 01 23 45 67 89 ab cd ef 06 10 01 02 00 0b 02 06 65 6c 65 76 65 6e 03 02 84 57 08 12 06 10 01 02 00 0c 02 06 74 77 65 6c 76 65 03 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t8l8v ordered test_enc_dec[4]
=== start t8l8v unordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t8l8v unordered test_enc_dec[0]
=== start t8l8v unordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t8l8v unordered test_enc_dec[1]
=== start t8l8v unordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t8l8v unordered test_enc_dec[3]
=== start t8l8v unordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 01 02 00 11 02 05 67 6f 6f 65 69 08 1c 07 08 01 23 45 67 89 ab cd ef 06 10 01 02 00 0b 02 06 65 6c 65 76 65 6e 03 02 84 57 08 12 06 10 01 02 00 0c 02 06 74 77 65 6c 76 65 03 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t8l8v unordered test_enc_dec[4]
=== start t16l16v ordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t16l16v ordered test_enc_dec[0]
=== start t16l16v ordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t16l16v ordered test_enc_dec[1]
=== start t16l16v ordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t16l16v ordered test_enc_dec[3]
=== start t16l16v ordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 00 01 00 02 00 11 00 02 00 05 67 6f 6f 65 69 00 08 00 26 00 07 00 08 01 23 45 67 89 ab cd ef 00 06 00 16 00 01 00 02 00 0b 00 02 00 06 65 6c 65 76 65 6e 00 03 00 02 84 57 00 08 00 1a 00 06 00 16 00 01 00 02 00 0c 00 02 00 06 74 77 65 6c 76 65 00 03 00 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t16l16v ordered test_enc_dec[4]
=== start t16l16v unordered test_enc_dec[0]
encoded: MOO={ FOO=23 BAR="twentythree" }
myproto_msg_enc() rc = 0
01 00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" }
=== end t16l16v unordered test_enc_dec[0]
=== start t16l16v unordered test_enc_dec[1]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} }
=== end t16l16v unordered test_enc_dec[1]
=== start t16l16v unordered test_enc_dec[2]
encoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ 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: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
myproto_msg_enc() rc = 0
01 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 .
myproto_msg_dec() rc = 0
decoded: MOO={ FOO=23 BAR="twentythree" BAZ={2323,true} REPEAT_INT={ 1, 2, 32767 } REPEAT_STRUCT={ {1001,true,R_A}, {1002,false,R_B} } MOO_NEST={ FOO=42 BAR="fortytwo" BAZ={4242,false} } }
=== end t16l16v unordered test_enc_dec[3]
=== start t16l16v unordered test_enc_dec[4]
encoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
myproto_msg_enc() rc = 0
07 00 01 00 02 00 11 00 02 00 05 67 6f 6f 65 69 00 08 00 26 00 07 00 08 01 23 45 67 89 ab cd ef 00 06 00 16 00 01 00 02 00 0b 00 02 00 06 65 6c 65 76 65 6e 00 03 00 02 84 57 00 08 00 1a 00 06 00 16 00 01 00 02 00 0c 00 02 00 06 74 77 65 6c 76 65 00 03 00 02 04 bc .
myproto_msg_dec() rc = 0
decoded: GOO={ FOO=17 BAR="gooei" GOO_NEST={ { VAL=0x123456789abcdef MOO_NEST={ FOO=11 BAR="eleven" BAZ={1111,true} } }, { MOO_NEST={ FOO=12 BAR="twelve" BAZ={1212,false} } } } }
=== end t16l16v unordered test_enc_dec[4]

View File

@@ -0,0 +1,413 @@
/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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/>.
*
*/
#include <stdio.h>
#include <errno.h>
#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/tlv/tlv_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_tlv_load *tlv)
{
int *foo = decode_to;
if (tlv->len != 2)
return -EINVAL;
*foo = osmo_load16be(tlv->val);
return 0;
}
int enc_u16(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
int *foo = encode_from;
if (*foo > INT16_MAX)
return -EINVAL;
msgb_put_u16(tlv->dst, *foo);
return 0;
}
int enc_to_str_u16(char *buf, size_t buflen, void *encode_from)
{
int *foo = encode_from;
return snprintf(buf, buflen, "%d", *foo);
}
int dec_bar(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
{
struct bar *bar = decode_to;
if (tlv->len > sizeof(bar->str) - 1)
return -EINVAL;
osmo_strlcpy(bar->str, (const char*)tlv->val, OSMO_MIN(tlv->len + 1, sizeof(bar->str)));
return 0;
}
int enc_bar(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
struct bar *bar = encode_from;
int len = strnlen(bar->str, sizeof(bar->str));
memcpy(msgb_put(tlv->dst, len), bar, len);
return 0;
}
int enc_to_str_bar(char *buf, size_t buflen, void *encode_from)
{
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_tlv_load *tlv)
{
struct baz *baz = decode_to;
uint16_t l;
if (tlv->len != 2)
return -EINVAL;
l = osmo_load16be(tlv->val);
baz->v_int = l & 0x7fff;
baz->v_bool = (l & 0x8000) ? true : false;
return 0;
}
int enc_baz(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
struct baz *baz = encode_from;
if (baz->v_int > 0x7fff)
return -EINVAL;
msgb_put_u16(tlv->dst, (baz->v_bool ? 0x8000 : 0) + (baz->v_int & 0x7fff));
return 0;
}
int enc_to_str_baz(char *buf, size_t buflen, void *encode_from)
{
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_tlv_load *tlv)
{
struct repeat *repeat_struct = decode_to;
if (tlv->len != 3)
return -EINVAL;
repeat_struct->v_int = osmo_load16be(tlv->val);
repeat_struct->v_bool = tlv->val[2] & 0x80;
repeat_struct->v_enum = tlv->val[2] & 0x7f;
return 0;
}
int enc_repeat_struct(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
{
struct repeat *repeat_struct = encode_from;
msgb_put_u16(tlv->dst, repeat_struct->v_int);
msgb_put_u8(tlv->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, void *encode_from)
{
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_tlv_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_tlv_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_tlvs_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,
},
},
},
};
void err_cb(void *decoded_struct, const char *file, int line, const char *fmt, ...)
{
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_tlv_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 = {0};
struct osmo_tlv_load load;
struct osmo_tlv_put put;
printf("\n=== start %s %s[%d]\n", label, __func__, i);
printf("encoded: %s\n", decoded_msg_to_str(orig));
put = (struct osmo_tlv_put){
.cfg = cfg,
.dst = msgb_alloc(1024, __func__),
};
rc = osmo_tlvs_encode(&put, (void*)orig, 0, msg_ie_coding, err_cb, tag_names);
printf("osmo_tlvs_encode() rc = %d\n", rc);
printf("%s.\n", osmo_hexdump(put.dst->data, put.dst->len));
load = (struct osmo_tlv_load){
.cfg = cfg,
.src = { put.dst->data, put.dst->len },
};
rc = osmo_tlvs_decode(&parsed, 0, &load, ordered, msg_ie_coding, err_cb, tag_names);
printf("osmo_tlvs_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, "tlv_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;
}

View File

@@ -0,0 +1,128 @@
=== start t8l8v ordered test_enc_dec[0]
encoded: FOO=23 BAR="twentythree"
osmo_tlvs_encode() rc = 0
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
osmo_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_encode() rc = 0
01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
osmo_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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_tlvs_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]

View File

@@ -0,0 +1,623 @@
/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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/>.
*
*/
#include <stdio.h>
#include <errno.h>
#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/tlv/tlv.h>
void *ctx;
struct ie {
struct osmo_tlv_tag_inst ti;
const char *val;
};
/* write all IEs to a msgb */
struct msgb *test_tlv_enc(const struct osmo_tlv_cfg *cfg, const struct ie *ies)
{
const struct ie *ie;
struct osmo_tlv_put tlv = {
.cfg = cfg,
.dst = msgb_alloc(1024, __func__),
};
for (ie = ies; ie->val; ie++) {
/* put header without knowing length yet */
OSMO_ASSERT(osmo_tlv_put_tli(&tlv, &ie->ti, 0) == 0);
/* put value data, as much as desired */
msgb_put(tlv.dst, osmo_hexparse(ie->val, tlv.dst->tail, msgb_tailroom(tlv.dst)));
/* update header len from amount of written data */
OSMO_ASSERT(osmo_tlv_put_update_tl(&tlv) == 0);
}
printf("- encoded: %s.\n", osmo_hexdump(tlv.dst->data, tlv.dst->len));
return tlv.dst;
}
/* read all IEs from the msgb, and verify that it matches the given list of IEs */
void test_tlv_dec(const struct osmo_tlv_cfg *cfg, const struct ie *ies, struct msgb *msg)
{
const struct ie *ie;
struct osmo_tlv_load tlv = {
.cfg = cfg,
.src = { msg->data, msg->len },
};
printf("- decoding:\n");
osmo_tlv_load_start(&tlv);
for (ie = ies; ie->val; ie++) {
int rc = osmo_tlv_load_next(&tlv);
if (rc) {
printf(" ERROR loading TLV structure: osmo_tlv_load_next() rc = %d\n", rc);
exit(1);
}
/* end of TLV structure? */
if (!tlv.val)
break;
printf(" T=%d L=%zu", tlv.tag, tlv.len);
if (tlv.instance != OSMO_TLV_NO_INSTANCE)
printf(" I=%u", tlv.instance);
printf(" v=%s\n", osmo_hexdump_nospc(tlv.val, tlv.len));
if (tlv.tag != ie->tag) {
printf(" ERROR loading TLV structure: expected tag %d, got tag %d\n", ie->tag, tlv.tag);
exit(1);
}
if (strcmp(ie->val, osmo_hexdump_nospc(tlv.val, tlv.len))) {
printf(" ERROR loading TLV structure: expected val %s, got val %s\n", ie->val,
osmo_hexdump_nospc(tlv.val, tlv.len));
exit(1);
}
}
}
void test_tlv_peek(const struct osmo_tlv_cfg *cfg, const struct ie *ies, struct msgb *msg)
{
const struct ie *ie;
struct osmo_tlv_load tlv = {
.cfg = cfg,
.src = { msg->data, msg->len },
};
printf("- peeking:\n");
osmo_tlv_load_start(&tlv);
ie = ies;
while (1) {
int rc;
unsigned int next_tag;
unsigned int next_instance;
rc = osmo_tlv_load_peek_tag(&tlv, &next_tag, &next_instance);
if (rc == -ENOENT) {
printf(" peek rc=-ENOENT\n");
} else {
printf(" peek T=%u", next_tag);
if (next_instance != OSMO_TLV_NO_INSTANCE)
printf(" I=%u", next_instance);
printf("\n");
}
if (ie->val && next_tag != ie->tag) {
printf(" ERROR peeking tag: expected tag %u, got tag %u\n", ie->tag, next_tag);
exit(1);
}
if (ie->val && ie->instance && next_instance != ie->instance) {
printf(" ERROR peeking tag: expected instance %u, got instance %u\n", ie->instance,
next_instance);
exit(1);
}
if (!ie->val && rc != -ENOENT) {
printf(" ERROR peeking tag: expected -ENOENT, got rc=%d, tag %u\n", rc, next_tag);
exit(1);
}
if (rc == -ENOENT)
break;
/* go to the next TLV */
rc = osmo_tlv_load_next(&tlv);
if (rc) {
printf(" ERROR loading TLV structure: osmo_tlv_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_tlv_cfg *cfg, const struct ie *ies, struct msgb *msg)
{
const struct ie *last_ie;
const struct ie *ie;
int rc;
struct osmo_tlv_load tlv = {
.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_tlv_load_start(&tlv);
rc = osmo_tlv_load_next_by_tag(&tlv, ie->tag, ie->instance ? : OSMO_TLV_NO_INSTANCE);
if (rc) {
printf(" ERROR loading TLV structure: osmo_tlv_load_next_by_tag(%d) rc = %d\n", ie->tag, rc);
exit(1);
}
if (!tlv.val) {
printf(" ERROR loading TLV structure: osmo_tlv_load_next_by_tag(%d) returned NULL val\n",
ie->tag);
exit(1);
}
if (tlv.tag != ie->tag) {
printf(" ERROR loading TLV structure: expected tag %d, got tag %d\n", ie->tag, tlv.tag);
exit(1);
}
if (strcmp(ie->val, osmo_hexdump_nospc(tlv.val, tlv.len))) {
while (1) {
printf(" (mismatch: T=%u L=%zu", tlv.tag, tlv.len);
if (tlv.instance != OSMO_TLV_NO_INSTANCE)
printf(" I=%u", tlv.instance);
printf(" v=%s, checking for another occurrence of T=%d)\n",
osmo_hexdump_nospc(tlv.val, tlv.len), tlv.tag);
rc = osmo_tlv_load_next_by_tag(&tlv, ie->tag, ie->instance ? : OSMO_TLV_NO_INSTANCE);
if (rc || !tlv.val) {
printf(" ERROR val not found\n");
exit(1);
}
if (strcmp(ie->val, osmo_hexdump_nospc(tlv.val, tlv.len)) == 0) {
break;
}
}
}
printf(" T=%d L=%zu", tlv.tag, tlv.len);
if (tlv.instance != OSMO_TLV_NO_INSTANCE)
printf(" I=%u", tlv.instance);
printf(" v=%s\n", osmo_hexdump_nospc(tlv.val, tlv.len));
}
printf("- decoding every second tag:\n");
osmo_tlv_load_start(&tlv);
for (ie = ies; ie->val; ie++) {
/* skip one tag */
ie++;
if (!ie->val)
break;
rc = osmo_tlv_load_next_by_tag(&tlv, ie->tag, ie->instance ? : OSMO_TLV_NO_INSTANCE);
if (rc) {
printf(" ERROR loading TLV structure: osmo_tlv_load_next_by_tag(%d) rc = %d\n", ie->tag, rc);
exit(1);
}
if (!tlv.val) {
printf(" ERROR loading TLV structure: osmo_tlv_load_next_by_tag(%d) returned NULL val\n",
ie->tag);
exit(1);
}
if (tlv.tag != ie->tag) {
printf(" ERROR loading TLV structure: expected tag %d, got tag %d\n", ie->tag, tlv.tag);
exit(1);
}
if (strcmp(ie->val, osmo_hexdump_nospc(tlv.val, tlv.len))) {
while (1) {
printf(" (mismatch: T=%u L=%zu", tlv.tag, tlv.len);
if (tlv.instance != OSMO_TLV_NO_INSTANCE)
printf(" I=%u", tlv.instance);
printf(" v=%s, checking for another occurrence of T=%d)\n",
osmo_hexdump_nospc(tlv.val, tlv.len), tlv.tag);
rc = osmo_tlv_load_next_by_tag(&tlv, ie->tag, ie->instance ? : OSMO_TLV_NO_INSTANCE);
if (rc || !tlv.val) {
printf(" ERROR val not found\n");
exit(1);
}
if (strcmp(ie->val, osmo_hexdump_nospc(tlv.val, tlv.len)) == 0) {
break;
}
}
}
printf(" T=%d L=%zu", tlv.tag, tlv.len);
if (tlv.instance != OSMO_TLV_NO_INSTANCE)
printf(" I=%u", tlv.instance);
printf(" v=%s\n", osmo_hexdump_nospc(tlv.val, tlv.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_tlv_load_next_by_tag(&tlv, ie->tag, ie->instance ? : OSMO_TLV_NO_INSTANCE);
printf(" osmo_tlv_load_next_by_tag(%u, %u) rc=%d", ie->tag, ie->instance ? : OSMO_TLV_NO_INSTANCE, rc);
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_tlv_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 */
{ 0, "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 */
{ 0, "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 */
{ 0, "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_tlv_load *tlv, 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;
tlv->tag = (((int)pos[1]) << 7) + (pos[0] & 0x7f);
pos += 2;
} else {
tlv->tag = pos[0];
pos++;
}
switch (tlv->tag) {
case 1002:
/* fixed-length IE */
tlv->len = 2;
break;
case 123:
/* 16bit length IE */
if (pos + 2 > end)
return -EINVAL;
tlv->len = osmo_load16be(pos);
pos += 2;
break;
default:
/* 8bit length IE */
if (pos + 1 > end)
return -EINVAL;
tlv->len = *pos;
pos++;
break;
}
tlv->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, unsigned int tag, unsigned int instance, size_t len,
struct osmo_tlv_put *tlv)
{
uint8_t *pos = dst_data;
uint8_t *end = dst_data + dst_data_avail;
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_tlv_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_tlv_load *tlv, const uint8_t *src_data, size_t src_data_len)
{
/* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 2. */
tlv->tag = src_data[0];
tlv->len = src_data[1];
switch (tlv->tag) {
/* All tags that are TLIV go here */
case 5:
case 7:
case 9:
if (src_data_len < 3)
return -ENOSPC;
tlv->instance = src_data[2];
tlv->val = src_data + 3;
return 0;
default:
tlv->val = src_data + 2;
return 0;
}
}
static int tliv_store_tl(uint8_t *dst_data, size_t dst_data_avail, unsigned int tag, unsigned int instance, size_t len,
struct osmo_tlv_put *tlv)
{
if (tag > UINT8_MAX)
return -EINVAL;
if (len > UINT8_MAX)
return -EMSGSIZE;
if (dst_data_avail < 2)
return -ENOSPC;
dst_data[0] = tag;
dst_data[1] = len;
switch (tag) {
/* All tags that are TLIV go here */
case 5:
case 7:
case 9:
if (dst_data_avail < 3)
return -ENOSPC;
if (instance > UINT8_MAX)
return -EINVAL;
dst_data[2] = instance;
return 3;
default:
return 2;
}
}
const struct osmo_tlv_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, "0017", 1 },
/* TLIV */
{ 5, "0018", 2 },
/* TLIV */
{ 5, "0019", 3 },
/* TLV */
{ 6, "001a" },
/* TLIV */
{ 7, "001b", 1 },
/* TLIV */
{ 9, "001c", 1 },
{}
};
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, "tlv_test");
msgb_talloc_ctx_init(ctx, 0);
test_t8l8v();
test_t16l16v();
test_txlxv();
test_tliv();
talloc_free(ctx);
return 0;
}

View File

@@ -0,0 +1,256 @@
=== start: test_t8l8v[0]
- encoded: 00 02 23 42 ff 02 23 42 01 00 7b ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 65 01 11 66 02 22 22 67 03 33 33 33 .
- decoding:
T=0 L=2 v=2342
T=255 L=2 v=2342
T=1 L=0 v=
T=123 L=255 v=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
T=101 L=1 v=11
T=102 L=2 v=2222
T=103 L=3 v=333333
- peeking:
peek T=0
peek T=255
peek T=1
peek T=123
peek T=101
peek T=102
peek T=103
peek T=-ENOENT
- decoding in reverse order:
T=103 L=3 v=333333
T=102 L=2 v=2222
T=101 L=1 v=11
T=123 L=255 v=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
T=1 L=0 v=
T=255 L=2 v=2342
T=0 L=2 v=2342
- decoding every second tag:
T=255 L=2 v=2342
T=123 L=255 v=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
T=102 L=2 v=2222
- enforcing order: without restart, a past tag is not parsed again:
osmo_tlv_load_next_by_tag(0) rc=-ENOENT
=== end: test_t8l8v[0]
=== start: test_t8l8v[1]
- encoded: 2a 01 42 02 02 01 01 02 02 22 22 03 01 11 03 02 22 22 03 03 33 33 33 17 01 23 2a 08 66 6f 72 74 79 74 77 6f 17 0b 74 77 65 6e 74 79 74 68 72 65 65 .
- decoding:
T=42 L=1 v=42
T=2 L=2 v=0101
T=2 L=2 v=2222
T=3 L=1 v=11
T=3 L=2 v=2222
T=3 L=3 v=333333
T=23 L=1 v=23
T=42 L=8 v=666f72747974776f
T=23 L=11 v=7477656e74797468726565
- peeking:
peek T=42
peek T=2
peek T=2
peek T=3
peek T=3
peek T=3
peek T=23
peek T=42
peek T=23
peek T=-ENOENT
- decoding in reverse order:
(mismatch: T=23 L=1 v=23, checking for another occurrence of T=23)
T=23 L=11 v=7477656e74797468726565
(mismatch: T=42 L=1 v=42, checking for another occurrence of T=42)
T=42 L=8 v=666f72747974776f
T=23 L=1 v=23
(mismatch: T=3 L=1 v=11, checking for another occurrence of T=3)
(mismatch: T=3 L=2 v=2222, checking for another occurrence of T=3)
T=3 L=3 v=333333
(mismatch: T=3 L=1 v=11, checking for another occurrence of T=3)
T=3 L=2 v=2222
T=3 L=1 v=11
(mismatch: T=2 L=2 v=0101, checking for another occurrence of T=2)
T=2 L=2 v=2222
T=2 L=2 v=0101
T=42 L=1 v=42
- decoding every second tag:
T=2 L=2 v=0101
T=3 L=1 v=11
(mismatch: T=3 L=2 v=2222, checking for another occurrence of T=3)
T=3 L=3 v=333333
T=42 L=8 v=666f72747974776f
- enforcing order: without restart, a past tag is not parsed again:
osmo_tlv_load_next_by_tag(42) rc=-ENOENT
=== end: test_t8l8v[1]
=== start: test_t16l16v[0]
- encoded: 00 00 00 02 23 42 ff ff 00 02 23 42 00 01 00 00 00 7b 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 e9 00 01 11 03 ea 00 02 22 22 03 eb 00 03 33 33 33 .
- decoding:
T=0 L=2 v=2342
T=65535 L=2 v=2342
T=1 L=0 v=
T=123 L=256 v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
T=1001 L=1 v=11
T=1002 L=2 v=2222
T=1003 L=3 v=333333
- peeking:
peek T=0
peek T=65535
peek T=1
peek T=123
peek T=1001
peek T=1002
peek T=1003
peek T=-ENOENT
- decoding in reverse order:
T=1003 L=3 v=333333
T=1002 L=2 v=2222
T=1001 L=1 v=11
T=123 L=256 v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
T=1 L=0 v=
T=65535 L=2 v=2342
T=0 L=2 v=2342
- decoding every second tag:
T=65535 L=2 v=2342
T=123 L=256 v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
T=1002 L=2 v=2222
- enforcing order: without restart, a past tag is not parsed again:
osmo_tlv_load_next_by_tag(0) rc=-ENOENT
=== end: test_t16l16v[0]
=== start: test_t16l16v[1]
- encoded: 04 12 00 01 42 00 66 00 02 01 01 00 66 00 02 22 22 00 67 00 01 11 00 67 00 02 22 22 00 67 00 03 33 33 33 03 ff 00 01 23 04 12 00 08 66 6f 72 74 79 74 77 6f 03 ff 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
- decoding:
T=1042 L=1 v=42
T=102 L=2 v=0101
T=102 L=2 v=2222
T=103 L=1 v=11
T=103 L=2 v=2222
T=103 L=3 v=333333
T=1023 L=1 v=23
T=1042 L=8 v=666f72747974776f
T=1023 L=11 v=7477656e74797468726565
- peeking:
peek T=1042
peek T=102
peek T=102
peek T=103
peek T=103
peek T=103
peek T=1023
peek T=1042
peek T=1023
peek T=-ENOENT
- decoding in reverse order:
(mismatch: T=1023 L=1 v=23, checking for another occurrence of T=1023)
T=1023 L=11 v=7477656e74797468726565
(mismatch: T=1042 L=1 v=42, checking for another occurrence of T=1042)
T=1042 L=8 v=666f72747974776f
T=1023 L=1 v=23
(mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
(mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
T=103 L=3 v=333333
(mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
T=103 L=2 v=2222
T=103 L=1 v=11
(mismatch: T=102 L=2 v=0101, checking for another occurrence of T=102)
T=102 L=2 v=2222
T=102 L=2 v=0101
T=1042 L=1 v=42
- decoding every second tag:
T=102 L=2 v=0101
T=103 L=1 v=11
(mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
T=103 L=3 v=333333
T=1042 L=8 v=666f72747974776f
- enforcing order: without restart, a past tag is not parsed again:
osmo_tlv_load_next_by_tag(1042) rc=-ENOENT
=== end: test_t16l16v[1]
=== start: txlxv_tests[0]
- encoded: 00 02 23 42 7f 02 23 42 80 01 02 23 42 ff ff 02 23 42 01 00 7b 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ea 07 22 22 eb 07 03 33 33 33 .
- decoding:
T=0 L=2 v=2342
T=127 L=2 v=2342
T=128 L=2 v=2342
T=32767 L=2 v=2342
T=1 L=0 v=
T=123 L=256 v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
T=1002 L=2 v=2222
T=1003 L=3 v=333333
- peeking:
peek T=0
peek T=127
peek T=128
peek T=32767
peek T=1
peek T=123
peek T=1002
peek T=1003
peek T=-ENOENT
- decoding in reverse order:
T=1003 L=3 v=333333
T=1002 L=2 v=2222
T=123 L=256 v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
T=1 L=0 v=
T=32767 L=2 v=2342
T=128 L=2 v=2342
T=127 L=2 v=2342
T=0 L=2 v=2342
- decoding every second tag:
T=127 L=2 v=2342
T=32767 L=2 v=2342
T=123 L=256 v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
T=1003 L=3 v=333333
- enforcing order: without restart, a past tag is not parsed again:
osmo_tlv_load_next_by_tag(0) rc=-ENOENT
=== end: txlxv_tests[0]
=== start: txlxv_tests[1]
- encoded: 92 08 01 42 ea 07 01 01 ea 07 22 22 67 01 11 67 02 22 22 67 03 33 33 33 ff 07 01 23 92 08 08 66 6f 72 74 79 74 77 6f ff 07 0b 74 77 65 6e 74 79 74 68 72 65 65 .
- decoding:
T=1042 L=1 v=42
T=1002 L=2 v=0101
T=1002 L=2 v=2222
T=103 L=1 v=11
T=103 L=2 v=2222
T=103 L=3 v=333333
T=1023 L=1 v=23
T=1042 L=8 v=666f72747974776f
T=1023 L=11 v=7477656e74797468726565
- peeking:
peek T=1042
peek T=1002
peek T=1002
peek T=103
peek T=103
peek T=103
peek T=1023
peek T=1042
peek T=1023
peek T=-ENOENT
- decoding in reverse order:
(mismatch: T=1023 L=1 v=23, checking for another occurrence of T=1023)
T=1023 L=11 v=7477656e74797468726565
(mismatch: T=1042 L=1 v=42, checking for another occurrence of T=1042)
T=1042 L=8 v=666f72747974776f
T=1023 L=1 v=23
(mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
(mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
T=103 L=3 v=333333
(mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
T=103 L=2 v=2222
T=103 L=1 v=11
(mismatch: T=1002 L=2 v=0101, checking for another occurrence of T=1002)
T=1002 L=2 v=2222
T=1002 L=2 v=0101
T=1042 L=1 v=42
- decoding every second tag:
T=1002 L=2 v=0101
T=103 L=1 v=11
(mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
T=103 L=3 v=333333
T=1042 L=8 v=666f72747974776f
- enforcing order: without restart, a past tag is not parsed again:
osmo_tlv_load_next_by_tag(1042) rc=-ENOENT
=== end: txlxv_tests[1]

View File

@@ -1,2 +1,20 @@
AT_INIT
AT_BANNER([Regression tests.])
AT_SETUP([tlv])
AT_KEYWORDS([tlv])
cat $abs_srcdir/libosmo-tlv/tlv_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/libosmo-tlv/tlv_test], [], [expout], [ignore])
AT_CLEANUP
AT_SETUP([tlv_dec_enc])
AT_KEYWORDS([tlv_dec_enc])
cat $abs_srcdir/libosmo-tlv/tlv_dec_enc_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/libosmo-tlv/tlv_dec_enc_test], [], [expout], [ignore])
AT_CLEANUP
AT_SETUP([tlv_gen])
AT_KEYWORDS([tlv_gen])
cat $abs_srcdir/libosmo-tlv/test_tlv_gen/tlv_gen_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/libosmo-tlv/test_tlv_gen/tlv_gen_test], [], [expout], [ignore])
AT_CLEANUP

View File

@@ -2,3 +2,34 @@ OsmoUPF> enable
OsmoUPF# configure terminal
OsmoUPF(config)# show running-config
...
OsmoUPF(config)# pfcp
OsmoUPF(config-pfcp)# list
...
local-addr IP_ADDR
OsmoUPF(config-pfcp)# local-addr?
local-addr Set the local IP address to bind on for PFCP
OsmoUPF(config-pfcp)# local-addr ?
IP_ADDR IP address
OsmoUPF(config-pfcp)# exit
OsmoUPF(config)# gtp
OsmoUPF(config-gtp)# list
...
dev create DEVNAME [LISTEN_ADDR]
dev use DEVNAME
dev delete DEVNAME
OsmoUPF(config-gtp)# dev?
dev Configure the GTP device to use for encaps/decaps.
OsmoUPF(config-gtp)# dev ?
create create a new GTP device. Will listen on GTPv1 port 2152 and GTPv0 port 3386 on the specified interface, or on ANY if LISTEN_ADDR is omitted.
use use an existing GTP device, e.g. created by 'gtp-link'
delete Remove a GTP device from the configuration, and delete the device if it was created here.
OsmoUPF(config-gtp)# dev create ?
DEVNAME device name, e.g. 'apn0'
OsmoUPF(config-gtp)# dev create foo ?
[LISTEN_ADDR] IPv4 or IPv6 address to listen on, omit for any
OsmoUPF(config-gtp)# dev delete ?
DEVNAME device name, e.g. 'apn0'
OsmoUPF(config-gtp)# exit