blob: 1e325f5701890e39377f72deb6ded42942c7b993 [file] [log] [blame]
Neels Hofmeyr72992152020-09-19 02:36:08 +02001/*
2 * (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
3 * All Rights Reserved
4 *
5 * SPDX-License-Identifier: AGPL-3.0+
6 *
7 * Author: Neels Hofmeyr
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include <osmocom/core/logging.h>
24
25#include <osmocom/sccp/sccp_types.h>
26#include <osmocom/sigtran/sccp_sap.h>
27#include <osmocom/sigtran/sccp_helpers.h>
28
29#include <osmocom/smlc/debug.h>
30#include <osmocom/smlc/smlc_data.h>
31#include <osmocom/smlc/sccp_lb_inst.h>
32#include <osmocom/smlc/lb_peer.h>
33
34/* We need an unused SCCP conn_id across all SCCP users. */
35int sccp_lb_inst_next_conn_id()
36{
37 static uint32_t next_id = 1;
38 int i;
39
40 /* This looks really suboptimal, but in most cases the static next_id should indicate exactly the next unused
41 * conn_id, and we only iterate all conns once to make super sure that it is not already in use. */
42
43 for (i = 0; i < 0xFFFFFF; i++) {
44 struct lb_peer *lb_peer;
45 uint32_t conn_id = next_id;
46 bool conn_id_already_used = false;
47 next_id = (next_id + 1) & 0xffffff;
48
49 llist_for_each_entry(lb_peer, &g_smlc->lb->lb_peers, entry) {
50 struct lb_conn *conn;
51 lb_peer_for_each_lb_conn(conn, lb_peer) {
52 if (conn_id == conn->sccp_conn_id) {
53 conn_id_already_used = true;
54 break;
55 }
56 }
57 if (conn_id_already_used)
58 break;
59 }
60
61 if (!conn_id_already_used)
62 return conn_id;
63 }
64 return -1;
65}
66
67static int sccp_lb_sap_up(struct osmo_prim_hdr *oph, void *_scu);
68
69struct sccp_lb_inst *sccp_lb_init(void *talloc_ctx, struct osmo_sccp_instance *sccp, enum osmo_sccp_ssn ssn,
70 const char *sccp_user_name)
71{
72 struct sccp_lb_inst *sli = talloc(talloc_ctx, struct sccp_lb_inst);
73 OSMO_ASSERT(sli);
74 *sli = (struct sccp_lb_inst){
75 .sccp = sccp,
76 };
77
78 INIT_LLIST_HEAD(&sli->lb_peers);
79 INIT_LLIST_HEAD(&sli->lb_conns);
80
81 osmo_sccp_local_addr_by_instance(&sli->local_sccp_addr, sccp, ssn);
82 sli->scu = osmo_sccp_user_bind(sccp, sccp_user_name, sccp_lb_sap_up, ssn);
83 osmo_sccp_user_set_priv(sli->scu, sli);
84
85 return sli;
86}
87
88static int sccp_lb_sap_up(struct osmo_prim_hdr *oph, void *_scu)
89{
90 struct osmo_sccp_user *scu = _scu;
91 struct sccp_lb_inst *sli = osmo_sccp_user_get_priv(scu);
92 struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
93 struct osmo_sccp_addr *my_addr;
94 struct osmo_sccp_addr *peer_addr;
95 uint32_t conn_id;
96 int rc;
97
98 switch (OSMO_PRIM_HDR(oph)) {
99 case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
100 /* indication of new inbound connection request */
101 conn_id = prim->u.connect.conn_id;
102 my_addr = &prim->u.connect.called_addr;
103 peer_addr = &prim->u.connect.calling_addr;
104 LOG_SCCP_LB_CO(sli, peer_addr, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
105
106 if (!msgb_l2(oph->msg) || msgb_l2len(oph->msg) == 0) {
107 LOG_SCCP_LB_CO(sli, peer_addr, conn_id, LOGL_NOTICE, "Received invalid N-CONNECT.ind\n");
108 rc = -1;
109 break;
110 }
111
112 if (osmo_sccp_addr_ri_cmp(&sli->local_sccp_addr, my_addr))
113 LOG_SCCP_LB_CO(sli, peer_addr, conn_id, LOGL_ERROR,
114 "Rx N-CONNECT: Called address is %s != local address %s\n",
115 osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, my_addr),
116 osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, &sli->local_sccp_addr));
117
118 /* ensure the local SCCP socket is ACTIVE */
119 osmo_sccp_tx_conn_resp(scu, conn_id, my_addr, NULL, 0);
120
121 rc = lb_peer_up_l2(sli, peer_addr, true, conn_id, oph->msg);
122 if (rc)
123 osmo_sccp_tx_disconn(scu, conn_id, my_addr, SCCP_RETURN_CAUSE_UNQUALIFIED);
124 break;
125
126 case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
127 /* connection-oriented data received */
128 conn_id = prim->u.data.conn_id;
129 LOG_SCCP_LB_CO(sli, NULL, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
130
131 rc = lb_peer_up_l2(sli, NULL, true, conn_id, oph->msg);
132 break;
133
134 case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
135 /* indication of disconnect */
136 conn_id = prim->u.disconnect.conn_id;
137 LOG_SCCP_LB_CO(sli, NULL, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
138
139 /* If there is no L2 payload in the N-DISCONNECT, no need to dispatch up_l2(). */
140 if (msgb_l2len(oph->msg))
141 rc = lb_peer_up_l2(sli, NULL, true, conn_id, oph->msg);
142 else
143 rc = 0;
144
145 /* Make sure the lb_conn is dropped. It might seem more optimal to combine the disconnect() into
146 * up_l2(), but since an up_l2() dispatch might already cause the lb_conn to be discarded for other
147 * reasons, a separate disconnect() with a separate conn_id lookup is actually necessary. */
148 sccp_lb_disconnect(sli, conn_id, 0);
149 break;
150
151 case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
152 /* connection-less data received */
153 my_addr = &prim->u.unitdata.called_addr;
154 peer_addr = &prim->u.unitdata.calling_addr;
155 LOG_SCCP_LB_CL(sli, peer_addr, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
156
157 if (osmo_sccp_addr_ri_cmp(&sli->local_sccp_addr, my_addr))
158 LOG_SCCP_LB_CL(sli, peer_addr, LOGL_ERROR,
159 "Rx N-UNITDATA: Called address is %s != local address %s\n",
160 osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, my_addr),
161 osmo_sccp_inst_addr_to_str_c(OTC_SELECT, sli->sccp, &sli->local_sccp_addr));
162
163 rc = lb_peer_up_l2(sli, peer_addr, false, 0, oph->msg);
164 break;
165
166 default:
167 LOG_SCCP_LB_CL(sli, NULL, LOGL_ERROR, "%s(%s) unsupported\n", __func__, osmo_scu_prim_name(oph));
168 rc = -1;
169 break;
170 }
171
172 msgb_free(oph->msg);
173 return rc;
174}
175
176/* Push some padding if necessary to reach a multiple-of-eight offset to be msgb_push() an osmo_scu_prim that will then
177 * be 8-byte aligned. */
178static void msgb_pad_mod8(struct msgb *msg)
179{
180 uint8_t mod8 = (intptr_t)(msg->data) % 8;
181 if (mod8)
182 msgb_push(msg, mod8);
183}
184
185static int sccp_lb_sap_down(struct sccp_lb_inst *sli, struct osmo_prim_hdr *oph)
186{
187 int rc;
188 if (!sli->scu) {
Pau Espin Pedrol44a2a852021-06-04 18:07:05 +0200189 rate_ctr_inc(rate_ctr_group_get_ctr(g_smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_CONN_NOT_READY));
Neels Hofmeyr72992152020-09-19 02:36:08 +0200190 return -EIO;
191 }
192 rc = osmo_sccp_user_sap_down_nofree(sli->scu, oph);
193 if (rc >= 0)
Pau Espin Pedrol44a2a852021-06-04 18:07:05 +0200194 rate_ctr_inc(rate_ctr_group_get_ctr(g_smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS));
Neels Hofmeyr72992152020-09-19 02:36:08 +0200195 else
Pau Espin Pedrol44a2a852021-06-04 18:07:05 +0200196 rate_ctr_inc(rate_ctr_group_get_ctr(g_smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND));
Neels Hofmeyr72992152020-09-19 02:36:08 +0200197 return rc;
198}
199
200int sccp_lb_down_l2_co_initial(struct sccp_lb_inst *sli,
201 const struct osmo_sccp_addr *called_addr,
202 uint32_t conn_id, struct msgb *l2)
203{
204 struct osmo_scu_prim *prim;
205
206 l2->l2h = l2->data;
207
208 msgb_pad_mod8(l2);
209 prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim));
210 prim->u.connect = (struct osmo_scu_connect_param){
211 .called_addr = *called_addr,
212 .calling_addr = sli->local_sccp_addr,
213 .sccp_class = 2,
214 //.importance = ?,
215 .conn_id = conn_id,
216 };
217 osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, l2);
218 return sccp_lb_sap_down(sli, &prim->oph);
219}
220
221int sccp_lb_down_l2_co(struct sccp_lb_inst *sli, uint32_t conn_id, struct msgb *l2)
222{
223 struct osmo_scu_prim *prim;
224
225 l2->l2h = l2->data;
226
227 msgb_pad_mod8(l2);
228 prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim));
229 prim->u.data.conn_id = conn_id;
230 osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, l2);
231 return sccp_lb_sap_down(sli, &prim->oph);
232}
233
234int sccp_lb_down_l2_cl(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *called_addr, struct msgb *l2)
235{
236 struct osmo_scu_prim *prim;
237
238 l2->l2h = l2->data;
239
240 msgb_pad_mod8(l2);
241 prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim));
242 prim->u.unitdata = (struct osmo_scu_unitdata_param){
243 .called_addr = *called_addr,
244 .calling_addr = sli->local_sccp_addr,
245 };
246 osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST, l2);
247 return sccp_lb_sap_down(sli, &prim->oph);
248}
249
250int sccp_lb_disconnect(struct sccp_lb_inst *sli, uint32_t conn_id, uint32_t cause)
251{
252 return osmo_sccp_tx_disconn(sli->scu, conn_id, NULL, cause);
253}