neels has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/osmo-upf/+/27631 )


Change subject: libosmo-pfcp: implement PFCP header and msg handling
......................................................................

libosmo-pfcp: implement PFCP header and msg handling

Related: SYS#5599
Change-Id: I3f85ea052a6b7c064244a8093777e53a47c8c61e
---
M include/osmocom/pfcp/Makefile.am
A include/osmocom/pfcp/pfcp_msg.h
M src/libosmo-pfcp/Makefile.am
M src/libosmo-pfcp/pfcp_ies_custom.c
A src/libosmo-pfcp/pfcp_msg.c
5 files changed, 722 insertions(+), 1 deletion(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-upf refs/changes/31/27631/1

diff --git a/include/osmocom/pfcp/Makefile.am b/include/osmocom/pfcp/Makefile.am
index 4bfc194..72be40e 100644
--- a/include/osmocom/pfcp/Makefile.am
+++ b/include/osmocom/pfcp/Makefile.am
@@ -1,5 +1,6 @@
 pfcp_HEADERS = \
        pfcp_ies_custom.h \
+       pfcp_msg.h \
        pfcp_proto.h \
        pfcp_strs.h \
        $(NULL)
diff --git a/include/osmocom/pfcp/pfcp_msg.h b/include/osmocom/pfcp/pfcp_msg.h
new file mode 100644
index 0000000..3e3ec84
--- /dev/null
+++ b/include/osmocom/pfcp/pfcp_msg.h
@@ -0,0 +1,165 @@
+/* PFCP message encoding and decoding */
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <[email protected]>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/pfcp/pfcp_proto.h>
+#include <osmocom/pfcp/pfcp_ies_auto.h>
+#include <osmocom/pfcp/pfcp_strs.h>
+
+struct msgb;
+struct osmo_t16l16v_ie;
+
+#define OSMO_PFCP_MSGB_ALLOC_SIZE 2048
+
+#define OSMO_LOG_PFCP_MSG_SRC(M, LEVEL, file, line, FMT, ARGS...) do { \
+               struct osmo_fsm_inst *_fi = (M) ? ( (M)->ctx.session_fi ?: 
(M)->ctx.peer_fi ) : NULL; \
+               enum osmo_pfcp_cause *cause = osmo_pfcp_msg_cause(M); \
+               if ((M)->h.seid_present) { \
+                       LOGPFSMSLSRC(_fi, DLPFCP, LEVEL, file, line, \
+                                    "%s%s PFCP seq-%u SEID-0x%"PRIx64" %s%s%s: 
" FMT, \
+                                    _fi ? "" : 
osmo_sockaddr_to_str_c(OTC_SELECT, &(M)->remote_addr), \
+                                    (M)->rx ? "-rx->" : "<-tx-", 
(M)->h.sequence_nr, \
+                                    (M)->h.seid, \
+                                    
osmo_pfcp_message_type_str((M)->h.message_type), cause ? ": " : "", \
+                                    cause ? osmo_pfcp_cause_str(*cause) : "", 
##ARGS); \
+               } else { \
+                       LOGPFSMSLSRC(_fi, DLPFCP, LEVEL, file, line, \
+                                    "%s%s PFCP seq-%u %s%s%s: " FMT, \
+                                    _fi ? "" : 
osmo_sockaddr_to_str_c(OTC_SELECT, &(M)->remote_addr), \
+                                    (M)->rx ? "-rx->" : "<-tx-", 
(M)->h.sequence_nr, \
+                                    
osmo_pfcp_message_type_str((M)->h.message_type), cause ? ": " : "", \
+                                    cause ? osmo_pfcp_cause_str(*cause) : "", 
##ARGS); \
+               } \
+       } while(0)
+
+#define OSMO_LOG_PFCP_MSG(M, LEVEL, FMT, ARGS...) \
+       OSMO_LOG_PFCP_MSG_SRC(M, LEVEL, __FILE__, __LINE__, FMT, ##ARGS)
+
+/* Return the next PFCP transmit sequence number based on the given sequence 
state var. */
+static inline uint32_t osmo_pfcp_next_seq(uint32_t *seq_state)
+{
+       (*seq_state)++;
+       (*seq_state) &= 0xffffff;
+       return *seq_state;
+}
+
+struct osmo_pfcp_header_parsed {
+       uint8_t version;
+       enum osmo_pfcp_message_type message_type;
+       uint32_t sequence_nr;
+       bool priority_present;
+       uint8_t priority;
+       bool seid_present;
+       uint64_t seid;
+};
+
+struct osmo_pfcp_msg {
+       /* Peer's remote address. Received from this peer, or should be sent to 
this peer. */
+       struct osmo_sockaddr remote_addr;
+       /* True when this message was received from a remote; false when this 
message is going to be sent. */
+       bool rx;
+       /* True when this message is a Response message type; false if Request. 
This is set by
+        * osmo_pfcp_msg_decode() for received messages, and by 
osmo_pfcp_msg_alloc_tx */
+       bool is_response;
+
+       struct osmo_pfcp_header_parsed h;
+
+       int ofs_cause;
+       int ofs_node_id;
+
+       /* The union of decoded IEs from all supported PFCP message types.  The 
union and its structure is defined in
+        * pfcp_ies_auto.h, which is generated by gen__pfcp_ies_auto.c.
+        */
+       union osmo_pfcp_ies ies;
+
+       /* Context information about this message, used for logging */
+       struct {
+               /* Peer FSM instance that this message is received from / sent 
to. This can be set in the
+                * osmo_pfcp_endpoint->set_msg_ctx() implementation, up to the 
caller. If present, this is used for
+                * logging context, and can also be used by the caller to 
reduce lookup iterations. */
+               struct osmo_fsm_inst *peer_fi;
+               struct osmo_use_count *peer_use_count;
+               const char *peer_use_token;
+
+               /* Session FSM instance that this message is received from / 
sent to. This can be set in the
+                * osmo_pfcp_endpoint->set_msg_ctx() implementation, up to the 
caller. If present, this is used for
+                * logging context, and can also be used by the caller to 
reduce lookup iterations. */
+               struct osmo_fsm_inst *session_fi;
+               struct osmo_use_count *session_use_count;
+               const char *session_use_token;
+       } ctx;
+};
+
+#define OSMO_PFCP_MSG_FOR_IES(IES_P) ((struct osmo_pfcp_msg*)((char*)IES_P - 
offsetof(struct osmo_pfcp_msg, ies)))
+
+bool osmo_pfcp_msgtype_is_response(enum osmo_pfcp_message_type message_type);
+
+int osmo_pfcp_ie_f_teid_to_str_buf(char *buf, size_t len, const struct 
osmo_pfcp_ie_f_teid *ft);
+char *osmo_pfcp_ie_f_teid_to_str_c(void *ctx, const struct osmo_pfcp_ie_f_teid 
*ft);
+
+int osmo_pfcp_msg_encode(struct msgb *msg, const struct osmo_pfcp_msg 
*pfcp_msg);
+
+int osmo_pfcp_msg_decode_header(struct osmo_gtlv_load *tlv, struct 
osmo_pfcp_msg *m,
+                               const struct msgb *msg);
+int osmo_pfcp_msg_decode_tlv(struct osmo_pfcp_msg *m, struct osmo_gtlv_load 
*tlv);
+
+struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_rx(void *ctx, const struct 
osmo_sockaddr *remote_addr);
+struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_tx(void *ctx, const struct 
osmo_sockaddr *remote_addr,
+                                            const struct osmo_pfcp_ie_node_id 
*local_node_id,
+                                            const struct osmo_pfcp_msg 
*in_reply_to,
+                                            enum osmo_pfcp_message_type 
msg_type);
+
+void osmo_pfcp_msg_invalidate_ctx(struct osmo_pfcp_msg *m, struct 
osmo_fsm_inst *deleted_fi);
+
+void osmo_pfcp_msg_free(struct osmo_pfcp_msg *m);
+
+int osmo_pfcp_ie_node_id_from_osmo_sockaddr(struct osmo_pfcp_ie_node_id 
*node_id, const struct osmo_sockaddr *os);
+int osmo_pfcp_ie_node_id_to_osmo_sockaddr(const struct osmo_pfcp_ie_node_id 
*node_id, struct osmo_sockaddr *os);
+
+#define OSMO_PFCP_MSG_MEMB(M, OFS) ((OFS) <= 0 ? NULL : (void*)((uint8_t*)(M) 
+ OFS))
+
+static inline enum osmo_pfcp_cause *osmo_pfcp_msg_cause(const struct 
osmo_pfcp_msg *m)
+{
+       return OSMO_PFCP_MSG_MEMB(m, m->ofs_cause);
+}
+
+static inline struct osmo_pfcp_ie_node_id *osmo_pfcp_msg_node_id(const struct 
osmo_pfcp_msg *m)
+{
+       return OSMO_PFCP_MSG_MEMB(m, m->ofs_node_id);
+}
+
+int osmo_pfcp_ie_f_seid_cmp(const struct osmo_pfcp_ie_f_seid *a, const struct 
osmo_pfcp_ie_f_seid *b);
+void osmo_pfcp_ie_f_seid_set_addr(struct osmo_pfcp_ie_f_seid *f_seid, const 
struct osmo_sockaddr *addr);
+
+int osmo_pfcp_msg_to_str_buf(char *buf, size_t buflen, const struct 
osmo_pfcp_msg *m);
+char *osmo_pfcp_msg_to_str_c(void *ctx, const struct osmo_pfcp_msg *m);
diff --git a/src/libosmo-pfcp/Makefile.am b/src/libosmo-pfcp/Makefile.am
index 0e249f9..c8accb9 100644
--- a/src/libosmo-pfcp/Makefile.am
+++ b/src/libosmo-pfcp/Makefile.am
@@ -25,6 +25,7 @@

 libosmo_pfcp_a_SOURCES = \
        pfcp_ies_custom.c \
+       pfcp_msg.c \
        pfcp_strs.c \
        \
        pfcp_ies_auto.c \
diff --git a/src/libosmo-pfcp/pfcp_ies_custom.c 
b/src/libosmo-pfcp/pfcp_ies_custom.c
index 660a08d..8823df7 100644
--- a/src/libosmo-pfcp/pfcp_ies_custom.c
+++ b/src/libosmo-pfcp/pfcp_ies_custom.c
@@ -34,12 +34,13 @@

 #include <osmocom/pfcp/pfcp_ies_custom.h>
 #include <osmocom/pfcp/pfcp_strs.h>
+#include <osmocom/pfcp/pfcp_msg.h>

 /* Assumes presence of local variable osmo_pfcp_msg *m. m->log_ctx may be 
NULL. */
 #define RETURN_ERROR(RC, FMT, ARGS...) \
        do {\
                OSMO_ASSERT(decoded_struct); \
-               LOGP(DLPFCP, LOGL_ERROR, FMT " (%d: %s)\n", ##ARGS, RC, \
+               OSMO_LOG_PFCP_MSG(OSMO_PFCP_MSG_FOR_IES(decoded_struct), 
LOGL_ERROR, FMT " (%d: %s)\n", ##ARGS, RC, \
                                  strerror((RC) > 0 ? (RC) : -(RC))); \
                return RC; \
        } while (0)
diff --git a/src/libosmo-pfcp/pfcp_msg.c b/src/libosmo-pfcp/pfcp_msg.c
new file mode 100644
index 0000000..aa390f0
--- /dev/null
+++ b/src/libosmo-pfcp/pfcp_msg.c
@@ -0,0 +1,553 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <[email protected]>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <osmocom/core/endian.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/use_count.h>
+#include <osmocom/core/bitvec.h>
+
+#include <osmocom/pfcp/pfcp_msg.h>
+#include <osmocom/gtlv/gtlv_dec_enc.h>
+
+/* Assumes presence of local variable osmo_pfcp_msg *m. m->log_ctx may be 
NULL. */
+#define RETURN_ERROR(RC, FMT, ARGS...) \
+       do {\
+               OSMO_ASSERT(m); \
+               OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, FMT " (%d: %s)\n", ##ARGS, RC, 
strerror((RC) > 0? (RC) : -(RC))); \
+               return RC; \
+       } while (0)
+
+bool osmo_pfcp_msgtype_is_response(enum osmo_pfcp_message_type message_type)
+{
+       switch (message_type) {
+       case OSMO_PFCP_MSGT_HEARTBEAT_RESP:
+       case OSMO_PFCP_MSGT_PFD_MGMT_RESP:
+       case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP:
+       case OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP:
+       case OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP:
+       case OSMO_PFCP_MSGT_VERSION_NOT_SUPP_RESP:
+       case OSMO_PFCP_MSGT_NODE_REPORT_RESP:
+       case OSMO_PFCP_MSGT_SESSION_SET_DEL_RESP:
+       case OSMO_PFCP_MSGT_SESSION_EST_RESP:
+       case OSMO_PFCP_MSGT_SESSION_MOD_RESP:
+       case OSMO_PFCP_MSGT_SESSION_DEL_RESP:
+       case OSMO_PFCP_MSGT_SESSION_REP_RESP:
+               return true;
+       default:
+               return false;
+       }
+}
+
+struct osmo_pfcp_header_common {
+       uint8_t seid_present:1,
+               message_priority_present:1,
+               follow_on:1,
+               spare:2,
+               version:3;
+       uint8_t message_type;
+       uint16_t message_length;
+} __attribute__ ((packed));
+
+struct osmo_pfcp_header_no_seid {
+       struct osmo_pfcp_header_common c;
+       uint8_t sequence_nr[3];
+       uint8_t spare;
+} __attribute__ ((packed));
+
+struct osmo_pfcp_header_seid {
+       struct osmo_pfcp_header_common c;
+       uint64_t session_endpoint_identifier;
+       uint8_t sequence_nr[3];
+       uint8_t message_priority:4,
+               spare:4;
+} __attribute__ ((packed));
+
+int osmo_pfcp_ie_node_id_from_osmo_sockaddr(struct osmo_pfcp_ie_node_id 
*node_id, const struct osmo_sockaddr *os)
+{
+       switch (os->u.sa.sa_family) {
+       case AF_INET:
+               node_id->type = OSMO_PFCP_NODE_ID_T_IPV4;
+               break;
+       case AF_INET6:
+               node_id->type = OSMO_PFCP_NODE_ID_T_IPV6;
+               break;
+       default:
+               return -ENOTSUP;
+       }
+       node_id->ip = *os;
+       return 0;
+}
+
+int osmo_pfcp_ie_node_id_to_osmo_sockaddr(const struct osmo_pfcp_ie_node_id 
*node_id, struct osmo_sockaddr *os)
+{
+       switch (node_id->type) {
+       case OSMO_PFCP_NODE_ID_T_IPV4:
+               if (os->u.sa.sa_family != AF_INET)
+                       return -EINVAL;
+               break;
+       case OSMO_PFCP_NODE_ID_T_IPV6:
+               if (os->u.sa.sa_family != AF_INET6)
+                       return -EINVAL;
+               break;
+       default:
+               return -ENOTSUP;
+       }
+       *os = node_id->ip;
+       return 0;
+}
+
+static int pfcp_header_set_message_length(struct osmo_pfcp_header_common *c, 
unsigned int header_and_payload_len)
+{
+       if (header_and_payload_len < sizeof(struct osmo_pfcp_header_common))
+               return -EINVAL;
+       if (header_and_payload_len - sizeof(struct osmo_pfcp_header_common) > 
UINT16_MAX)
+               return -EMSGSIZE;
+       osmo_store16be(header_and_payload_len - sizeof(struct 
osmo_pfcp_header_common),
+                      &c->message_length);
+       return 0;
+}
+
+static unsigned int pfcp_header_get_message_length(const struct 
osmo_pfcp_header_common *c)
+{
+       unsigned int len = osmo_load16be(&c->message_length);
+       return len + sizeof(struct osmo_pfcp_header_common);
+}
+
+/*! Encode and append the given PFCP header to a msgb.
+ * \param[out] msg message buffer to which to push the header.
+ * \param[in] to-be-encoded representation of PFCP header. */
+static int enc_pfcp_header(struct msgb *msg, const struct osmo_pfcp_msg *m)
+{
+       const struct osmo_pfcp_header_parsed *parsed = &m->h;
+       struct osmo_pfcp_header_seid *h_seid;
+       struct osmo_pfcp_header_no_seid *h_no_seid;
+       struct osmo_pfcp_header_common *c;
+       int rc;
+
+       if (!parsed->seid_present) {
+               h_no_seid = (struct osmo_pfcp_header_no_seid*)msgb_put(msg, 
sizeof(struct osmo_pfcp_header_no_seid));
+               c = &h_no_seid->c;
+       } else {
+               h_seid = (struct osmo_pfcp_header_seid*)msgb_put(msg, 
sizeof(struct osmo_pfcp_header_seid));
+               c = &h_seid->c;
+       }
+
+       *c = (struct osmo_pfcp_header_common){
+               .version = parsed->version,
+               .message_priority_present = (parsed->priority_present ? 1 : 0),
+               .seid_present = (parsed->seid_present ? 1 : 0),
+               .message_type = parsed->message_type,
+       };
+
+       /* Put a preliminary length reflecting only the header, until it is 
updated later in osmo_pfcp_msg_encode(). */
+       rc = pfcp_header_set_message_length(c, parsed->seid_present ? 
sizeof(struct osmo_pfcp_header_seid)
+                                                                   : 
sizeof(struct osmo_pfcp_header_no_seid));
+       if (rc)
+               RETURN_ERROR(rc, "Problem with PFCP message length");
+
+       if (!parsed->seid_present) {
+               osmo_store32be_ext(parsed->sequence_nr, h_no_seid->sequence_nr, 
3);
+               if (parsed->priority_present)
+                       RETURN_ERROR(-EINVAL, "Message Priority can only be 
present when the SEID is also present");
+       } else {
+               osmo_store64be(parsed->seid, 
&h_seid->session_endpoint_identifier);
+               osmo_store32be_ext(parsed->sequence_nr, h_seid->sequence_nr, 3);
+               if (parsed->priority_present)
+                       h_seid->message_priority = parsed->priority;
+       }
+
+       return 0;
+}
+
+static void osmo_pfcp_msg_set_memb_ofs(struct osmo_pfcp_msg *m)
+{
+       const struct osmo_gtlv_coding *mc = 
osmo_pfcp_get_msg_coding(m->h.message_type);
+       m->ofs_cause = 0;
+       m->ofs_node_id = 0;
+       if (!mc)
+               return;
+       for (; !osmo_gtlv_coding_end(mc) && (m->ofs_cause == 0 || 
m->ofs_node_id == 0); mc++) {
+               if (mc->ti.tag == OSMO_PFCP_IEI_CAUSE)
+                       m->ofs_cause = offsetof(struct osmo_pfcp_msg, ies) + 
mc->memb_ofs;
+               if (mc->ti.tag == OSMO_PFCP_IEI_NODE_ID)
+                       m->ofs_node_id = offsetof(struct osmo_pfcp_msg, ies) + 
mc->memb_ofs;
+       }
+
+#if 0
+       switch (m->h.message_type) {
+       case OSMO_PFCP_MSGT_ASSOC_SETUP_REQ:
+               m->ofs_node_id = offsetof(struct osmo_pfcp_msg, 
ies.assoc_setup_req.node_id);
+               break;
+       case OSMO_PFCP_MSGT_ASSOC_SETUP_RESP:
+               m->ofs_cause = offsetof(struct osmo_pfcp_msg, 
ies.assoc_setup_resp.cause);
+               m->ofs_node_id = offsetof(struct osmo_pfcp_msg, 
ies.assoc_setup_resp.node_id);
+               break;
+       case OSMO_PFCP_MSGT_SESSION_EST_REQ:
+               m->ofs_node_id = offsetof(struct osmo_pfcp_msg, 
ies.session_est_req.node_id);
+               break;
+       case OSMO_PFCP_MSGT_SESSION_EST_RESP:
+               m->ofs_cause = offsetof(struct osmo_pfcp_msg, 
ies.session_est_resp.cause);
+               m->ofs_node_id = offsetof(struct osmo_pfcp_msg, 
ies.session_est_resp.node_id);
+               break;
+       default:
+               m->ofs_cause = 0;
+               m->ofs_node_id = 0;
+               break;
+       }
+#endif
+};
+
+/*! Decode a single PFCP message's header.
+ *
+ * If msg->l4h is non-NULL, decode at msgb_l4(msg). If l4h is NULL, decode at 
msgb_l3(msg).
+ * In case of bundled PFCP messages, decode only one message and return the 
offset to the next message in the buffer.
+ * Hence, to decode a message bundle, increment msg->l4h until all messages 
are decoded:
+ *
+ *   msg->l4h = msg->l3h;
+ *   while (msgb_l4len(msg)) {
+ *           struct osmo_pfcp_msg m;
+ *           struct osmo_gtlv_load tlv;
+ *           int rc;
+ *           rc = osmo_pfcp_msg_decode_header(&tlv, &m, msg);
+ *           if (rc < 0)
+ *                  error();
+ *           msg->l4h += rc;
+ *
+ *           if (osmo_pfcp_msg_decode_tlv(&m, &tlv))
+ *                  error();
+ *           handle(&m);
+ *   }
+ *
+ * \param[out] tlv  Return TLV start pointer and length in tlv->src.*.
+ * \param[inout] m  Place the decoded data in m->h; use m->ctx.* as logging 
context.
+ * \param[in] msg  PFCP data to parse, possibly containing a PFCP message 
bundle.
+ * \return total single PFCP message length (<= data_len) on success, negative 
on error.
+ */
+int osmo_pfcp_msg_decode_header(struct osmo_gtlv_load *tlv, struct 
osmo_pfcp_msg *m,
+                               const struct msgb *msg)
+{
+       struct osmo_pfcp_header_parsed *parsed = &m->h;
+       const uint8_t *pfcp_msg_data;
+       unsigned int pfcp_msg_data_len;
+       unsigned int header_len;
+       unsigned int message_length;
+       const struct osmo_pfcp_header_common *c;
+
+       if (msg->l4h) {
+               pfcp_msg_data = msgb_l4(msg);
+               pfcp_msg_data_len = msgb_l4len(msg);
+       } else {
+               pfcp_msg_data = msgb_l3(msg);
+               pfcp_msg_data_len = msgb_l3len(msg);
+       }
+
+       if (!pfcp_msg_data || !pfcp_msg_data_len)
+               RETURN_ERROR(-EINVAL, "No Layer 3 data in this message buffer");
+
+       if (pfcp_msg_data_len < sizeof(struct osmo_pfcp_header_common))
+               RETURN_ERROR(-EINVAL, "Message too short for PFCP header: %u", 
pfcp_msg_data_len);
+
+       c = (void*)pfcp_msg_data;
+
+       header_len = (c->seid_present ? sizeof(struct osmo_pfcp_header_seid) : 
sizeof(struct osmo_pfcp_header_no_seid));
+       if (pfcp_msg_data_len < header_len)
+               RETURN_ERROR(-EINVAL, "Message too short for PFCP header: %u", 
pfcp_msg_data_len);
+
+       *parsed = (struct osmo_pfcp_header_parsed){
+               .version = c->version,
+               .priority_present = (bool)c->message_priority_present,
+               .seid_present = (bool)c->seid_present,
+               .message_type = c->message_type,
+       };
+
+       m->is_response = osmo_pfcp_msgtype_is_response(parsed->message_type);
+       osmo_pfcp_msg_set_memb_ofs(m);
+
+       message_length = pfcp_header_get_message_length(c);
+       if (message_length > pfcp_msg_data_len)
+               RETURN_ERROR(-EMSGSIZE,
+                            "The header's indicated total message length %u is 
larger than the received data %u",
+                            message_length, pfcp_msg_data_len);
+
+       /* T16L16V payload data and len */
+       *tlv = (struct osmo_gtlv_load){
+               .cfg = &osmo_t16l16v_cfg,
+               .src = {
+                       .data = pfcp_msg_data + header_len,
+                       .len = message_length - header_len,
+               },
+       };
+
+       if (c->follow_on) {
+               /* Another PFCP message should follow */
+               if (pfcp_msg_data_len - message_length < sizeof(struct 
osmo_pfcp_header_common))
+                       OSMO_LOG_PFCP_MSG(m, LOGL_INFO,
+                                         "PFCP message indicates more messages 
should follow in the bundle,"
+                                         " but remaining size %u is too 
short", pfcp_msg_data_len - message_length);
+       } else {
+               /* No more PFCP message should follow in the bundle */
+               if (pfcp_msg_data_len > message_length)
+                       OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "Surplus data after 
PFCP message: %u",
+                                         pfcp_msg_data_len - message_length);
+       }
+
+       if (!parsed->seid_present) {
+               const struct osmo_pfcp_header_no_seid *h_no_seid = 
(void*)pfcp_msg_data;
+               parsed->sequence_nr = 
osmo_load32be_ext_2(h_no_seid->sequence_nr, 3);
+               if (parsed->priority_present)
+                       RETURN_ERROR(-EINVAL, "Message Priority can only be 
present when the SEID is also present");
+       } else {
+               const struct osmo_pfcp_header_seid *h_seid = 
(void*)pfcp_msg_data;
+               parsed->seid = 
osmo_load64be(&h_seid->session_endpoint_identifier);
+               parsed->sequence_nr = osmo_load32be_ext_2(h_seid->sequence_nr, 
3);
+               if (parsed->priority_present)
+                       parsed->priority = h_seid->message_priority;
+       }
+
+       return message_length;
+}
+
+void osmo_pfcp_msg_err_cb(void *data, void *decoded_struct, const char *file, 
int line, const char *fmt, ...)
+{
+       va_list ap;
+       if (log_check_level(DLPFCP, LOGL_ERROR)) {
+               char *errmsg;
+
+               va_start(ap, fmt);
+               errmsg = talloc_vasprintf(OTC_SELECT, fmt, ap);
+               va_end(ap);
+               OSMO_LOG_PFCP_MSG_SRC((struct osmo_pfcp_msg *)data, LOGL_ERROR, 
file, line, "%s", errmsg);
+       }
+}
+
+int osmo_pfcp_msg_decode_tlv(struct osmo_pfcp_msg *m, struct osmo_gtlv_load 
*tlv)
+{
+       return osmo_pfcp_ies_decode(&m->ies, tlv, false, m->h.message_type, 
osmo_pfcp_msg_err_cb, m, osmo_pfcp_iei_strs);
+}
+
+static int osmo_pfcp_msg_encode_tlv(struct msgb *msg, const struct 
osmo_pfcp_msg *m)
+{
+       struct osmo_gtlv_put tlv = {
+               .cfg = &osmo_t16l16v_cfg,
+               .dst = msg,
+       };
+       return osmo_pfcp_ies_encode(&tlv, &m->ies, m->h.message_type, 
osmo_pfcp_msg_err_cb, (void*)m, osmo_pfcp_iei_strs);
+}
+
+/* Append the encoded PFCP message to the message buffer.
+ *
+ * If msg->l3h is NULL, point it at the start of the encoded message.
+ * Always point msg->l4h at the start of the newly encoded message.
+ * Hence, in a message bundle, msg->l3h always points at the first PFCP 
message, while msg->l4h always points at the
+ * last PFCP message.
+ *
+ * When adding a PFCP message to a bundle, set the Follow On (FO) flag of the 
previously last message to 1, and of the
+ * newly encoded, now last message as 0.
+ *
+ * To log errors to a specific osmo_fsm_inst, point m->log_ctx to that 
instance before calling this function. Otherwise
+ * set log_ctx = NULL.
+ *
+ * \return 0 on success, negative on error. */
+int osmo_pfcp_msg_encode(struct msgb *msg, const struct osmo_pfcp_msg *m)
+{
+       struct osmo_pfcp_header_common *c;
+       int rc;
+
+       /* Forming a bundle? If yes, set the Follow On flag of the currently 
last message to 1 */
+       if (msg->l4h && msgb_l4len(msg)) {
+               c = msgb_l4(msg);
+               c->follow_on = 1;
+       }
+       /* Make sure l3h points at the first PFCP message in a message bundle */
+       if (!msg->l3h)
+               msg->l3h = msg->tail;
+       /* Make sure l4h points at the last PFCP message in a message bundle */
+       msg->l4h = msg->tail;
+       c = (void*)msg->tail;
+
+       rc = enc_pfcp_header(msg, m);
+       if (rc)
+               return rc;
+
+       rc = osmo_pfcp_msg_encode_tlv(msg, m);
+       if (rc)
+               return rc;
+
+       /* Update the header's message_length */
+       rc = pfcp_header_set_message_length(c, msgb_l4len(msg));
+       if (rc)
+               RETURN_ERROR(rc, "Problem with PFCP message length");
+       return 0;
+}
+
+static int osmo_pfcp_msg_destructor(struct osmo_pfcp_msg *m);
+
+static struct osmo_pfcp_msg *_osmo_pfcp_msg_alloc(void *ctx, const struct 
osmo_sockaddr *remote_addr)
+{
+       struct osmo_pfcp_msg *m = talloc(ctx, struct osmo_pfcp_msg);
+       *m = (struct osmo_pfcp_msg){
+               .remote_addr = *remote_addr,
+               .h = {
+                       .version = 1,
+               },
+       };
+       talloc_set_destructor(m, osmo_pfcp_msg_destructor);
+       return m;
+}
+
+struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_rx(void *ctx, const struct 
osmo_sockaddr *remote_addr)
+{
+       struct osmo_pfcp_msg *rx = _osmo_pfcp_msg_alloc(ctx, remote_addr);
+       rx->rx = true;
+       return rx;
+}
+
+struct osmo_pfcp_msg *osmo_pfcp_msg_alloc_tx(void *ctx, const struct 
osmo_sockaddr *remote_addr,
+                                            const struct osmo_pfcp_ie_node_id 
*node_id,
+                                            const struct osmo_pfcp_msg 
*in_reply_to,
+                                            enum osmo_pfcp_message_type 
msg_type)
+{
+       struct osmo_pfcp_msg *tx;
+       if (!remote_addr && in_reply_to)
+               remote_addr = &in_reply_to->remote_addr;
+       OSMO_ASSERT(remote_addr);
+       tx = _osmo_pfcp_msg_alloc(ctx, remote_addr);
+       tx->is_response = osmo_pfcp_msgtype_is_response(msg_type);
+       tx->h.message_type = msg_type;
+       if (in_reply_to)
+               tx->h.sequence_nr = in_reply_to->h.sequence_nr;
+       osmo_pfcp_msg_set_memb_ofs(tx);
+
+       /* Write the local node id data to the correct tx->ies.* member. */
+       if (node_id) {
+               struct osmo_pfcp_ie_node_id *tx_node_id = 
osmo_pfcp_msg_node_id(tx);
+               if (tx_node_id)
+                       *tx_node_id = *node_id;
+       }
+       return tx;
+}
+
+static int osmo_pfcp_msg_destructor(struct osmo_pfcp_msg *m)
+{
+       OSMO_LOG_PFCP_MSG(m, LOGL_DEBUG, "discarding\n");
+       if (m->ctx.session_use_count)
+               osmo_use_count_get_put(m->ctx.session_use_count, 
m->ctx.session_use_token, -1);
+       m->ctx.session_fi = NULL;
+       m->ctx.session_use_count = NULL;
+       m->ctx.session_use_token = NULL;
+
+       if (m->ctx.peer_use_count)
+               osmo_use_count_get_put(m->ctx.peer_use_count, 
m->ctx.peer_use_token, -1);
+       m->ctx.peer_fi = NULL;
+       m->ctx.peer_use_count = NULL;
+       m->ctx.peer_use_token = NULL;
+       return 0;
+}
+
+void osmo_pfcp_msg_free(struct osmo_pfcp_msg *m)
+{
+       if (!m)
+               return;
+       talloc_free(m);
+}
+
+int osmo_pfcp_ie_f_seid_cmp(const struct osmo_pfcp_ie_f_seid *a, const struct 
osmo_pfcp_ie_f_seid *b)
+{
+       int rc;
+       if (a == b)
+               return 0;
+       if (!a)
+               return -1;
+       if (!b)
+               return 1;
+       /* It suffices if one of the IP addresses match */
+       if (a->ip_addr.v4_present && b->ip_addr.v4_present)
+               rc = osmo_sockaddr_cmp(&a->ip_addr.v4, &b->ip_addr.v4);
+       else if (a->ip_addr.v6_present && b->ip_addr.v6_present)
+               rc = osmo_sockaddr_cmp(&a->ip_addr.v6, &b->ip_addr.v6);
+       else
+               rc = (a->ip_addr.v4_present ? -1 : 1);
+       if (rc)
+               return rc;
+       if (a->seid < b->seid)
+               return -1;
+       if (a->seid > b->seid)
+               return 1;
+       return 0;
+}
+
+int osmo_pfcp_ip_addrs_set(struct osmo_pfcp_ip_addrs *dst, const struct 
osmo_sockaddr *addr)
+{
+       switch (addr->u.sas.ss_family) {
+       case AF_INET:
+               dst->v4_present = true;
+               dst->v4 = *addr;
+               return 0;
+       case AF_INET6:
+               dst->v6_present = true;
+               dst->v6 = *addr;
+               return 0;
+       default:
+               return -ENOTSUP;
+       }
+}
+
+#if 0
+void osmo_pfcp_msg_invalidate_ctx(struct osmo_pfcp_msg *m, struct 
osmo_fsm_inst *deleted_fi)
+{
+       if (m->ctx.session_fi == deleted_fi) {
+               m->ctx.session_fi = NULL;
+               m->ctx.session_use_count = NULL;
+               m->ctx.session_use_token = NULL;
+       }
+       if (m->ctx.peer_fi == deleted_fi) {
+               m->ctx.peer_fi = NULL;
+               m->ctx.peer_use_count = NULL;
+               m->ctx.peer_use_token = NULL;
+       }
+}
+#endif
+
+int osmo_pfcp_msg_to_str_buf(char *buf, size_t buflen, const struct 
osmo_pfcp_msg *m)
+{
+       struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+       OSMO_STRBUF_PRINTF(sb, "PFCPv%u %s hdr={seq=%u", m->h.version, 
osmo_pfcp_message_type_str(m->h.message_type),
+                          m->h.sequence_nr);
+       if (m->h.priority_present)
+               OSMO_STRBUF_PRINTF(sb, " prio=%u", m->h.priority);
+       if (m->h.seid_present)
+               OSMO_STRBUF_PRINTF(sb, " SEID=0x%"PRIx64, m->h.seid);
+       OSMO_STRBUF_PRINTF(sb, "} ies={");
+       OSMO_STRBUF_APPEND(sb, osmo_pfcp_ies_encode_to_str, &m->ies, 
m->h.message_type, osmo_pfcp_iei_strs);
+       OSMO_STRBUF_PRINTF(sb, " }");
+       return sb.chars_needed;
+}
+
+char *osmo_pfcp_msg_to_str_c(void *ctx, const struct osmo_pfcp_msg *m)
+{
+       OSMO_NAME_C_IMPL(ctx, 256, "ERROR", osmo_pfcp_msg_to_str_buf, m)
+}

--
To view, visit https://gerrit.osmocom.org/c/osmo-upf/+/27631
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-upf
Gerrit-Branch: master
Gerrit-Change-Id: I3f85ea052a6b7c064244a8093777e53a47c8c61e
Gerrit-Change-Number: 27631
Gerrit-PatchSet: 1
Gerrit-Owner: neels <[email protected]>
Gerrit-MessageType: newchange

Reply via email to