blob: 904f106195231e5b8444fbd150ed25dc727f0ebf [file] [log] [blame]
Vadim Yanitskiye774fae2024-06-11 03:18:09 +07001module S1AP_Server {
2
3/* S1AP Server, runs on top of S1AP_CodecPort, accepting the S1AP
4 * connections and forwarding S1AP PDUs to/from the ConnHdlr components.
5 * A ConnHdlr component may subscribe for one or more S1AP connections
6 * using the Global_ENB_ID value, which is sent in S1AP SetupReq.
7 *
8 * (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
9 * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
10 *
11 * All rights reserved.
12 *
13 * Released under the terms of GNU General Public License, Version 2 or
14 * (at your option) any later version.
15 *
16 * SPDX-License-Identifier: GPL-2.0-or-later
17 */
18
19import from General_Types all;
20import from Osmocom_Types all;
21import from IPL4asp_Types all;
22
23import from S1AP_CodecPort all;
24import from S1AP_CodecPort_CtrlFunct all;
25import from S1AP_Types all;
26import from S1AP_Constants all;
27import from S1AP_PDU_Contents all;
28import from S1AP_PDU_Descriptions all;
29import from S1AP_IEs all;
30import from S1AP_Templates all;
31
32type enumerated S1APSRV_Event {
33 S1APSRV_EVENT_CONN_UP,
34 S1APSRV_EVENT_CONN_DOWN
35};
36
37type port S1APSRV_CONN_PT message {
38 inout S1AP_PDU, S1APSRV_Event;
39} with { extension "internal" };
40
41type component S1APSRV_ConnHdlr {
42 port S1APSRV_CONN_PT S1AP_CONN;
43 port S1APSRV_PROC_PT S1AP_PROC;
44};
45
46type record S1APSRV_ConnParams {
47 HostName local_ip,
48 PortNumber local_port
49};
50
51type component S1AP_Server_CT {
52 /* port facing to the SUT */
53 port S1AP_CODEC_PT S1AP;
54 /* all S1APSRV_ConnHdlr S1AP ports connect here */
55 port S1APSRV_CONN_PT S1AP_CLIENT;
56 /* procedure based port to register for incoming connections */
57 port S1APSRV_PROC_PT S1AP_PROC;
58
59 /* active eNB connections */
60 var ConnList g_conn_list;
61 /* registered ConnHdlr */
62 var ConnHdlrList g_conn_hdlr_list;
63
64 var S1APSRV_ConnParams g_cpars;
65 var ConnectionId g_s1ap_conn_id := -1;
66};
67
68
69/* represents a single eNB connection */
70private type record ConnData {
71 ConnectionId conn_id,
72 Global_ENB_ID genb_id /* can be unbound */
73};
74private type record of ConnData ConnList;
75
76/* represents a single ConnHdlr item */
77private type record ConnHdlrData {
78 S1APSRV_ConnHdlr vc_conn,
79 Global_ENB_ID genb_id,
80 ConnectionId conn_id /* can be -1 */
81};
82private type record of ConnHdlrData ConnHdlrList;
83
84private template (present) S1AP_RecvFrom
85tr_S1AP_RecvFrom_R(template (present) S1AP_PDU msg := ?,
86 template (present) ConnectionId conn_id := ?) := {
87 connId := conn_id,
88 remName := ?,
89 remPort := ?,
90 locName := ?,
91 locPort := ?,
92 msg := msg
93};
94
Vadim Yanitskiyceb04772024-06-12 06:37:47 +070095const SctpTuple c_SctpTuple_S1AP := {
Vadim Yanitskiye774fae2024-06-11 03:18:09 +070096 sinfo_stream := omit,
Vadim Yanitskiyceb04772024-06-12 06:37:47 +070097 sinfo_ppid := 18, /* S1AP */
Vadim Yanitskiye774fae2024-06-11 03:18:09 +070098 remSocks := omit,
99 assocId := omit
100};
101
102/***********************************************************************************
103 * Connection management API
104 **********************************************************************************/
105
106/* find a connection [index] by a connection ID */
107private function f_conn_find_by_conn_id(ConnectionId conn_id)
108runs on S1AP_Server_CT return integer {
109 for (var integer i := 0; i < lengthof(g_conn_list); i := i + 1) {
110 if (g_conn_list[i].conn_id == conn_id) {
111 return i;
112 }
113 }
114
115 return -1;
116}
117
118/* find a connection [index] by a global eNB ID */
119private function f_conn_find_by_genb_id(Global_ENB_ID genb_id)
120runs on S1AP_Server_CT return integer {
121 for (var integer i := 0; i < lengthof(g_conn_list); i := i + 1) {
122 if (isbound(g_conn_list[i].genb_id) and
123 g_conn_list[i].genb_id == genb_id) {
124 return i;
125 }
126 }
127
128 return -1;
129}
130
131/* add a new connection, return its index */
132private function f_conn_add(ConnectionId conn_id)
133runs on S1AP_Server_CT return integer {
134 var ConnData conn := { conn_id, - };
135 var integer idx;
136
137 if (f_conn_find_by_conn_id(conn_id) != -1) {
138 setverdict(fail, "Connection (id=", conn_id, ") is already added");
139 mtc.stop;
140 }
141
142 idx := lengthof(g_conn_list);
143 g_conn_list := g_conn_list & { conn };
144 log("Connection (id=", conn_id, ") is registered");
145
146 return idx;
147}
148
149/* del an existing connection */
150private function f_conn_del(ConnectionId conn_id)
151runs on S1AP_Server_CT {
152 var ConnList conn_list := { };
153
154 for (var integer i := 0; i < lengthof(g_conn_list); i := i + 1) {
155 if (g_conn_list[i].conn_id == conn_id) {
156 if (isbound(g_conn_list[i].genb_id)) {
157 f_ConnHdlr_update(g_conn_list[i].genb_id, -1);
158 }
159 } else {
160 conn_list := conn_list & { g_conn_list[i] };
161 }
162 }
163
164 if (lengthof(conn_list) == lengthof(g_conn_list)) {
165 setverdict(fail, "Connection (id=", conn_id, ") is not known");
166 mtc.stop;
167 }
168
169 g_conn_list := conn_list;
170 log("Connection (id=", conn_id, ") is deleted");
171}
172
173/* add a new connection, return its index */
174private function f_conn_set_genb_id(ConnectionId conn_id, Global_ENB_ID genb_id)
175runs on S1AP_Server_CT {
176 var integer idx;
177
178 if (f_conn_find_by_genb_id(genb_id) != -1) {
179 setverdict(fail, "Duplicate Global eNB ID ", genb_id);
180 mtc.stop;
181 }
182
183 idx := f_conn_find_by_conn_id(conn_id);
184 if (idx == -1) {
185 setverdict(fail, "Connection (id=", conn_id, ") is not known");
186 mtc.stop;
187 }
188
189 g_conn_list[idx].genb_id := genb_id;
190
191 f_ConnHdlr_update(genb_id, conn_id);
192}
193
194private function f_conn_close(ConnectionId conn_id)
195runs on S1AP_Server_CT {
196 log("Closing an eNB connection (id=", conn_id, ")");
197 S1AP_CodecPort_CtrlFunct.f_IPL4_close(S1AP, conn_id,
Vadim Yanitskiyceb04772024-06-12 06:37:47 +0700198 { sctp := c_SctpTuple_S1AP });
Vadim Yanitskiye774fae2024-06-11 03:18:09 +0700199 f_conn_del(conn_id);
200}
201
202private function f_conn_close_by_genb_id(Global_ENB_ID genb_id)
203runs on S1AP_Server_CT {
204 var integer idx;
205
206 idx := f_conn_find_by_genb_id(genb_id);
207 if (idx == -1) {
208 setverdict(fail, "There is no connection for Global eNB ID ", genb_id);
209 mtc.stop;
210 }
211
212 f_conn_close(g_conn_list[idx].conn_id);
213}
214
215/***********************************************************************************
216 * ConnHdlr management API
217 **********************************************************************************/
218
219/* find a ConnHdlr [index] by a connection ID */
220private function f_ConnHdlr_find_by_conn_id(ConnectionId conn_id)
221runs on S1AP_Server_CT return integer {
222 for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) {
223 if (g_conn_hdlr_list[i].conn_id == conn_id) {
224 return i;
225 }
226 }
227
228 return -1;
229}
230
231/* find a ConnHdlr [index] by a global eNB ID */
232private function f_ConnHdlr_find_by_genb_id(Global_ENB_ID genb_id)
233runs on S1AP_Server_CT return integer {
234 for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) {
235 if (g_conn_hdlr_list[i].genb_id == genb_id) {
236 return i;
237 }
238 }
239
240 return -1;
241}
242
243/* find a ConnHdlr [index] by a component reference */
244private function f_ConnHdlr_find_by_vc_conn(S1APSRV_ConnHdlr vc_conn)
245runs on S1AP_Server_CT return integer {
246 for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) {
247 if (g_conn_hdlr_list[i].vc_conn == vc_conn) {
248 return i;
249 }
250 }
251
252 return -1;
253}
254
255private function f_ConnHdlr_add(S1APSRV_ConnHdlr vc_conn, Global_ENB_ID genb_id)
256runs on S1AP_Server_CT {
257 var ConnectionId conn_id := -1;
258 var integer idx;
259
260 if (f_ConnHdlr_find_by_genb_id(genb_id) != -1) {
261 setverdict(fail, "Global eNB ID ", genb_id, " is already registered");
262 mtc.stop;
263 }
264
265 idx := f_conn_find_by_genb_id(genb_id);
266 if (idx != -1) {
267 conn_id := g_conn_list[idx].conn_id;
268 }
269
270 g_conn_hdlr_list := g_conn_hdlr_list & { {vc_conn, genb_id, conn_id} };
271 log("Global eNB ID ", genb_id, " has been registered");
272}
273
274private function f_ConnHdlr_del(integer idx)
275runs on S1AP_Server_CT {
276 var ConnHdlrList conn_hdlr_list := { };
277
278 for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) {
279 if (i != idx) {
280 conn_hdlr_list := conn_hdlr_list & { g_conn_hdlr_list[i] };
281 }
282 }
283
284 g_conn_hdlr_list := conn_hdlr_list;
285}
286
287private function f_ConnHdlr_del_by_genb_id(Global_ENB_ID genb_id)
288runs on S1AP_Server_CT {
289 var integer idx;
290
291 idx := f_ConnHdlr_find_by_genb_id(genb_id);
292 if (idx == -1) {
293 setverdict(fail, "Global eNB ID ", genb_id, " is not registered");
294 mtc.stop;
295 }
296
297 f_ConnHdlr_del(idx);
298 log("Global eNB ID ", genb_id, " has been unregistered");
299}
300
301private function f_ConnHdlr_update(Global_ENB_ID genb_id, ConnectionId conn_id)
302runs on S1AP_Server_CT {
303 for (var integer i := 0; i < lengthof(g_conn_hdlr_list); i := i + 1) {
304 if (g_conn_hdlr_list[i].genb_id == genb_id) {
305 g_conn_hdlr_list[i].conn_id := conn_id;
306 /* notify the ConnHdlr about connection state */
307 var S1APSRV_Event ev;
308 if (conn_id == -1) {
309 ev := S1APSRV_EVENT_CONN_DOWN;
310 } else {
311 ev := S1APSRV_EVENT_CONN_UP;
312 }
313 S1AP_CLIENT.send(ev) to g_conn_hdlr_list[i].vc_conn;
314 }
315 }
316}
317
318signature S1APSRV_register(in S1APSRV_ConnHdlr vc_conn, in Global_ENB_ID genb_id);
319signature S1APSRV_unregister(in S1APSRV_ConnHdlr vc_conn, in Global_ENB_ID genb_id);
320signature S1APSRV_close_conn(in S1APSRV_ConnHdlr vc_conn, in Global_ENB_ID genb_id);
321
322type port S1APSRV_PROC_PT procedure {
323 inout S1APSRV_register;
324 inout S1APSRV_unregister;
325 inout S1APSRV_close_conn;
326} with { extension "internal" };
327
328function f_ConnHdlr_register(Global_ENB_ID genb_id)
329runs on S1APSRV_ConnHdlr {
330 S1AP_PROC.call(S1APSRV_register:{self, genb_id}) {
331 [] S1AP_PROC.getreply(S1APSRV_register:{?, ?}) { };
332 }
333}
334
335function f_ConnHdlr_unregister(Global_ENB_ID genb_id)
336runs on S1APSRV_ConnHdlr {
337 S1AP_PROC.call(S1APSRV_unregister:{self, genb_id}) {
338 [] S1AP_PROC.getreply(S1APSRV_unregister:{?, ?}) { };
339 }
340}
341
342function f_ConnHdlr_close_conn(Global_ENB_ID genb_id)
343runs on S1APSRV_ConnHdlr {
344 S1AP_PROC.call(S1APSRV_close_conn:{self, genb_id}) {
345 [] S1AP_PROC.getreply(S1APSRV_close_conn:{?, ?}) { };
346 }
347}
348
349function main(S1APSRV_ConnParams cpars) runs on S1AP_Server_CT {
350 var Result res;
351
352 g_cpars := cpars;
353 g_conn_list := { };
354 g_conn_hdlr_list := { };
355
356 map(self:S1AP, system:S1AP_CODEC_PT);
357 res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP,
358 cpars.local_ip, cpars.local_port,
Vadim Yanitskiyceb04772024-06-12 06:37:47 +0700359 { sctp := c_SctpTuple_S1AP });
Vadim Yanitskiye774fae2024-06-11 03:18:09 +0700360 if (not ispresent(res.connId)) {
361 setverdict(fail, "Could not create an S1AP socket, check your configuration");
362 mtc.stop;
363 }
364 g_s1ap_conn_id := res.connId;
365
366 log("SCTP server listening on ", cpars.local_ip, ":", cpars.local_port);
367
368 while (true) {
369 var S1APSRV_ConnHdlr vc_conn;
370 var S1AP_RecvFrom mrf;
371 var S1AP_PDU msg;
372
373 var Global_ENB_ID genb_id;
374 var PortEvent pev;
375
376 alt {
377 /* S1AP PDU from a peer (eNB) */
378 [] S1AP.receive(tr_S1AP_RecvFrom_R) -> value mrf {
379 if (match(mrf.msg, tr_S1AP_SetupReq)) {
380 genb_id := mrf.msg.initiatingMessage.value_.S1SetupRequest.protocolIEs[0].value_.Global_ENB_ID;
381 f_conn_set_genb_id(mrf.connId, genb_id);
382 }
383 var integer idx := f_ConnHdlr_find_by_conn_id(mrf.connId);
384 if (idx != -1) {
385 S1AP_CLIENT.send(mrf.msg) to g_conn_hdlr_list[idx].vc_conn;
386 } /* else: no ConnHdlr, drop PDU */
387 }
388 /* S1AP PDU from a ConnHdlr: pass on transparently */
389 [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
390 var integer idx := f_ConnHdlr_find_by_vc_conn(vc_conn);
391 if (idx == -1) {
392 setverdict(fail, "Component ", vc_conn, " is not registered");
393 mtc.stop;
394 }
395 if (g_conn_hdlr_list[idx].conn_id == -1) {
396 setverdict(fail, "S1AP connection is not up");
397 mtc.stop;
398 }
399 S1AP.send(t_S1AP_Send(g_conn_hdlr_list[idx].conn_id, msg));
400 }
401
402 /* connection opened/closed events */
403 [] S1AP.receive(PortEvent:{connOpened := ?}) -> value pev {
404 log("eNB connection (id=", pev.connOpened.connId, ", ",
405 pev.connOpened.remName, ":", pev.connOpened.remPort, ") ",
406 "established");
407 f_conn_add(pev.connOpened.connId);
408 }
409 [] S1AP.receive(PortEvent:{connClosed := ?}) -> value pev {
410 log("eNB connection (id=", pev.connClosed.connId, ", ",
411 pev.connClosed.remName, ":", pev.connClosed.remPort, ") ",
412 "closed");
413 f_conn_del(pev.connClosed.connId);
414 }
415
416 /* SCTP events we don't care about */
417 [] S1AP.receive(PortEvent:{sctpEvent := ?}) { }
418
419 /* ConnHdlr registration/unregistration */
420 [] S1AP_PROC.getcall(S1APSRV_register:{?, ?}) -> param(vc_conn, genb_id) {
421 f_ConnHdlr_add(vc_conn, genb_id);
422 S1AP_PROC.reply(S1APSRV_register:{vc_conn, genb_id}) to vc_conn;
423 }
424 [] S1AP_PROC.getcall(S1APSRV_unregister:{?, ?}) -> param(vc_conn, genb_id) {
425 f_ConnHdlr_del_by_genb_id(genb_id);
426 S1AP_PROC.reply(S1APSRV_unregister:{vc_conn, genb_id}) to vc_conn;
427 }
428 [] S1AP_PROC.getcall(S1APSRV_close_conn:{?, ?}) -> param(vc_conn, genb_id) {
429 f_conn_close_by_genb_id(genb_id);
430 S1AP_PROC.reply(S1APSRV_close_conn:{vc_conn, genb_id}) to vc_conn;
431 }
432 }
433 }
434}
435
436}