aboutsummaryrefslogtreecommitdiffstats
path: root/src/octoi/octoi_fsm.c
blob: 06d2105aed3f4870eedb0f71e94928ee285fdbf1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*
 * 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);
		}
	}
}