libosmo-netif/src/prim.c

474 lines
14 KiB
C

/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <inttypes.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/netif/prim.h>
#include <osmocom/netif/stream.h>
struct osmo_prim_pkt_hdr {
uint32_t sap; /*!< Service Access Point Identifier */
uint16_t primitive; /*!< Primitive number */
uint16_t operation; /*! Primitive Operation (enum osmo_prim_operation) */
} __attribute__ ((packed));
/* Here we take advantage of the fact that sizeof(struct
* osmo_prim_pkt_hdr) <= sizeof(struct osmo_prim_hdr), so we don't need
* to allocate headroom when serializing later.
*/
osmo_static_assert(sizeof(struct osmo_prim_pkt_hdr) <= sizeof(struct osmo_prim_hdr),
osmo_prim_msgb_alloc_validate_headroom);
/*! Allocate a primitive of given type and its associated msgb.
* \param[in] sap Service Access Point
* \param[in] primitive Primitive Number
* \param[in] operation Primitive Operation (REQ/RESP/IND/CONF)
* \param[in] alloc_len Total length (including struct osmo_prim_hdr) to allocate for the primitive
* \returns Pointer to allocated prim_hdr inisde its own msgb. The osmo_prim_hdr
* is pre-alocated & pre-filled.
*/
struct osmo_prim_hdr *osmo_prim_msgb_alloc(unsigned int sap, unsigned int primitive,
enum osmo_prim_operation operation, size_t alloc_len)
{
struct msgb *msg;
struct osmo_prim_hdr *oph;
if (alloc_len < sizeof(*oph))
return NULL;
msg = msgb_alloc(alloc_len, "osmo_prim_msgb_alloc");
oph = (struct osmo_prim_hdr *)msgb_put(msg, sizeof(*oph));
osmo_prim_init(oph, sap, primitive, operation, msg);
msg->l2h = msg->tail;
return oph;
}
struct osmo_prim_srv_link {
void *priv;
char *addr;
int log_cat; /* Defaults to DLGLOBAL */
struct osmo_stream_srv_link *stream;
osmo_prim_srv_conn_cb opened_conn_cb;
osmo_prim_srv_conn_cb closed_conn_cb;
osmo_prim_srv_rx_sapi_version rx_sapi_version_cb;
osmo_prim_srv_rx_cb rx_cb;
size_t rx_msgb_alloc_len;
};
struct osmo_prim_srv {
void *priv;
struct osmo_prim_srv_link *link; /* backpointer */
struct osmo_stream_srv *stream;
};
/******************************
* CONTROL SAP
******************************/
#define OSMO_PRIM_CTL_SAPI 0xffffffff
#define OSMO_PRIM_CTL_API_VERSION 0
enum sap_ctl_prim_type {
SAP_CTL_PRIM_HELLO,
_SAP_CTL_PRIM_MAX
};
const struct value_string sap_ctl_prim_type_names[] = {
OSMO_VALUE_STRING(SAP_CTL_PRIM_HELLO),
{ 0, NULL }
};
/* HNB_CTL_PRIM_HELLO.ind, UL */
struct sap_ctl_hello_param {
uint32_t sapi; /* SAPI for which we negotiate version */
uint16_t api_version; /* The intended version */
} __attribute__ ((packed));
struct sap_ctl_prim {
struct osmo_prim_hdr hdr;
union {
struct sap_ctl_hello_param hello_req;
struct sap_ctl_hello_param hello_cnf;
} u;
} __attribute__ ((packed));
static struct sap_ctl_prim *_sap_ctl_makeprim_hello_cnf(uint32_t sapi, uint16_t api_version)
{
struct sap_ctl_prim *ctl_prim;
ctl_prim = (struct sap_ctl_prim *)osmo_prim_msgb_alloc(
OSMO_PRIM_CTL_SAPI, SAP_CTL_PRIM_HELLO, PRIM_OP_CONFIRM,
sizeof(struct osmo_prim_hdr) + sizeof(struct sap_ctl_hello_param));
msgb_put(ctl_prim->hdr.msg, sizeof(struct sap_ctl_hello_param));
ctl_prim->u.hello_cnf.sapi = sapi;
ctl_prim->u.hello_cnf.api_version = api_version;
return ctl_prim;
}
/******************************
* osmo_prim_srv
******************************/
#define LOGSRV(srv, lvl, fmt, args...) LOGP((srv)->link->log_cat, lvl, fmt, ## args)
static int _srv_sap_ctl_rx_hello_req(struct osmo_prim_srv *prim_srv, struct sap_ctl_hello_param *hello_ind)
{
struct sap_ctl_prim *prim_resp;
int rc;
LOGSRV(prim_srv, LOGL_INFO, "Rx CTL-HELLO.req SAPI=%u API_VERSION=%u\n", hello_ind->sapi, hello_ind->api_version);
if (hello_ind->sapi == OSMO_PRIM_CTL_SAPI)
rc = hello_ind->api_version == OSMO_PRIM_CTL_API_VERSION ? OSMO_PRIM_CTL_API_VERSION : -1;
else if (prim_srv->link->rx_sapi_version_cb)
rc = prim_srv->link->rx_sapi_version_cb(prim_srv, hello_ind->sapi, hello_ind->api_version);
else /* Accept whatever version by default: */
rc = hello_ind->api_version;
if (rc < 0) {
LOGSRV(prim_srv, LOGL_ERROR,
"SAPI=%u API_VERSION=%u not supported! destroying connection\n",
hello_ind->sapi, hello_ind->api_version);
osmo_stream_srv_set_flush_and_destroy(prim_srv->stream);
return rc;
}
prim_resp = _sap_ctl_makeprim_hello_cnf(hello_ind->sapi, (uint16_t)rc);
LOGSRV(prim_srv, LOGL_INFO, "Tx CTL-HELLO.cnf SAPI=%u API_VERSION=%u\n",
hello_ind->sapi, prim_resp->u.hello_cnf.api_version);
rc = osmo_prim_srv_send(prim_srv, prim_resp->hdr.msg);
return rc;
}
static int _srv_sap_ctl_rx(struct osmo_prim_srv *prim_srv, struct osmo_prim_hdr *oph)
{
switch (oph->operation) {
case PRIM_OP_REQUEST:
switch (oph->primitive) {
case SAP_CTL_PRIM_HELLO:
return _srv_sap_ctl_rx_hello_req(prim_srv, (struct sap_ctl_hello_param *)msgb_data(oph->msg));
default:
LOGSRV(prim_srv, LOGL_ERROR, "Rx unknown CTL SAP primitive %u (len=%u)\n",
oph->primitive, msgb_length(oph->msg));
return -EINVAL;
}
break;
case PRIM_OP_RESPONSE:
case PRIM_OP_INDICATION:
case PRIM_OP_CONFIRM:
default:
LOGSRV(prim_srv, LOGL_ERROR, "Rx CTL SAP unexpected primitive operation %s-%s (len=%u)\n",
get_value_string(sap_ctl_prim_type_names, oph->primitive),
get_value_string(osmo_prim_op_names, oph->operation),
msgb_length(oph->msg));
return -EINVAL;
}
}
static int _osmo_prim_srv_read_cb(struct osmo_stream_srv *srv)
{
struct osmo_prim_srv *prim_srv = osmo_stream_srv_get_data(srv);
struct osmo_prim_pkt_hdr *pkth;
struct msgb *msg;
struct osmo_prim_hdr oph;
int rc;
msg = msgb_alloc_c(prim_srv, sizeof(*pkth) + prim_srv->link->rx_msgb_alloc_len,
"osmo_prim_srv_link_rx");
if (!msg)
return -ENOMEM;
rc = osmo_stream_srv_recv(srv, msg);
if (rc == 0)
goto close;
if (rc < 0) {
if (errno == EAGAIN) {
msgb_free(msg);
return 0;
}
goto close;
}
if (rc < sizeof(*pkth)) {
LOGSRV(prim_srv, LOGL_ERROR, "Received %d bytes on UD Socket, but primitive hdr size "
"is %zu, discarding\n", rc, sizeof(*pkth));
msgb_free(msg);
return 0;
}
pkth = (struct osmo_prim_pkt_hdr *)msgb_data(msg);
/* De-serialize message: */
osmo_prim_init(&oph, pkth->sap, pkth->primitive, pkth->operation, msg);
msgb_pull(msg, sizeof(*pkth));
switch (oph.sap) {
case OSMO_PRIM_CTL_SAPI:
rc = _srv_sap_ctl_rx(prim_srv, &oph);
break;
default:
if (prim_srv->link->rx_cb)
rc = prim_srv->link->rx_cb(prim_srv, &oph);
break;
}
/* as we always synchronously process the message in _osmo_prim_srv_link_rx() and
* its callbacks, we can free the message here. */
msgb_free(msg);
return rc;
close:
msgb_free(msg);
osmo_prim_srv_close(prim_srv);
return -1;
}
static void osmo_prim_srv_free(struct osmo_prim_srv *prim_srv);
static int _osmo_prim_srv_closed_cb(struct osmo_stream_srv *srv)
{
struct osmo_prim_srv *prim_srv = osmo_stream_srv_get_data(srv);
struct osmo_prim_srv_link *prim_link = prim_srv->link;
if (prim_link->closed_conn_cb)
return prim_link->closed_conn_cb(prim_srv);
osmo_prim_srv_free(prim_srv);
return 0;
}
/*! Allocate a primitive of given type and its associated msgb.
* \param[in] srv The osmo_prim_srv_link instance where message is to be sent through
* \param[in] msg msgb containing osmo_prim_hdr plus extra content, allocated through \ref osmo_prim_msgb_alloc()
* \returns zero on success, negative on error */
int osmo_prim_srv_send(struct osmo_prim_srv *prim_srv, struct msgb *msg)
{
struct osmo_prim_hdr *oph;
struct osmo_prim_pkt_hdr *pkth;
unsigned int sap;
unsigned int primitive;
enum osmo_prim_operation operation;
OSMO_ASSERT(prim_srv);
/* Serialize the oph: */
oph = (struct osmo_prim_hdr *)msgb_data(msg);
OSMO_ASSERT(oph && msgb_length(msg) >= sizeof(*oph));
sap = oph->sap;
primitive = oph->primitive;
operation = oph->operation;
msgb_pull(msg, sizeof(*oph));
pkth = (struct osmo_prim_pkt_hdr *)msgb_push(msg, sizeof(*pkth));
pkth->sap = sap;
pkth->primitive = primitive;
pkth->operation = operation;
/* Finally enqueue the msg */
osmo_stream_srv_send(prim_srv->stream, msg);
return 0;
}
static struct osmo_prim_srv *osmo_prim_srv_alloc(struct osmo_prim_srv_link *prim_link, int fd)
{
struct osmo_prim_srv *prim_srv;
prim_srv = talloc_zero(prim_link, struct osmo_prim_srv);
if (!prim_srv)
return NULL;
prim_srv->link = prim_link;
prim_srv->stream = osmo_stream_srv_create(prim_link, prim_link->stream, fd,
_osmo_prim_srv_read_cb,
_osmo_prim_srv_closed_cb,
prim_srv);
if (!prim_srv->stream) {
talloc_free(prim_srv);
return NULL;
}
/* Inherit link priv pointer by default, user can later set it through API: */
prim_srv->priv = prim_link->priv;
return prim_srv;
}
static void osmo_prim_srv_free(struct osmo_prim_srv *prim_srv)
{
talloc_free(prim_srv);
}
void osmo_prim_srv_set_name(struct osmo_prim_srv *prim_srv, const char *name)
{
osmo_stream_srv_set_name(prim_srv->stream, name);
}
struct osmo_prim_srv_link *osmo_prim_srv_get_link(struct osmo_prim_srv *prim_srv)
{
return prim_srv->link;
}
void osmo_prim_srv_set_priv(struct osmo_prim_srv *prim_srv, void *priv)
{
prim_srv->priv = priv;
}
void *osmo_prim_srv_get_priv(const struct osmo_prim_srv *prim_srv)
{
return prim_srv->priv;
}
void osmo_prim_srv_close(struct osmo_prim_srv *prim_srv)
{
osmo_stream_srv_destroy(prim_srv->stream);
/* we free prim_srv in _osmo_prim_srv_closed_cb() */
}
/******************************
* osmo_prim_srv_link
******************************/
#define LOGSRVLINK(srv, lvl, fmt, args...) LOGP((srv)->log_cat, lvl, fmt, ## args)
/* accept connection coming from PCU */
static int _osmo_prim_srv_link_accept(struct osmo_stream_srv_link *link, int fd)
{
struct osmo_prim_srv *prim_srv;
struct osmo_prim_srv_link *prim_link = osmo_stream_srv_link_get_data(link);
prim_srv = osmo_prim_srv_alloc(prim_link, fd);
if (prim_link->opened_conn_cb)
return prim_link->opened_conn_cb(prim_srv);
return 0;
}
struct osmo_prim_srv_link *osmo_prim_srv_link_alloc(void *ctx)
{
struct osmo_prim_srv_link *prim_link;
prim_link = talloc_zero(ctx, struct osmo_prim_srv_link);
if (!prim_link)
return NULL;
prim_link->stream = osmo_stream_srv_link_create(prim_link);
if (!prim_link->stream) {
talloc_free(prim_link);
return NULL;
}
osmo_stream_srv_link_set_data(prim_link->stream, prim_link);
osmo_stream_srv_link_set_domain(prim_link->stream, AF_UNIX);
osmo_stream_srv_link_set_type(prim_link->stream, SOCK_SEQPACKET);
osmo_stream_srv_link_set_accept_cb(prim_link->stream, _osmo_prim_srv_link_accept);
prim_link->log_cat = DLGLOBAL;
prim_link->rx_msgb_alloc_len = 1600 - sizeof(struct osmo_prim_pkt_hdr);
return prim_link;
}
void osmo_prim_srv_link_free(struct osmo_prim_srv_link *prim_link)
{
if (!prim_link)
return;
if (prim_link->stream) {
osmo_stream_srv_link_close(prim_link->stream);
osmo_stream_srv_link_destroy(prim_link->stream);
prim_link->stream = NULL;
}
talloc_free(prim_link);
}
void osmo_prim_srv_link_set_name(struct osmo_prim_srv_link *prim_link, const char *name)
{
osmo_stream_srv_link_set_name(prim_link->stream, name);
}
int osmo_prim_srv_link_set_addr(struct osmo_prim_srv_link *prim_link, const char *path)
{
osmo_talloc_replace_string(prim_link, &prim_link->addr, path);
osmo_stream_srv_link_set_addr(prim_link->stream, path);
return 0;
}
const char *osmo_prim_srv_link_get_addr(struct osmo_prim_srv_link *prim_link)
{
return prim_link->addr;
}
void osmo_prim_srv_link_set_priv(struct osmo_prim_srv_link *prim_link, void *priv)
{
prim_link->priv = priv;
}
void *osmo_prim_srv_link_get_priv(const struct osmo_prim_srv_link *prim_link)
{
return prim_link->priv;
}
void osmo_prim_srv_link_set_log_category(struct osmo_prim_srv_link *prim_link, int log_cat)
{
prim_link->log_cat = log_cat;
}
void osmo_prim_srv_link_set_opened_conn_cb(struct osmo_prim_srv_link *prim_link, osmo_prim_srv_conn_cb opened_conn_cb)
{
prim_link->opened_conn_cb = opened_conn_cb;
}
void osmo_prim_srv_link_set_closed_conn_cb(struct osmo_prim_srv_link *prim_link, osmo_prim_srv_conn_cb closed_conn_cb)
{
prim_link->closed_conn_cb = closed_conn_cb;
}
void osmo_prim_srv_link_set_rx_sapi_version_cb(struct osmo_prim_srv_link *prim_link, osmo_prim_srv_rx_sapi_version rx_sapi_version_cb)
{
prim_link->rx_sapi_version_cb = rx_sapi_version_cb;
}
void osmo_prim_srv_link_set_rx_cb(struct osmo_prim_srv_link *prim_link, osmo_prim_srv_rx_cb rx_cb)
{
prim_link->rx_cb = rx_cb;
}
void osmo_prim_srv_link_set_rx_msgb_alloc_len(struct osmo_prim_srv_link *prim_link, size_t alloc_len)
{
prim_link->rx_msgb_alloc_len = alloc_len;
}
int osmo_prim_srv_link_open(struct osmo_prim_srv_link *prim_link)
{
int rc;
if (!prim_link->addr) {
LOGSRVLINK(prim_link, LOGL_ERROR, "Cannot open, Address not configured\n");
return -1;
}
rc = osmo_stream_srv_link_open(prim_link->stream);
LOGSRVLINK(prim_link, LOGL_INFO, "Started listening on Lower Layer Unix Domain Socket: %s\n", prim_link->addr);
return rc;
}