mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-upf.git
synced 2025-10-23 08:12:03 +00:00
libosmo-pfcp: implement PFCP header and msg handling
Related: SYS#5599 Change-Id: I3f85ea052a6b7c064244a8093777e53a47c8c61e
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
pfcp_HEADERS = \
|
||||
pfcp_ies_custom.h \
|
||||
pfcp_msg.h \
|
||||
pfcp_proto.h \
|
||||
pfcp_strs.h \
|
||||
$(NULL)
|
||||
|
143
include/osmocom/pfcp/pfcp_msg.h
Normal file
143
include/osmocom/pfcp/pfcp_msg.h
Normal 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);
|
||||
|
@@ -25,6 +25,7 @@ noinst_LIBRARIES = \
|
||||
|
||||
libosmo_pfcp_a_SOURCES = \
|
||||
pfcp_ies_custom.c \
|
||||
pfcp_msg.c \
|
||||
pfcp_strs.c \
|
||||
\
|
||||
pfcp_ies_auto.c \
|
||||
|
603
src/libosmo-pfcp/pfcp_msg.c
Normal file
603
src/libosmo-pfcp/pfcp_msg.c
Normal 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->tag == OSMO_PFCP_IEI_CAUSE)
|
||||
m->ofs_cause = offsetof(struct osmo_pfcp_msg, ies) + mc->memb_ofs;
|
||||
if (mc->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
|
Reference in New Issue
Block a user