osmo-e1d/src/octoi/octoi_fsm.c

249 lines
7.3 KiB
C

/*
* octoi_fsm.c - OCTOI Protocol / Finite State Machine interface
*
* (C) 2022 by Harald Welte <laforge@osmocom.org>
*
* 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.
*/
#include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/octoi/e1oip_proto.h>
#include "octoi_sock.h"
#include "octoi_fsm.h"
#include "e1oip.h"
/* how many microseconds ago was 'old_ts'? */
int32_t ts_us_ago(const struct timespec *old_ts)
{
struct timespec ts_now, ts_diff;
clock_gettime(CLOCK_MONOTONIC, &ts_now);
timespecsub(&ts_now, old_ts, &ts_diff);
if (ts_diff.tv_sec > INT32_MAX / 1000000)
return INT32_MAX;
return ts_diff.tv_sec * 1000000 + ts_diff.tv_nsec / 1000;
}
const struct value_string octoi_fsm_event_names[] = {
{ OCTOI_EV_RX_TDM_DATA, "RX_TDM_DATA" },
{ OCTOI_EV_RX_ECHO_REQ, "RX_ECHO_REQ" },
{ OCTOI_EV_RX_ECHO_RESP, "RX_ECHO_RESP" },
{ OCTOI_EV_RX_ERROR_IND, "RX_ERROR_IND" },
{ OCTOI_SRV_EV_RX_SERVICE_REQ, "RX_SERVICE_REQ" },
{ OCTOI_SRV_EV_RX_AUTH_VEC, "RX_AUTH_VEC" },
{ OCTOI_SRV_EV_RX_AUTH_RESP, "RX_AUTH_RESP" },
{ OCTOI_CLNT_EV_REQUEST_SERVICE,"REQUEST_SERVICE" },
{ OCTOI_CLNT_EV_RX_AUTH_REQ, "RX_AUTH_REQ" },
{ OCTOI_CLNT_EV_RX_SVC_ACK, "RX_SERVICE_ACK" },
{ OCTOI_CLNT_EV_RX_SVC_REJ, "RX_SERVICE_REJ" },
{ OCTOI_CLNT_EV_RX_REDIR_CMD, "RX_REDIR_CMD" },
{ 0, NULL }
};
/* ensure given fixed-length string is zero-terminated */
#define ENSURE_ZERO_TERM(x) ensure_zero_term(x, sizeof(x))
static void ensure_zero_term(char *buf, size_t len)
{
for (unsigned int i = 0; i < len; i++) {
if (buf[i] == '\0')
return;
}
buf[len-1] = '\0';
}
/* verify if the given OCTOI message is consistent */
static bool octoi_msg_validate(struct osmo_fsm_inst *fi, struct msgb *msg)
{
struct e1oip_msg *eip = msgb_l1(msg);
/* ensure that the minimum length is >= header length, and that the version matches */
if (msgb_l1len(msg) < sizeof(eip->hdr)) {
LOGPFSML(fi, LOGL_INFO, "Rx short message (%u < %zu)\n", msgb_l1len(msg), sizeof(eip->hdr));
return false;
}
if (eip->hdr.version != E1OIP_VERSION) {
LOGPFSML(fi, LOGL_INFO, "Rx unsupported version (%u != %u)\n", eip->hdr.version,
E1OIP_VERSION);
return false;
}
switch (eip->hdr.msg_type) {
case E1OIP_MSGT_ECHO_REQ:
if (msgb_l2len(msg) < sizeof(eip->u.echo))
goto err_msg_len;
break;
case E1OIP_MSGT_ECHO_RESP:
if (msgb_l2len(msg) < sizeof(eip->u.echo))
goto err_msg_len;
break;
case E1OIP_MSGT_TDM_DATA:
if (msgb_l2len(msg) < sizeof(eip->u.tdm_hdr))
goto err_msg_len;
break;
case E1OIP_MSGT_SERVICE_REQ:
if (msgb_l2len(msg) < sizeof(eip->u.service_req))
goto err_msg_len;
ENSURE_ZERO_TERM(eip->u.service_req.subscriber_id);
ENSURE_ZERO_TERM(eip->u.service_req.software_id);
ENSURE_ZERO_TERM(eip->u.service_req.software_version);
break;
case E1OIP_MSGT_SERVICE_ACK:
if (msgb_l2len(msg) < sizeof(eip->u.service_ack))
goto err_msg_len;
ENSURE_ZERO_TERM(eip->u.service_ack.server_id);
ENSURE_ZERO_TERM(eip->u.service_ack.software_id);
ENSURE_ZERO_TERM(eip->u.service_ack.software_version);
break;
case E1OIP_MSGT_SERVICE_REJ:
if (msgb_l2len(msg) < sizeof(eip->u.service_rej))
goto err_msg_len;
ENSURE_ZERO_TERM(eip->u.service_rej.reject_message);
break;
case E1OIP_MSGT_REDIR_CMD:
if (msgb_l2len(msg) < sizeof(eip->u.redir_cmd))
goto err_msg_len;
ENSURE_ZERO_TERM(eip->u.redir_cmd.server_ip);
break;
case E1OIP_MSGT_AUTH_REQ:
if (msgb_l2len(msg) < sizeof(eip->u.auth_req))
goto err_msg_len;
if (eip->u.auth_req.rand_len > sizeof(eip->u.auth_req.rand))
goto err_ie_len;
if (eip->u.auth_req.autn_len > sizeof(eip->u.auth_req.autn))
goto err_ie_len;
break;
case E1OIP_MSGT_AUTH_RESP:
if (msgb_l2len(msg) < sizeof(eip->u.auth_resp))
goto err_msg_len;
if (eip->u.auth_resp.res_len > sizeof(eip->u.auth_resp.res))
goto err_ie_len;
if (eip->u.auth_resp.auts_len > sizeof(eip->u.auth_resp.auts))
goto err_ie_len;
break;
case E1OIP_MSGT_ERROR_IND:
if (msgb_l2len(msg) < sizeof(eip->u.error_ind))
goto err_msg_len;
ENSURE_ZERO_TERM(eip->u.error_ind.error_message);
break;
default:
LOGPFSML(fi, LOGL_NOTICE, "Rx unknown OCTOI message type 0x%02x\n", eip->hdr.msg_type);
return false;
}
return true;
err_msg_len:
LOGPFSML(fi, LOGL_NOTICE, "Rx truncated OCTOI message 0x%02x\n", eip->hdr.msg_type);
return false;
err_ie_len:
LOGPFSML(fi, LOGL_NOTICE, "Rx invalid IE length in OCTOI message 0x%02x\n", eip->hdr.msg_type);
return false;
}
/* call-back function for every received OCTOI socket message for given peer */
int _octoi_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg)
{
struct osmo_fsm_inst *fi = peer->priv;
struct e1oip_hdr *e1h = msgb_l1(msg);
OSMO_ASSERT(fi);
OSMO_ASSERT(msgb_l1(msg));
OSMO_ASSERT(msgb_l2(msg));
if (peer->iline) {
iline_ctr_add(peer->iline, LINE_CTR_E1oIP_RX_PACKETS, 1);
iline_ctr_add(peer->iline, LINE_CTR_E1oIP_RX_BYTES,
peer->sock->iph_udph_size + msgb_length(msg));
}
if (!octoi_msg_validate(fi, msg))
return -1;
switch (e1h->msg_type) {
case E1OIP_MSGT_TDM_DATA:
osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_TDM_DATA, msg);
break;
case E1OIP_MSGT_ECHO_REQ:
osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_ECHO_REQ, msg);
break;
case E1OIP_MSGT_ECHO_RESP:
osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_ECHO_RESP, msg);
break;
case E1OIP_MSGT_ERROR_IND:
osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_ERROR_IND, msg);
break;
case E1OIP_MSGT_SERVICE_REQ:
osmo_fsm_inst_dispatch(fi, OCTOI_SRV_EV_RX_SERVICE_REQ, msg);
break;
case E1OIP_MSGT_AUTH_RESP:
osmo_fsm_inst_dispatch(fi, OCTOI_SRV_EV_RX_AUTH_RESP, msg);
break;
case E1OIP_MSGT_SERVICE_ACK:
osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_SVC_ACK, msg);
break;
case E1OIP_MSGT_SERVICE_REJ:
osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_SVC_REJ, msg);
break;
case E1OIP_MSGT_REDIR_CMD:
osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_REDIR_CMD, msg);
break;
case E1OIP_MSGT_AUTH_REQ:
osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_AUTH_REQ, msg);
break;
default:
LOGPFSML(fi, LOGL_NOTICE, "Rx Unknown OCTOI message type 0x%02x\n", e1h->msg_type);
break;
}
msgb_free(msg);
return 0;
}
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>
#include "e1oip.h"
void vty_show_octoi_sock(struct vty *vty, struct octoi_sock *sock)
{
struct octoi_peer *peer;
vty_out(vty, "OCTOI %s Socket on "OSMO_SOCKADDR_STR_FMT"%s",
sock->cfg.server_mode ? "Server" : "Client",
OSMO_SOCKADDR_STR_FMT_ARGS(&sock->cfg.local), VTY_NEWLINE);
llist_for_each_entry(peer, &sock->peers, list) {
vty_out(vty, " Peer '%s', Remote "OSMO_SOCKADDR_STR_FMT", State %s%s",
peer->name, OSMO_SOCKADDR_STR_FMT_ARGS(&peer->cfg.remote),
osmo_fsm_inst_state_name(peer->priv), VTY_NEWLINE);
if (peer->iline) {
vty_out_rate_ctr_group(vty, " ", peer->iline->ctrs);
vty_out_stat_item_group(vty, " ", peer->iline->stats);
}
}
}