From 6e59534d6b17499356d60cf471cc20bccb4f0129 Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Thu, 16 Jun 2022 16:23:40 +0200 Subject: [PATCH] Add initial SBc-AP support to osmo-cbc This patch makes use of the newly introduced sbcap library, and introduces new code (and extends existing one) to handle all the aspects of MME peers talking SBc-AP and its underlaying SCTP connection. This commit doesn't aim to implement all the SBc-AP features, but to implement a minimal subset of features already available for CBSP in osmo-cbc, in order to have similar support level for both 2G and 4G networks. Related: OS#4945 Change-Id: Ib278bc1d1a74459814016fef7a8fe21cc29d46c9 --- configure.ac | 12 ++ contrib/osmo-cbc.spec.in | 1 + debian/control | 1 + doc/examples/osmo-cbc/osmo-cbc.cfg | 4 + include/osmocom/cbc/Makefile.am | 2 + include/osmocom/cbc/cbc_data.h | 13 +- include/osmocom/cbc/internal.h | 26 +++ include/osmocom/cbc/sbcap_msg.h | 11 + include/osmocom/cbc/sbcap_server.h | 47 ++++ src/Makefile.am | 7 +- src/cbc_data.c | 18 +- src/cbc_main.c | 41 +++- src/cbc_vty.c | 179 ++++++++++++++-- src/message_handling.c | 21 ++ src/sbcap_msg.c | 270 +++++++++++++++++++++++ src/sbcap_server.c | 250 ++++++++++++++++++++++ src/sbcap_server_fsm.c | 330 +++++++++++++++++++++++++++++ src/smscb_message_fsm.c | 12 +- src/smscb_peer_fsm.c | 125 ++++++++--- 19 files changed, 1317 insertions(+), 53 deletions(-) create mode 100644 include/osmocom/cbc/sbcap_msg.h create mode 100644 include/osmocom/cbc/sbcap_server.h create mode 100644 src/sbcap_msg.c create mode 100644 src/sbcap_server.c create mode 100644 src/sbcap_server_fsm.c diff --git a/configure.ac b/configure.ac index 2c910e7..4baced9 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,18 @@ AC_SUBST([ASN1C_SKELETON_PATH]) AC_SUBST([ASN1C_BIN_PATH]) AC_SUBST([ASN_MODULE_CFLAGS]) +AC_CHECK_HEADERS(netinet/sctp.h,,AC_MSG_ERROR(netinet/sctp.h not found)) +old_LIBS=$LIBS +AC_SEARCH_LIBS([sctp_recvmsg], [sctp], [ + AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support]) + AC_SUBST(HAVE_LIBSCTP, [1]) + if test -n "$ac_lib"; then + AC_SUBST(LIBSCTP_LIBS, [-l$ac_lib]) + fi + ], [ + AC_MSG_ERROR([sctp_recvmsg not found in searched libs])]) +LIBS=$old_LIBS + AC_ARG_ENABLE(sanitize, [AS_HELP_STRING( [--enable-sanitize], diff --git a/contrib/osmo-cbc.spec.in b/contrib/osmo-cbc.spec.in index 4feada7..0a4fecf 100644 --- a/contrib/osmo-cbc.spec.in +++ b/contrib/osmo-cbc.spec.in @@ -30,6 +30,7 @@ BuildRequires: pkgconfig >= 0.20 %if 0%{?suse_version} BuildRequires: systemd-rpm-macros %endif +BuildRequires: pkgconfig(libsctp) BuildRequires: pkgconfig(libosmocore) >= 1.7.0 BuildRequires: pkgconfig(libosmogsm) >= 1.7.0 BuildRequires: pkgconfig(libosmovty) >= 1.7.0 diff --git a/debian/control b/debian/control index 7dbaa10..aeb021f 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: debhelper (>=9), libosmo-netif-dev (>= 1.2.0), libulfius-dev, libjansson-dev, + libsctp-dev, osmo-gsm-manuals-dev Standards-Version: 3.9.8 Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-cbc diff --git a/doc/examples/osmo-cbc/osmo-cbc.cfg b/doc/examples/osmo-cbc/osmo-cbc.cfg index 0b66996..b2bce58 100644 --- a/doc/examples/osmo-cbc/osmo-cbc.cfg +++ b/doc/examples/osmo-cbc/osmo-cbc.cfg @@ -9,3 +9,7 @@ cbc ecbe local-ip 127.0.0.1 local-port 12345 + sbcap + local-ip 127.0.0.1 + local-ip ::1 + local-port 6789 diff --git a/include/osmocom/cbc/Makefile.am b/include/osmocom/cbc/Makefile.am index 1135800..d1cc46e 100644 --- a/include/osmocom/cbc/Makefile.am +++ b/include/osmocom/cbc/Makefile.am @@ -3,5 +3,7 @@ noinst_HEADERS = \ cbsp_server.h \ charset.h \ internal.h \ + sbcap_msg.h \ + sbcap_server.h \ rest_it_op.h \ $(NULL) diff --git a/include/osmocom/cbc/cbc_data.h b/include/osmocom/cbc/cbc_data.h index d6fc3d2..a90ff66 100644 --- a/include/osmocom/cbc/cbc_data.h +++ b/include/osmocom/cbc/cbc_data.h @@ -11,6 +11,8 @@ struct osmo_cbsp_cbc_client; struct osmo_sabp_cbc_client; struct rest_it_op; +#define CBC_MAX_ADDRS 8 + /********************************************************************************* * CBC Peer *********************************************************************************/ @@ -18,13 +20,15 @@ struct rest_it_op; enum cbc_peer_protocol { CBC_PEER_PROTO_CBSP, CBC_PEER_PROTO_SABP, + CBC_PEER_PROTO_SBcAP }; struct cbc_peer { struct llist_head list; /* linked to cbc.peers */ const char *name; - char *remote_host; /* remote IP address in string format */ + char *remote_host[CBC_MAX_ADDRS]; /* remote IP address in string format */ + unsigned int num_remote_host; /* number of addresses present in remote_host */ int remote_port; /* remote port number or -1 for random */ bool unknown_dynamic_peer; /* dynamic/unknown peer; not saved in VTY */ @@ -32,6 +36,7 @@ struct cbc_peer { union { struct osmo_cbsp_cbc_client *cbsp; struct osmo_sabp_cbc_client *sabp; + struct osmo_sbcap_cbc_client *sbcap; } client; }; @@ -170,6 +175,11 @@ struct cbc { char *local_host; int local_port; } cbsp; + struct { + char *local_host[CBC_MAX_ADDRS]; + unsigned int num_local_host; + int local_port; + } sbcap; struct { char *local_host; int local_port; @@ -197,4 +207,3 @@ struct cbc_peer *cbc_peer_by_addr_proto(const char *remote_host, uint16_t remote enum cbc_peer_protocol proto); struct cbc_peer *cbc_peer_create(const char *name, enum cbc_peer_protocol proto); void cbc_peer_remove(struct cbc_peer *peer); - diff --git a/include/osmocom/cbc/internal.h b/include/osmocom/cbc/internal.h index e3521bd..044387a 100644 --- a/include/osmocom/cbc/internal.h +++ b/include/osmocom/cbc/internal.h @@ -4,11 +4,13 @@ #include #include +#include #include enum { DCBSP, + DSBcAP, DREST, }; @@ -23,12 +25,30 @@ enum cbsp_server_event { CBSP_SRV_E_CMD_CLOSE, /* CLOSE command from CBC */ }; +extern struct osmo_fsm sbcap_server_fsm; + +enum sbcap_server_event { + SBcAP_SRV_E_RX_RST_COMPL, /* reset complete received */ + SBcAP_SRV_E_RX_RST_FAIL, /* reset failure received */ + SBcAP_SRV_E_RX_KA_COMPL, /* keep-alive complete received */ + SBcAP_SRV_E_RX_RESTART, /* restart received */ + SBcAP_SRV_E_CMD_RESET, /* RESET command from CBC */ + SBcAP_SRV_E_CMD_CLOSE, /* CLOSE command from CBC */ +}; + /* rest_api.c */ int rest_api_init(void *ctx, const char *bind_addr, uint16_t port); void rest_api_fin(void); /* cbc_vty.c */ +enum cbc_vty_node { + CBC_NODE = _LAST_OSMOVTY_NODE + 1, + PEER_NODE, + CBSP_NODE, + SBcAP_NODE, + ECBE_NODE, +}; void cbc_vty_init(void); /* message_handling.c */ @@ -65,6 +85,12 @@ enum smscb_fsm_event { /* CBSP peer confirms status query */ SMSCB_E_CBSP_STATUS_ACK, SMSCB_E_CBSP_STATUS_NACK, + /* SBc-AP peer confirms write */ + SMSCB_E_SBCAP_WRITE_ACK, + SMSCB_E_SBCAP_WRITE_NACK, + /* SBc-AP peer confirms delete */ + SMSCB_E_SBCAP_DELETE_ACK, + SMSCB_E_SBCAP_DELETE_NACK, }; enum smscb_fsm_state { diff --git a/include/osmocom/cbc/sbcap_msg.h b/include/osmocom/cbc/sbcap_msg.h new file mode 100644 index 0000000..784938d --- /dev/null +++ b/include/osmocom/cbc/sbcap_msg.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#include "cbc_data.h" + +struct cbc_message; +typedef struct SBcAP_SBC_AP_PDU SBcAP_SBC_AP_PDU_t; + +SBcAP_SBC_AP_PDU_t *cbcmsg_to_sbcap(void *ctx, const struct cbc_message *cbcmsg); +SBcAP_SBC_AP_PDU_t *sbcap_gen_stop_warning_req(void *ctx, const struct cbc_message *cbcmsg); diff --git a/include/osmocom/cbc/sbcap_server.h b/include/osmocom/cbc/sbcap_server.h new file mode 100644 index 0000000..7b4b7e1 --- /dev/null +++ b/include/osmocom/cbc/sbcap_server.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include + +#include + +#include "cbc_data.h" + +#define SBcAP_SCTP_PORT 29168 +typedef struct SBcAP_SBC_AP_PDU SBcAP_SBC_AP_PDU_t; +#define LOGPSBCAPC(client, level, fmt, args...) \ + LOGP(DSBcAP, level, "%s: " fmt, sbcap_cbc_client_name(client), ## args) + +struct osmo_sbcap_cbc_client; +struct osmo_fsm_inst; + +/* a CBC server */ +struct osmo_sbcap_cbc { + /* libosmo-netif stream server */ + struct osmo_stream_srv_link *link; + + /* MMEs / clients connected to this CBC */ + struct llist_head clients; + + /* receive call-back; called for every received message */ + int (*rx_cb)(struct osmo_sbcap_cbc_client *client, SBcAP_SBC_AP_PDU_t *pdu); +}; +struct osmo_sbcap_cbc *sbcap_cbc_create(void *ctx); + +/* a single (remote) client connected to the (local) CBC server */ +struct osmo_sbcap_cbc_client { + /* entry in osmo_sbcap_cbc.clients */ + struct llist_head list; + /* stream server connection for this client */ + struct osmo_stream_srv *conn; + /* partially received sbcap message (rx completion pending) */ + struct msgb *rx_msg; + + struct osmo_fsm_inst *fi; + + struct cbc_peer *peer; +}; + +const char *sbcap_cbc_client_name(const struct osmo_sbcap_cbc_client *client); +void sbcap_cbc_client_tx(struct osmo_sbcap_cbc_client *client, SBcAP_SBC_AP_PDU_t *pdu); +void sbcap_cbc_client_close(struct osmo_sbcap_cbc_client *client); +int sbcap_cbc_client_rx_cb(struct osmo_sbcap_cbc_client *client, SBcAP_SBC_AP_PDU_t *pdu); diff --git a/src/Makefile.am b/src/Makefile.am index cc88962..5805fd7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,7 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_srcdir)/src/sbcap/ AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) \ $(LIBOSMONETIF_CFLAGS) \ $(ULFIUS_CFLAGS) $(JANSSON_CFLAGS) $(ORCANIA_CFLAGS) \ - $(COVERAGE_CFLAGS) + $(COVERAGE_CFLAGS) $(ASN_MODULE_CFLAGS) AM_LDFLAGS=$(COVERAGE_LDFLAGS) bin_PROGRAMS = osmo-cbc @@ -19,13 +19,16 @@ osmo_cbc_SOURCES = \ charset.c \ message_handling.c \ rest_it_op.c \ + sbcap_msg.c \ + sbcap_server.c \ + sbcap_server_fsm.c \ smscb_peer_fsm.c \ smscb_message_fsm.c \ $(NULL) osmo_cbc_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) \ $(LIBOSMONETIF_LIBS) \ - $(ULFIUS_LIBS) $(JANSSON_LIBS) $(ORCANIA_LIBS) \ + $(ULFIUS_LIBS) $(JANSSON_LIBS) $(ORCANIA_LIBS) $(LIBSCTP_LIBS) \ sbcap/libosmo-sbcap.la regen: diff --git a/src/cbc_data.c b/src/cbc_data.c index 4b2c831..92da369 100644 --- a/src/cbc_data.c +++ b/src/cbc_data.c @@ -34,6 +34,7 @@ const struct value_string cbc_peer_proto_name[] = { { CBC_PEER_PROTO_CBSP, "CBSP" }, { CBC_PEER_PROTO_SABP, "SABP" }, + { CBC_PEER_PROTO_SBcAP, "SBc-AP" }, { 0, NULL } }; @@ -99,13 +100,16 @@ struct cbc_peer *cbc_peer_by_addr_proto(const char *remote_host, uint16_t remote struct cbc_peer *peer; llist_for_each_entry(peer, &g_cbc->peers, list) { - if (!peer->remote_host) - continue; - if (!strcasecmp(remote_host, peer->remote_host)) { - if (peer->remote_port == -1) - return peer; - else if (remote_port == peer->remote_port) - return peer; + unsigned int i; + for (i = 0; i < peer->num_remote_host; i++) { + if (peer->proto != proto) + continue; + if (!strcasecmp(remote_host, peer->remote_host[i])) { + if (peer->remote_port == -1) + return peer; + else if (remote_port == peer->remote_port) + return peer; + } } } return NULL; diff --git a/src/cbc_main.c b/src/cbc_main.c index c49514b..dd157ca 100644 --- a/src/cbc_main.c +++ b/src/cbc_main.c @@ -46,6 +46,7 @@ #include #include +#include #include static void *tall_cbc_ctx; @@ -59,10 +60,17 @@ static const struct log_info_cat log_info_cat[] = { .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DSBcAP] = { + .name = "DSBcAP", + .description = "SBc Application Part (CBC-MME)", + .color = "\033[1;32m", + .enabled = 1, + .loglevel = LOGL_NOTICE, + }, [DREST] = { .name = "DREST", .description = "REST interface", - .color = "\033[1;32m", + .color = "\033[1;33m", .enabled = 1, .loglevel = LOGL_NOTICE, }, @@ -73,6 +81,27 @@ static const struct log_info log_info = { .num_cat = ARRAY_SIZE(log_info_cat), }; +static int cbc_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case SBcAP_NODE: + /* If no local addr set, add a default one: */ + if (g_cbc->config.sbcap.num_local_host) { + g_cbc->config.sbcap.local_host[0] = talloc_strdup(g_cbc, "127.0.0.1"); + g_cbc->config.sbcap.num_local_host = 1; + } + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + default: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + } + + return vty->node; +} + static const char cbc_copyright[] = "Copyright (C) 2019-2021 by Harald Welte \r\n" "License AGPLv3+: GNU Affero GPL Version 3 or later \r\n" @@ -83,6 +112,7 @@ static const char cbc_copyright[] = static struct vty_app_info vty_info = { .name = "OsmoCBC", .copyright = cbc_copyright, + .go_parent_cb = cbc_vty_go_parent, .version = PACKAGE_VERSION, .go_parent_cb = NULL, .is_config_node = NULL, @@ -201,8 +231,6 @@ static void signal_handler(int signal) } } -extern int cbc_client_rx_cb(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec); - int main(int argc, char **argv) { void *tall_rest_ctx; @@ -222,6 +250,8 @@ int main(int argc, char **argv) INIT_LLIST_HEAD(&g_cbc->expired_messages); g_cbc->config.cbsp.local_host = talloc_strdup(g_cbc, "127.0.0.1"); g_cbc->config.cbsp.local_port = CBSP_TCP_PORT; + /* g_cbc->config.sbcap local_host set up during VTY (and vty_go_parent) */ + g_cbc->config.sbcap.local_port = SBcAP_SCTP_PORT; g_cbc->config.ecbe.local_host = talloc_strdup(g_cbc, "127.0.0.1"); g_cbc->config.ecbe.local_port = 12345; @@ -250,6 +280,11 @@ int main(int argc, char **argv) exit(1); } + if (sbcap_cbc_create(tall_cbc_ctx) == NULL) { + perror("Error binding SBc-AP port\n"); + exit(1); + } + rc = rest_api_init(tall_rest_ctx, g_cbc->config.ecbe.local_host, g_cbc->config.ecbe.local_port); if (rc < 0) { perror("Error binding ECBE port"); diff --git a/src/cbc_vty.c b/src/cbc_vty.c index b07bf70..5244c3e 100644 --- a/src/cbc_vty.c +++ b/src/cbc_vty.c @@ -33,10 +33,14 @@ #include #include #include +#include static void dump_one_cbc_peer(struct vty *vty, const struct cbc_peer *peer) { const char *state = ""; + char rem_addrs[1024] = ""; + struct osmo_strbuf sb = { .buf = rem_addrs, .len = sizeof(rem_addrs) }; + unsigned int i; switch (peer->proto) { case CBC_PEER_PROTO_CBSP: @@ -45,11 +49,21 @@ static void dump_one_cbc_peer(struct vty *vty, const struct cbc_peer *peer) break; case CBC_PEER_PROTO_SABP: break; + case CBC_PEER_PROTO_SBcAP: + if (peer->client.sbcap) + state = osmo_fsm_inst_state_name(peer->client.sbcap->fi); + break; + } + + for (i = 0; i < peer->num_remote_host; i++) { + if (i > 0) + OSMO_STRBUF_PRINTF(sb, ","); + OSMO_STRBUF_PRINTF(sb, peer->remote_host[i]); } vty_out(vty, "|%-20s| %-15s| %-5d| %-6s| %-20s|%s", peer->name ? peer->name : "", - peer->remote_host ? peer->remote_host : "", peer->remote_port, + rem_addrs, peer->remote_port, get_value_string(cbc_peer_proto_name, peer->proto), state, VTY_NEWLINE); } @@ -265,13 +279,6 @@ DEFUN(show_messages_etws, show_messages_etws_cmd, /* TODO: Re-send all messages to one peer / all peers? */ /* TODO: Completed / Load status */ -enum cbc_vty_node { - CBC_NODE = _LAST_OSMOVTY_NODE + 1, - PEER_NODE, - CBSP_NODE, - ECBE_NODE, -}; - static struct cmd_node cbc_node = { CBC_NODE, "%s(config-cbc)# ", @@ -321,6 +328,14 @@ DEFUN(cfg_cbsp, cfg_cbsp_cmd, return CMD_SUCCESS; } +DEFUN(cfg_sbcap, cfg_sbcap_cmd, + "sbcap", + "SBc Application Part\n") +{ + vty->node = SBcAP_NODE; + return CMD_SUCCESS; +} + DEFUN(cfg_ecbe, cfg_ecbe_cmd, "ecbe", "External CBS Entity (REST Interface)\n") @@ -402,6 +417,87 @@ DEFUN(cfg_ecbe_local_port, cfg_ecbe_local_port_cmd, return CMD_SUCCESS; } +/* SBc-AP */ + +static struct cmd_node sbcap_node = { + SBcAP_NODE, + "%s(config-sbcap)# ", + 1, +}; + +static int config_write_sbcap(struct vty *vty) +{ + unsigned int i; + + vty_out(vty, " sbcap%s", VTY_NEWLINE); + for (i = 0; i < g_cbc->config.sbcap.num_local_host; i++) + vty_out(vty, " local-ip %s%s", g_cbc->config.sbcap.local_host[i], VTY_NEWLINE); + vty_out(vty, " local-port %u%s", g_cbc->config.sbcap.local_port, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_sbcap_local_ip, cfg_sbcap_local_ip_cmd, + "local-ip " VTY_IPV46_CMD, + "Local IP address for SBc-AP\n" + "Local IPv4 address for SBc-AP Interface\n" + "Local IPv6 address for SBc-AP Interface\n") +{ + unsigned int i; + const char *newaddr = argv[0]; + + if (g_cbc->config.sbcap.num_local_host >= ARRAY_SIZE(g_cbc->config.sbcap.local_host)) { + vty_out(vty, "%% Only up to %zu addresses allowed%s", + ARRAY_SIZE(g_cbc->config.sbcap.local_host), VTY_NEWLINE); + return CMD_WARNING; + } + + /* Check for repeated entries: */ + for (i = 0; i < g_cbc->config.sbcap.num_local_host; i++) { + if (strcmp(g_cbc->config.sbcap.local_host[i], newaddr) == 0) { + vty_out(vty, "%% IP address %s already in list%s", newaddr, VTY_NEWLINE); + return CMD_WARNING; + } + } + osmo_talloc_replace_string(g_cbc, + &g_cbc->config.sbcap.local_host[g_cbc->config.sbcap.num_local_host], newaddr); + g_cbc->config.sbcap.num_local_host++; + return CMD_SUCCESS; +} + +DEFUN(cfg_sbcap_no_local_ip, cfg_sbcap_no_local_ip_cmd, + "no local-ip " VTY_IPV46_CMD, + NO_STR "Local IP address for SBc-AP\n" + "Local IPv4 address for SBc-AP Interface\n" + "Local IPv6 address for SBc-AP Interface\n") +{ + unsigned int i, j; + const char *rmaddr = argv[0]; + + for (i = 0; i < g_cbc->config.sbcap.num_local_host; i++) { + if (strcmp(g_cbc->config.sbcap.local_host[i], rmaddr) == 0) { + talloc_free(g_cbc->config.sbcap.local_host[i]); + g_cbc->config.sbcap.num_local_host--; + for (j = i; j < g_cbc->config.sbcap.num_local_host; j++) { + g_cbc->config.sbcap.local_host[j] = g_cbc->config.sbcap.local_host[j + 1]; + } + g_cbc->config.sbcap.local_host[j] = NULL; + return CMD_SUCCESS; + } + } + vty_out(vty, "%% IP address %s not in list%s", rmaddr, VTY_NEWLINE); + return CMD_WARNING; +} + +DEFUN(cfg_sbcap_local_port, cfg_sbcap_local_port_cmd, + "local-port <0-65535>", + "Local TCP port for SBc-AP Interface\n" + "Local TCP port for SBc-AP Interface\n") +{ + g_cbc->config.sbcap.local_port = atoi(argv[0]); + return CMD_SUCCESS; +} + /* PEER */ @@ -441,12 +537,15 @@ DEFUN(cfg_cbc_no_peer, cfg_cbc_no_peer_cmd, DEFUN(cfg_peer_proto, cfg_peer_proto_cmd, - "protocol (cbsp)", + "protocol (cbsp|sbcap)", "Configure Protocol of Peer\n" "Cell Broadcast Service Protocol (GSM)\n") { struct cbc_peer *peer = (struct cbc_peer *) vty->index; - peer->proto = CBC_PEER_PROTO_CBSP; + if (strcmp(argv[0], "cbsp") == 0) + peer->proto = CBC_PEER_PROTO_CBSP; + else + peer->proto = CBC_PEER_PROTO_SBcAP; return CMD_SUCCESS; } @@ -477,22 +576,69 @@ DEFUN(cfg_peer_remote_ip, cfg_peer_remote_ip_cmd, "IPv4 address of peer\n" "IPv6 address of peer\n") { struct cbc_peer *peer = (struct cbc_peer *) vty->index; - osmo_talloc_replace_string(peer, &peer->remote_host, argv[0]); + unsigned int allowed_address; + unsigned int i; + const char *newaddr = argv[0]; + + if (peer->proto == CBC_PEER_PROTO_SBcAP) + allowed_address = ARRAY_SIZE(peer->remote_host); + else + allowed_address = 1; + + if (peer->num_remote_host >= allowed_address) { + vty_out(vty, "%% Only up to %u addresses allowed%s", + allowed_address, VTY_NEWLINE); + return CMD_WARNING; + } + + /* Check for repeated entries: */ + for (i = 0; i < peer->num_remote_host; i++) { + if (strcmp(peer->remote_host[i], newaddr) == 0) { + vty_out(vty, "%% IP address %s already in list%s", newaddr, VTY_NEWLINE); + return CMD_WARNING; + } + } + osmo_talloc_replace_string(peer, &peer->remote_host[peer->num_remote_host], newaddr); + peer->num_remote_host++; + return CMD_SUCCESS; } +DEFUN(cfg_peer_no_remote_ip, cfg_peer_no_remote_ip_cmd, + "no remote-ip " VTY_IPV46_CMD, + NO_STR "Keep remote IP of peer\n" + "IPv4 address of peer\n" "IPv6 address of peer\n") +{ + struct cbc_peer *peer = (struct cbc_peer *) vty->index; + unsigned int i, j; + const char *rmaddr = argv[0]; + for (i = 0; i < peer->num_remote_host; i++) { + if (strcmp(peer->remote_host[i], rmaddr) == 0) { + talloc_free(peer->remote_host[i]); + peer->num_remote_host--; + for (j = i; j < peer->num_remote_host; j++) { + peer->remote_host[j] = peer->remote_host[j + 1]; + } + peer->remote_host[j] = NULL; + return CMD_SUCCESS; + } + } + vty_out(vty, "%% IP address %s not in list%s", rmaddr, VTY_NEWLINE); + return CMD_WARNING; +} static void write_one_peer(struct vty *vty, struct cbc_peer *peer) { + unsigned int i; vty_out(vty, " peer %s%s", peer->name, VTY_NEWLINE); vty_out(vty, " protocol cbsp%s", VTY_NEWLINE); if (peer->remote_port == -1) vty_out(vty, " no remote-port%s", VTY_NEWLINE); else vty_out(vty, " remote-port %d%s", peer->remote_port, VTY_NEWLINE); - if (peer->remote_host) - vty_out(vty, " remote-ip %s%s", peer->remote_host, VTY_NEWLINE); + for (i = 0; i < peer->num_remote_host; i++) + vty_out(vty, " remote-ip %s%s", peer->remote_host[i], VTY_NEWLINE); } static int config_write_peer(struct vty *vty) @@ -528,6 +674,12 @@ void cbc_vty_init(void) install_lib_element(ECBE_NODE, &cfg_ecbe_local_ip_cmd); install_lib_element(ECBE_NODE, &cfg_ecbe_local_port_cmd); + install_lib_element(CBC_NODE, &cfg_sbcap_cmd); + install_node(&sbcap_node, config_write_sbcap); + install_lib_element(SBcAP_NODE, &cfg_sbcap_local_ip_cmd); + install_lib_element(SBcAP_NODE, &cfg_sbcap_no_local_ip_cmd); + install_lib_element(SBcAP_NODE, &cfg_sbcap_local_port_cmd); + install_lib_element(CBC_NODE, &cfg_cbc_peer_cmd); install_lib_element(CBC_NODE, &cfg_cbc_no_peer_cmd); install_node(&peer_node, config_write_peer); @@ -535,5 +687,6 @@ void cbc_vty_init(void) install_lib_element(PEER_NODE, &cfg_peer_remote_port_cmd); install_lib_element(PEER_NODE, &cfg_peer_no_remote_port_cmd); install_lib_element(PEER_NODE, &cfg_peer_remote_ip_cmd); + install_lib_element(PEER_NODE, &cfg_peer_no_remote_ip_cmd); } diff --git a/src/message_handling.c b/src/message_handling.c index 41b854a..3344a75 100644 --- a/src/message_handling.c +++ b/src/message_handling.c @@ -29,6 +29,8 @@ #include #include +#include +#include #include #include @@ -128,6 +130,7 @@ static bool is_peer_in_scope(const struct cbc_peer *peer, const struct cbc_messa int peer_new_cbc_message(struct cbc_peer *peer, struct cbc_message *cbcmsg) { struct osmo_cbsp_decoded *cbsp; + SBcAP_SBC_AP_PDU_t *sbcap; switch (peer->proto) { case CBC_PEER_PROTO_CBSP: @@ -144,6 +147,24 @@ int peer_new_cbc_message(struct cbc_peer *peer, struct cbc_message *cbcmsg) } cbsp_cbc_client_tx(peer->client.cbsp, cbsp); break; + case CBC_PEER_PROTO_SBcAP: + /* skip peers without any current SBc-AP connection */ + if (!peer->client.sbcap) { + LOGP(DSBcAP, LOGL_NOTICE, "[%s] Tx SBc-AP: not connected\n", + peer->name); + return -ENOTCONN; + } + if (!(sbcap = cbcmsg_to_sbcap(peer, cbcmsg))) { + LOGP(DSBcAP, LOGL_ERROR, "[%s] Tx SBc-AP: msg gen failed\n", + peer->name); + return -EINVAL; + } + sbcap_cbc_client_tx(peer->client.sbcap, sbcap); + break; + case CBC_PEER_PROTO_SABP: + LOGP(DLGLOBAL, LOGL_ERROR, "Sending message to peer proto %s not implemented!\n", + get_value_string(cbc_peer_proto_name, peer->proto)); + return -1; default: OSMO_ASSERT(0); } diff --git a/src/sbcap_msg.c b/src/sbcap_msg.c new file mode 100644 index 0000000..6cb3a35 --- /dev/null +++ b/src/sbcap_msg.c @@ -0,0 +1,270 @@ +/* Osmocom CBC (Cell Broacast Centre) */ + +/* (C) 2022 by sysmocom - s.f.m.c. GmbH + * All Rights Reserved + * Author: Pau Espin Pedrol + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +/* 3GPP TS 36.413 9.2.1.53 */ +#define SBCAP_WARN_MSG_CONTENTS_IE_MAX_LEN 9600 + +#if 0 +static void msgb_put_sbcap_cell_list(const struct cbc_message *cbcmsg, void *void_li) +{ + static uint8_t ie_plm_id0[] = {0x05, 0xf5, 0x32}; + static uint8_t ie_cell_id0[] = {0xa0, 0x00, 0x00, 0x10}; + static uint8_t ie_cell_id1[] = {0xa0, 0x00, 0x00, 0x20}; + SBcAP_EUTRAN_CGI_t *ecgi; + A_SEQUENCE_OF(void) *li = void_li; + + ecgi = CALLOC(1, sizeof(*ecgi)); + *ecgi = (SBcAP_EUTRAN_CGI_t) { + .pLMNidentity = { + .buf = ie_plm_id0, + .size = sizeof(ie_plm_id0), + }, + .cell_ID = { /* SBcAP_CellIdentity_t*/ + .buf = ie_cell_id0, + .size = sizeof(ie_cell_id0), + .bits_unused = 4, + } + }; + ASN_SEQUENCE_ADD(li, ecgi); + + ecgi = CALLOC(1, sizeof(*ecgi)); + *ecgi = (SBcAP_EUTRAN_CGI_t) { + .pLMNidentity = { + .buf = ie_plm_id0, + .size = sizeof(ie_plm_id0), + }, + .cell_ID = { /* SBcAP_CellIdentity_t*/ + .buf = ie_cell_id1, + .size = sizeof(ie_cell_id1), + .bits_unused = 4, + } + }; + ASN_SEQUENCE_ADD(li, ecgi); + +} +#endif + +/* generate a SBc-AP WRITE-REPLACE WARNING REQUEST from our internal representation */ +SBcAP_SBC_AP_PDU_t *cbcmsg_to_sbcap(void *ctx, const struct cbc_message *cbcmsg) +{ + const struct smscb_message *smscb = &cbcmsg->msg; + SBcAP_SBC_AP_PDU_t *pdu; + SBcAP_Write_Replace_Warning_Request_IEs_t *ie; + uint16_t ie_warning_type; + unsigned int i; + uint8_t *ptr; +#if 0 + A_SEQUENCE_OF(void) *as_warn_area_ecgi = NULL; +#endif + + pdu = sbcap_pdu_alloc(); + if (!pdu) + return NULL; + pdu->present = SBcAP_SBC_AP_PDU_PR_initiatingMessage; + pdu->choice.initiatingMessage.procedureCode = SBcAP_ProcedureId_Write_Replace_Warning; + pdu->choice.initiatingMessage.criticality = SBcAP_Criticality_reject; + pdu->choice.initiatingMessage.value.present = SBcAP_InitiatingMessage__value_PR_Write_Replace_Warning_Request; + + A_SEQUENCE_OF(void) *as_pdu = (void *)&pdu->choice.initiatingMessage.value.choice.Write_Replace_Warning_Request.protocolIEs.list; + + /* static const long asn_VAL_1_SBcAP_id_Message_Identifier = 5; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(5, SBcAP_Criticality_reject, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Message_Identifier); + ie->value.choice.Message_Identifier.buf = MALLOC(sizeof(uint16_t)); + ie->value.choice.Message_Identifier.size = sizeof(uint16_t); + ie->value.choice.Message_Identifier.bits_unused = 0; + osmo_store16be(smscb->message_id, ie->value.choice.Message_Identifier.buf); + ASN_SEQUENCE_ADD(as_pdu, ie); + + /* static const long asn_VAL_2_SBcAP_id_Serial_Number = 11; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(11, SBcAP_Criticality_reject, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Serial_Number); + ie->value.choice.Serial_Number.buf = MALLOC(sizeof(uint16_t)); + ie->value.choice.Serial_Number.size = sizeof(uint16_t); + ie->value.choice.Serial_Number.bits_unused = 0; + osmo_store16be(smscb->serial_nr, ie->value.choice.Serial_Number.buf); + ASN_SEQUENCE_ADD(as_pdu, ie); + + switch (cbcmsg->scope) { + case CBC_MSG_SCOPE_PLMN: + break; /* Nothing to be done :*/ +#if 0 + case CBC_MSG_SCOPE_EUTRAN_CGI: + /* static const long asn_VAL_25_SBcAP_id_Warning_Area_List = 15; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(15, SBcAP_Criticality_ignore, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Warning_Area_List); + ASN_SEQUENCE_ADD(as_pdu, ie); + as_warn_area_ecgi = (void *)ie->value.choice.Warning_Area_List.choice.cell_ID_List.list; + msgb_put_sbcap_cell_list(cbcmsg, as_warn_area_ecgi); + break; +#endif + default: + OSMO_ASSERT(0); + } + + /* static const long asn_VAL_5_SBcAP_id_Repetition_Period = 10; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(10, SBcAP_Criticality_reject, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Repetition_Period); + ie->value.choice.Repetition_Period = cbcmsg->rep_period; /*seconds */ + ASN_SEQUENCE_ADD(as_pdu, ie); + + /* static const long asn_VAL_7_SBcAP_id_Number_of_Broadcasts_Requested = 7; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(7, SBcAP_Criticality_reject, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Number_of_Broadcasts_Requested); + ie->value.choice.Number_of_Broadcasts_Requested = cbcmsg->num_bcast; + ASN_SEQUENCE_ADD(as_pdu, ie); + + if (smscb->is_etws) { + /* Warning Type, 3GPP TS 36.413 sec 9.2.1.50: */ + ie_warning_type = smscb->etws.warning_type; + if (smscb->etws.user_alert) + ie_warning_type |= 0x0100; + if (smscb->etws.popup_on_display) + ie_warning_type |= 0x0080; + /* static const long asn_VAL_8_SBcAP_id_Warning_Type = 18; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(18, SBcAP_Criticality_ignore, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Warning_Type); + ie->value.choice.Warning_Type.buf = MALLOC(sizeof(ie_warning_type)); + ie->value.choice.Warning_Type.size = sizeof(ie_warning_type); + memcpy(ie->value.choice.Warning_Type.buf, &ie_warning_type, sizeof(ie_warning_type)); + ASN_SEQUENCE_ADD(as_pdu, ie); + + /* Warning Security Information, 3GPP TS 36.413 sec 9.2.1.51: */ + /*static const long asn_VAL_9_SBcAP_id_Warning_Security_Information = 17 */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(17, SBcAP_Criticality_ignore, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Warning_Security_Information); + ie->value.choice.Warning_Security_Information.buf = MALLOC(sizeof(smscb->etws.warning_sec_info)); + ie->value.choice.Warning_Security_Information.size = sizeof(smscb->etws.warning_sec_info); + memcpy(ie->value.choice.Warning_Security_Information.buf, + smscb->etws.warning_sec_info, sizeof(smscb->etws.warning_sec_info)); + ASN_SEQUENCE_ADD(as_pdu, ie); + + } else { + /* static const long asn_VAL_10_SBcAP_id_Data_Coding_Scheme = 3; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(3, SBcAP_Criticality_ignore, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Data_Coding_Scheme); + ie->value.choice.Data_Coding_Scheme.buf = MALLOC(1); + ie->value.choice.Data_Coding_Scheme.size = 1; + ie->value.choice.Data_Coding_Scheme.bits_unused = 0; + *ie->value.choice.Data_Coding_Scheme.buf = smscb->cbs.dcs; + ASN_SEQUENCE_ADD(as_pdu, ie); + + /* 3GPP TS 36.413 9.2.1.53 Warning Message Contents. Encoded as in S1AP. */ + /* static const long asn_VAL_11_SBcAP_id_Warning_Message_Content = 16; */ + ie = sbcap_alloc_Write_Replace_Warning_Request_IE(16, SBcAP_Criticality_ignore, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Warning_Message_Content); + ie->value.choice.Warning_Message_Content.buf = MALLOC(1 + smscb->cbs.num_pages * (SMSCB_RAW_PAGE_LEN+1)); + ie->value.choice.Warning_Message_Content.size = 1 + smscb->cbs.num_pages * (SMSCB_RAW_PAGE_LEN+1); + ptr = &ie->value.choice.Warning_Message_Content.buf[0]; + *ptr = (uint8_t)smscb->cbs.num_pages; + ptr++; + for (i = 0; i < smscb->cbs.num_pages; i++) { + unsigned len = 0; + if (i == smscb->cbs.num_pages - 1) + len = smscb->cbs.data_user_len - (i * SMSCB_RAW_PAGE_LEN); + else + len = SMSCB_RAW_PAGE_LEN; + if (len > 0) { + memcpy(ptr, smscb->cbs.data[i], SMSCB_RAW_PAGE_LEN); + ptr += SMSCB_RAW_PAGE_LEN; + } + *ptr = (uint8_t)len; + ptr++; + } + ASN_SEQUENCE_ADD(as_pdu, ie); + } + + return pdu; +} + +/* generate a SBc-AP WRITE-REPLACE WARNING REQUEST from our internal representation */ +SBcAP_SBC_AP_PDU_t *sbcap_gen_stop_warning_req(void *ctx, const struct cbc_message *cbcmsg) +{ + const struct smscb_message *smscb = &cbcmsg->msg; + SBcAP_SBC_AP_PDU_t *pdu; + SBcAP_Stop_Warning_Request_IEs_t *ie; +#if 0 + A_SEQUENCE_OF(void) *as_warn_area_ecgi = NULL; +#endif + + pdu = sbcap_pdu_alloc(); + if (!pdu) + return NULL; + pdu->present = SBcAP_SBC_AP_PDU_PR_initiatingMessage; + pdu->choice.initiatingMessage.procedureCode = SBcAP_ProcedureId_Stop_Warning; + pdu->choice.initiatingMessage.criticality = SBcAP_Criticality_reject; + pdu->choice.initiatingMessage.value.present = SBcAP_InitiatingMessage__value_PR_Stop_Warning_Request; + + A_SEQUENCE_OF(void) *as_pdu = (void *)&pdu->choice.initiatingMessage.value.choice.Stop_Warning_Request.protocolIEs.list; + + /* static const long asn_VAL_1_SBcAP_id_Message_Identifier = 5; */ + ie = sbcap_alloc_Stop_Warning_Request_IE(5, SBcAP_Criticality_reject, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Message_Identifier); + ie->value.choice.Message_Identifier.buf = MALLOC(sizeof(uint16_t)); + ie->value.choice.Message_Identifier.size = sizeof(uint16_t); + ie->value.choice.Message_Identifier.bits_unused = 0; + osmo_store16be(smscb->message_id, ie->value.choice.Message_Identifier.buf); + ASN_SEQUENCE_ADD(as_pdu, ie); + + /* static const long asn_VAL_2_SBcAP_id_Serial_Number = 11; */ + ie = sbcap_alloc_Stop_Warning_Request_IE(11, SBcAP_Criticality_reject, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Serial_Number); + ie->value.choice.Serial_Number.buf = MALLOC(sizeof(uint16_t)); + ie->value.choice.Serial_Number.size = sizeof(uint16_t); + ie->value.choice.Serial_Number.bits_unused = 0; + osmo_store16be(smscb->serial_nr, ie->value.choice.Serial_Number.buf); + ASN_SEQUENCE_ADD(as_pdu, ie); + + switch (cbcmsg->scope) { + case CBC_MSG_SCOPE_PLMN: + break; /* Nothing to be done :*/ +#if 0 + case CBC_MSG_SCOPE_EUTRAN_CGI: + /* static const long asn_VAL_25_SBcAP_id_Warning_Area_List = 15; */ + ie = sbcap_alloc_Stop_Warning_Request_IE(15, SBcAP_Criticality_ignore, + SBcAP_Write_Replace_Warning_Request_IEs__value_PR_Warning_Area_List); + ASN_SEQUENCE_ADD(as_pdu, ie); + as_warn_area_ecgi = (void *)ie->value.choice.Warning_Area_List.choice.cell_ID_List.list; + msgb_put_sbcap_cell_list(cbcmsg, as_warn_area_ecgi); + break; +#endif + default: + OSMO_ASSERT(0); + } + + return pdu; +} diff --git a/src/sbcap_server.c b/src/sbcap_server.c new file mode 100644 index 0000000..e111675 --- /dev/null +++ b/src/sbcap_server.c @@ -0,0 +1,250 @@ +/* (C) 2019 by Harald Welte + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +const char *sbcap_cbc_client_name(const struct osmo_sbcap_cbc_client *client) +{ + struct osmo_fd *ofd; + OSMO_ASSERT(client); + + if (client->peer && client->peer->name) { + return client->peer->name; + } + + ofd = osmo_stream_srv_get_ofd(client->conn); + return osmo_sock_get_name2(ofd->fd); +} + +/* data from MME has arrived at CBC */ +static int sbcap_cbc_read_cb(struct osmo_stream_srv *conn) +{ + struct osmo_stream_srv_link *link = osmo_stream_srv_get_master(conn); + struct osmo_sbcap_cbc_client *client = osmo_stream_srv_get_data(conn); + struct osmo_sbcap_cbc *cbc = osmo_stream_srv_link_get_data(link); + struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); + SBcAP_SBC_AP_PDU_t *pdu; + struct msgb *msg = msgb_alloc_c(client, 1500, "SBcAP-rx"); + struct sctp_sndrcvinfo sinfo; + int flags = 0; + int rc; + + /* read SBc-AP message from socket and process it */ + rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg), + NULL, NULL, &sinfo, &flags); + LOGPSBCAPC(client, LOGL_DEBUG, "%s(): sctp_recvmsg() returned %d (flags=0x%x)\n", + __func__, rc, flags); + if (rc < 0) { + osmo_stream_srv_destroy(conn); + goto out; + } else if (rc == 0) { + osmo_stream_srv_destroy(conn); + } else { + msgb_put(msg, rc); + } + + if (flags & MSG_NOTIFICATION) { + union sctp_notification *notif = (union sctp_notification *) msgb_data(msg); + LOGPSBCAPC(client, LOGL_DEBUG, "Rx sctp notif %s\n", + osmo_sctp_sn_type_str(notif->sn_header.sn_type)); + switch (notif->sn_header.sn_type) { + case SCTP_SHUTDOWN_EVENT: + osmo_stream_srv_destroy(conn); + break; + case SCTP_ASSOC_CHANGE: + LOGPSBCAPC(client, LOGL_DEBUG, "Rx sctp notif SCTP_ASSOC_CHANGE: %s\n", + osmo_sctp_assoc_chg_str(notif->sn_assoc_change.sac_state)); + default: + break; + } + rc = 0; + goto out; + } + + if (rc == 0) + goto out; + + LOGPSBCAPC(client, LOGL_DEBUG, "Received SBc-AP %s\n", msgb_hexdump(msg)); + + /* decode + dispatch message */ + pdu = sbcap_decode(msg); + if (pdu) { + LOGPSBCAPC(client, LOGL_INFO, "Received SBc-AP %d\n", + pdu->present); + cbc->rx_cb(client, pdu); + } else { + LOGPSBCAPC(client, LOGL_ERROR, "Unable to decode %s\n", msgb_hexdump(msg)); + } +out: + msgb_free(msg); + return rc; +} + +/* connection from MME to CBC has been closed */ +static int sbcap_cbc_closed_cb(struct osmo_stream_srv *conn) +{ + struct osmo_sbcap_cbc_client *client = osmo_stream_srv_get_data(conn); + LOGPSBCAPC(client, LOGL_NOTICE, "connection closed\n"); + + if (client->peer) + client->peer->client.sbcap = NULL; + client->conn = NULL; + if (client->fi) + osmo_fsm_inst_dispatch(client->fi, SBcAP_SRV_E_CMD_CLOSE, NULL); + + return 0; +} + +/* new connection from MME has arrived at CBC */ +static int sbcap_cbc_accept_cb(struct osmo_stream_srv_link *link, int fd) +{ + struct osmo_sbcap_cbc *cbc = osmo_stream_srv_link_get_data(link); + struct osmo_sbcap_cbc_client *client = talloc_zero(cbc, struct osmo_sbcap_cbc_client); + char remote_ip[INET6_ADDRSTRLEN], portbuf[6]; + int remote_port; + OSMO_ASSERT(client); + + remote_ip[0] = '\0'; + portbuf[0] = '\0'; + osmo_sock_get_ip_and_port(fd, remote_ip, sizeof(remote_ip), portbuf, sizeof(portbuf), false); + remote_port = atoi(portbuf); + + LOGP(DSBcAP, LOGL_NOTICE, "New SBc-AP client connection from %s:%u\n", remote_ip, remote_port); + + client->conn = osmo_stream_srv_create(link, link, fd, sbcap_cbc_read_cb, sbcap_cbc_closed_cb, client); + if (!client->conn) { + LOGP(DSBcAP, LOGL_ERROR, "Unable to create stream server for %s:%d\n", + remote_ip, remote_port); + talloc_free(client); + return -1; + } + client->fi = osmo_fsm_inst_alloc(&sbcap_server_fsm, client, client, LOGL_DEBUG, NULL); + if (!client->fi) { + LOGPSBCAPC(client, LOGL_ERROR, "Unable to allocate FSM\n"); + osmo_stream_srv_destroy(client->conn); + talloc_free(client); + return -1; + } + llist_add_tail(&client->list, &cbc->clients); + + /* Match client to peer */ + client->peer = cbc_peer_by_addr_proto(remote_ip, remote_port, CBC_PEER_PROTO_SBcAP); + if (!client->peer) { + if (g_cbc->config.permit_unknown_peers) { + LOGPSBCAPC(client, LOGL_NOTICE, "Accepting unknown SBc-AP peer %s:%d\n", + remote_ip, remote_port); + client->peer = cbc_peer_create(NULL, CBC_PEER_PROTO_SBcAP); + OSMO_ASSERT(client->peer); + client->peer->unknown_dynamic_peer = true; + } else { + LOGPSBCAPC(client, LOGL_NOTICE, "Rejecting unknown SBc-AP peer %s:%d (not permitted)\n", + remote_ip, remote_port); + osmo_stream_srv_destroy(client->conn); + return -1; + } + } else { + if (client->peer->client.sbcap) { + LOGPSBCAPC(client, LOGL_ERROR, "We already have a connection for peer %s\n", + client->peer->name); + /* FIXME */ + } + client->peer->client.sbcap = client; + } + + osmo_fsm_inst_dispatch(client->fi, SBcAP_SRV_E_CMD_RESET, NULL); + return 0; +} + +void sbcap_cbc_client_tx(struct osmo_sbcap_cbc_client *client, SBcAP_SBC_AP_PDU_t *pdu) +{ + struct msgb *msg; + + if (!pdu) { + LOGP(DSBcAP, LOGL_NOTICE, "Cannot transmit msg: no pdu\n"); + return; + } + + if (!client) { + LOGP(DSBcAP, LOGL_NOTICE, "Cannot transmit msg: no connection\n"); + return; + } + + LOGPSBCAPC(client, LOGL_INFO, "Transmitting msg\n"); + OSMO_ASSERT(client->conn); + msg = sbcap_encode(pdu); + if (!msg) + goto ret_free; + LOGPSBCAPC(client, LOGL_DEBUG, "Encoded message: %s\n", msgb_hexdump(msg)); + osmo_stream_srv_send(client->conn, msg); +ret_free: + sbcap_pdu_free(pdu); +} + +void sbcap_cbc_client_close(struct osmo_sbcap_cbc_client *client) +{ + osmo_stream_srv_destroy(client->conn); +} + +/* initialize the CBC-side SBc-AP server */ +struct osmo_sbcap_cbc *sbcap_cbc_create(void *ctx) +{ + struct osmo_sbcap_cbc *cbc = talloc_zero(ctx, struct osmo_sbcap_cbc); + int rc; + int bind_port = g_cbc->config.sbcap.local_port; + + if (bind_port == -1) + bind_port = SBcAP_SCTP_PORT; + + OSMO_ASSERT(cbc); + cbc->rx_cb = sbcap_cbc_client_rx_cb; + INIT_LLIST_HEAD(&cbc->clients); + cbc->link = osmo_stream_srv_link_create(cbc); + osmo_stream_srv_link_set_proto(cbc->link, IPPROTO_SCTP); + osmo_stream_srv_link_set_data(cbc->link, cbc); + osmo_stream_srv_link_set_nodelay(cbc->link, true); + osmo_stream_srv_link_set_port(cbc->link, bind_port); + osmo_stream_srv_link_set_addrs(cbc->link, (const char **)g_cbc->config.sbcap.local_host, + g_cbc->config.sbcap.num_local_host); + osmo_stream_srv_link_set_accept_cb(cbc->link, sbcap_cbc_accept_cb); + rc = osmo_stream_srv_link_open(cbc->link); + OSMO_ASSERT(rc == 0); + LOGP(DSBcAP, LOGL_NOTICE, "Listening for SBc-AP at %s\n", + osmo_stream_srv_link_get_sockname(cbc->link)); + + return cbc; +} diff --git a/src/sbcap_server_fsm.c b/src/sbcap_server_fsm.c new file mode 100644 index 0000000..18167bd --- /dev/null +++ b/src/sbcap_server_fsm.c @@ -0,0 +1,330 @@ +/* (C) 2022 by sysmocom - s.f.m.c. GmbH + * All Rights Reserved + * Author: Pau Espin Pedrol + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include + +#include + +#include +#include + +#define S(x) (1 << (x)) + +enum sbcap_server_state { + /* initial state after client SCTP connection established */ + SBcAP_SRV_S_INIT, + /* normal operation (idle) */ + SBcAP_SRV_S_IDLE, +}; + +static const struct value_string sbcap_server_event_names[] = { + { SBcAP_SRV_E_RX_RST_COMPL, "Rx Reset Complete" }, + { SBcAP_SRV_E_RX_RST_FAIL, "Rx Reset Failure" }, + { SBcAP_SRV_E_RX_KA_COMPL, "Rx Keep-Alive Complete" }, + { SBcAP_SRV_E_RX_RESTART, "Rx Restart" }, + { SBcAP_SRV_E_CMD_RESET, "RESET.cmd" }, + { SBcAP_SRV_E_CMD_CLOSE, "CLOSE.cmd" }, + { 0, NULL } +}; + +static void sbcap_server_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case SBcAP_SRV_E_CMD_RESET: + osmo_fsm_inst_state_chg(fi, SBcAP_SRV_S_IDLE, 0, 0); + break; + default: + OSMO_ASSERT(0); + } +} + +static void sbcap_server_s_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + default: + OSMO_ASSERT(0); + } +} + +static void sbcap_server_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_sbcap_cbc_client *client = (struct osmo_sbcap_cbc_client *) fi->priv; + //SBcAP_SBC_AP_PDU_t *pdu; + + switch (event) { + case SBcAP_SRV_E_CMD_CLOSE: + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL); + break; + case SBcAP_SRV_E_RX_RESTART: + //pdu = data; + /* TODO: delete any CBS state we have for this peer */ + /* TODO: re-send messages we have matching the scope of the peer */ + LOGPSBCAPC(client, LOGL_NOTICE, "RESTART but re-sending not implemented yet\n"); + break; + default: + OSMO_ASSERT(0); + } +} + +static void sbcap_server_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct osmo_sbcap_cbc_client *client = (struct osmo_sbcap_cbc_client *) fi->priv; + + if (client->conn) + osmo_stream_srv_destroy(client->conn); + llist_del(&client->list); + client->fi = NULL; + + /* reparent the fsm_inst to the cbc as we're about to free() it's talloc + * parent 'client' */ + talloc_steal(g_cbc, fi); + talloc_free(client); +} + +static const struct osmo_fsm_state sbcap_server_fsm_states[] = { + [SBcAP_SRV_S_INIT] = { + .name = "INIT", + .in_event_mask = S(SBcAP_SRV_E_CMD_RESET), + .out_state_mask = S(SBcAP_SRV_S_IDLE), + .action = sbcap_server_s_init, + }, + [SBcAP_SRV_S_IDLE] = { + .name = "IDLE", + .in_event_mask = 0, + .out_state_mask = 0, + .action = sbcap_server_s_idle, + }, +}; + +struct osmo_fsm sbcap_server_fsm = { + .name = "SBcAP-SERVER", + .states = sbcap_server_fsm_states, + .num_states = ARRAY_SIZE(sbcap_server_fsm_states), + .allstate_event_mask = S(SBcAP_SRV_E_CMD_CLOSE) | + S(SBcAP_SRV_E_RX_RESTART), + .allstate_action = sbcap_server_fsm_allstate, + .log_subsys = DSBcAP, + .event_names = sbcap_server_event_names, + .cleanup = sbcap_server_fsm_cleanup, +}; + +static void *sbcap_as_find_ie(void *void_list, SBcAP_ProtocolIE_ID_t ie_id) +{ + A_SEQUENCE_OF(SBcAP_ProtocolIE_ID_t) *li = (void *)void_list; + int i; + for (i = 0; i < li->count; i++) { + /* "SBcAP_ProtocolIE_ID_t id" is first element in all *_IEs struct */ + SBcAP_ProtocolIE_ID_t *cur_ie_id = li->array[i]; + if (*cur_ie_id == ie_id) { + return cur_ie_id; + } + } + return NULL; +} + +static SBcAP_Message_Identifier_t *get_msg_id_ie(struct osmo_sbcap_cbc_client *client, + const SBcAP_SBC_AP_PDU_t *pdu) +{ + A_SEQUENCE_OF(void) *as_pdu = NULL; + /* static const long asn_VAL_1_SBcAP_id_Message_Identifier = 5; */ + const SBcAP_ProtocolIE_ID_t msg_id_ie = 5; + void *ie; + + switch (pdu->present) { + case SBcAP_SBC_AP_PDU_PR_initiatingMessage: + switch (pdu->choice.initiatingMessage.procedureCode) { + case SBcAP_ProcedureId_Write_Replace_Warning_Indication: + as_pdu = (void *)&pdu->choice.initiatingMessage.value.choice.Write_Replace_Warning_Indication.protocolIEs.list; + if (!(ie = sbcap_as_find_ie(as_pdu, msg_id_ie))) + return NULL; + return &((SBcAP_Write_Replace_Warning_Indication_IEs_t *)ie)->value.choice.Message_Identifier; + case SBcAP_ProcedureId_Stop_Warning_Indication: + as_pdu = (void *)&pdu->choice.initiatingMessage.value.choice.Stop_Warning_Indication.protocolIEs.list; + if (!(ie = sbcap_as_find_ie(as_pdu, msg_id_ie))) + return NULL; + return &((SBcAP_Stop_Warning_Indication_IEs_t *)ie)->value.choice.Message_Identifier; + default: + LOGPSBCAPC(client, LOGL_ERROR, "get_msg_id initiatingMessage procedure=%ld not implemented\n", + pdu->choice.unsuccessfulOutcome.procedureCode); + return NULL; + } + break; + case SBcAP_SBC_AP_PDU_PR_successfulOutcome: + switch (pdu->choice.successfulOutcome.procedureCode) { + case SBcAP_ProcedureId_Write_Replace_Warning: + as_pdu = (void *)&pdu->choice.successfulOutcome.value.choice.Write_Replace_Warning_Response.protocolIEs.list; + if (!(ie = sbcap_as_find_ie(as_pdu, msg_id_ie))) + return NULL; + return &((SBcAP_Write_Replace_Warning_Response_IEs_t *)ie)->value.choice.Message_Identifier; + case SBcAP_ProcedureId_Stop_Warning: + as_pdu = (void *)&pdu->choice.successfulOutcome.value.choice.Stop_Warning_Response.protocolIEs.list; + if (!(ie = sbcap_as_find_ie(as_pdu, msg_id_ie))) + return NULL; + return &((SBcAP_Stop_Warning_Response_IEs_t *)ie)->value.choice.Message_Identifier; + default: + LOGPSBCAPC(client, LOGL_ERROR, "get_msg_id successfulOutcome procedure=%ld not implemented\n", + pdu->choice.unsuccessfulOutcome.procedureCode); + return NULL; + } + break; + case SBcAP_SBC_AP_PDU_PR_unsuccessfulOutcome: + switch (pdu->choice.unsuccessfulOutcome.procedureCode) { + default: + LOGPSBCAPC(client, LOGL_ERROR, "get_msg_id unsuccessfulOutcome procedure=%ld not implemented\n", + pdu->choice.unsuccessfulOutcome.procedureCode); + return NULL; + } + break; + default: + return NULL; + } +} + +static int get_msg_id(struct osmo_sbcap_cbc_client *client, const SBcAP_SBC_AP_PDU_t *pdu) +{ + SBcAP_Message_Identifier_t *ie = get_msg_id_ie(client, pdu); + if (!ie) + return -1; + if (ie->size != 2) { + LOGPSBCAPC(client, LOGL_ERROR, "get_msg_id wrong size %ld\n", ie->size); + return -1; + } + return osmo_load16be(ie->buf); +} + +/* message was received from remote SBcAP peer (BSC) */ +int sbcap_cbc_client_rx_cb(struct osmo_sbcap_cbc_client *client, SBcAP_SBC_AP_PDU_t *pdu) +{ + struct cbc_message *smscb; + struct cbc_message_peer *mp; + int msg_id; + + /* messages without reference to a specific SMSCB message */ + switch (pdu->present) { + case SBcAP_SBC_AP_PDU_PR_initiatingMessage: + switch (pdu->choice.initiatingMessage.procedureCode) { + case SBcAP_ProcedureId_Write_Replace_Warning: + case SBcAP_ProcedureId_Stop_Warning: + LOGPSBCAPC(client, LOGL_ERROR, + "SBcAP initiatingMessage procedure=%ld MME->CBC not expected\n", + pdu->choice.initiatingMessage.procedureCode); + return -EINVAL; + case SBcAP_ProcedureId_PWS_Restart_Indication: + return osmo_fsm_inst_dispatch(client->fi, SBcAP_SRV_E_RX_RESTART, pdu); + case SBcAP_ProcedureId_Stop_Warning_Indication: + case SBcAP_ProcedureId_Write_Replace_Warning_Indication: + break; /* Handle msg id below */ + case SBcAP_ProcedureId_Error_Indication: + case SBcAP_ProcedureId_PWS_Failure_Indication: + default: + LOGPSBCAPC(client, LOGL_ERROR, "SBcAP initiatingMessage procedure=%ld not implemented?\n", + pdu->choice.initiatingMessage.procedureCode); + return 0; + } + break; + case SBcAP_SBC_AP_PDU_PR_successfulOutcome: + switch (pdu->choice.successfulOutcome.procedureCode) { + default: + LOGPSBCAPC(client, LOGL_INFO, "SBcAP SuccessfulOutcome procedure=%ld\n", + pdu->choice.successfulOutcome.procedureCode); + break; + } + break; + case SBcAP_SBC_AP_PDU_PR_unsuccessfulOutcome: + switch (pdu->choice.unsuccessfulOutcome.procedureCode) { + default: + LOGPSBCAPC(client, LOGL_ERROR, "SBcAP UnsuccessfulOutcome procedure=%ld\n", + pdu->choice.unsuccessfulOutcome.procedureCode); + break; + } + break; + case SBcAP_SBC_AP_PDU_PR_NOTHING: + default: + LOGPSBCAPC(client, LOGL_ERROR, "Rx SBc-AP unexpected message type %d\n", + pdu->present); + return 0; + } + + + /* messages with reference to a specific SMSCB message handled below*/ + msg_id = get_msg_id(client, pdu); + OSMO_ASSERT(msg_id >= 0); + + /* look-up smscb_message */ + smscb = cbc_message_by_id(msg_id); + if (!smscb) { + LOGPSBCAPC(client, LOGL_ERROR, "Rx SBc-AP msg for unknown message-id 0x%04x\n", + msg_id); + /* TODO: inform peer? */ + return 0; + } + + /* look-up smscb_message_peer */ + mp = cbc_message_peer_get(smscb, client->peer); + if (!mp) { + LOGPSBCAPC(client, LOGL_ERROR, "Rx SBc-AP msg for message-id 0x%04x without peer %s\n", + msg_id, client->peer->name); + /* TODO: inform peer? */ + return 0; + } + + /* dispatch event to smscp_p_fms instance */ + switch (pdu->present) { + case SBcAP_SBC_AP_PDU_PR_initiatingMessage: + switch (pdu->choice.initiatingMessage.procedureCode) { + default: + break; + } + break; + case SBcAP_SBC_AP_PDU_PR_successfulOutcome: + switch (pdu->choice.successfulOutcome.procedureCode) { + case SBcAP_ProcedureId_Write_Replace_Warning: + //if (dec->u.write_replace_compl.old_serial_nr) + // return osmo_fsm_inst_dispatch(mp->fi, SMSCB_E_SBcAP_REPLACE_ACK, dec); + //else + return osmo_fsm_inst_dispatch(mp->fi, SMSCB_E_SBCAP_WRITE_ACK, pdu); + case SBcAP_ProcedureId_Stop_Warning: + return osmo_fsm_inst_dispatch(mp->fi, SMSCB_E_SBCAP_DELETE_ACK, pdu); + default: + break; + } + break; + case SBcAP_SBC_AP_PDU_PR_unsuccessfulOutcome: + switch (pdu->choice.unsuccessfulOutcome.procedureCode) { + case SBcAP_ProcedureId_Stop_Warning: + return osmo_fsm_inst_dispatch(mp->fi, SMSCB_E_SBCAP_DELETE_NACK, pdu); + default: + break; + } + break; + case SBcAP_SBC_AP_PDU_PR_NOTHING: + default: + OSMO_ASSERT(0); + } + return 0; +} + +static __attribute__((constructor)) void on_dso_load_sbcap_srv_fsm(void) +{ + OSMO_ASSERT(osmo_fsm_register(&sbcap_server_fsm) == 0); +} diff --git a/src/smscb_message_fsm.c b/src/smscb_message_fsm.c index 04b0718..27d31c2 100644 --- a/src/smscb_message_fsm.c +++ b/src/smscb_message_fsm.c @@ -62,6 +62,8 @@ static void smscb_fsm_wait_write_ack(struct osmo_fsm_inst *fi, uint32_t event, v switch (event) { case SMSCB_E_CBSP_WRITE_ACK: case SMSCB_E_CBSP_WRITE_NACK: + case SMSCB_E_SBCAP_WRITE_ACK: + case SMSCB_E_SBCAP_WRITE_NACK: /* check if any per-peer children have not yet received the ACK or * timed out */ llist_for_each_entry(peer_fi, &fi->proc.children, proc.child) { @@ -179,6 +181,8 @@ static void smscb_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event, switch (event) { case SMSCB_E_CBSP_DELETE_ACK: case SMSCB_E_CBSP_DELETE_NACK: + case SMSCB_E_SBCAP_DELETE_ACK: + case SMSCB_E_SBCAP_DELETE_NACK: llist_for_each_entry(peer_fi, &fi->proc.children, proc.child) { if (peer_fi->state != SMSCB_S_DELETED) return; @@ -225,7 +229,9 @@ static struct osmo_fsm_state smscb_fsm_states[] = { [SMSCB_S_WAIT_WRITE_ACK] = { .name = "WAIT_WRITE_ACK", .in_event_mask = S(SMSCB_E_CBSP_WRITE_ACK) | - S(SMSCB_E_CBSP_WRITE_NACK), + S(SMSCB_E_CBSP_WRITE_NACK) | + S(SMSCB_E_SBCAP_WRITE_ACK) | + S(SMSCB_E_SBCAP_WRITE_NACK), .out_state_mask = S(SMSCB_S_ACTIVE), .action = smscb_fsm_wait_write_ack, .onleave = smscb_fsm_wait_write_ack_onleave, @@ -260,7 +266,9 @@ static struct osmo_fsm_state smscb_fsm_states[] = { [SMSCB_S_WAIT_DELETE_ACK] = { .name = "WAIT_DELETE_ACK", .in_event_mask = S(SMSCB_E_CBSP_DELETE_ACK) | - S(SMSCB_E_CBSP_DELETE_NACK), + S(SMSCB_E_CBSP_DELETE_NACK) | + S(SMSCB_E_SBCAP_DELETE_ACK) | + S(SMSCB_E_SBCAP_DELETE_NACK), .out_state_mask = S(SMSCB_S_DELETED), .action = smscb_fsm_wait_delete_ack, .onleave = smscb_fsm_wait_delete_ack_onleave, diff --git a/src/smscb_peer_fsm.c b/src/smscb_peer_fsm.c index cc91e39..c72a8b4 100644 --- a/src/smscb_peer_fsm.c +++ b/src/smscb_peer_fsm.c @@ -32,8 +32,12 @@ #include #include +#include + #include #include +#include +#include #include #define S(x) (1 << (x)) @@ -44,14 +48,18 @@ const struct value_string smscb_fsm_event_names[] = { { SMSCB_E_REPLACE, "REPLACE" }, { SMSCB_E_STATUS, "STATUS" }, { SMSCB_E_DELETE, "DELETE" }, - { SMSCB_E_CBSP_WRITE_ACK, "WRITE_ACK" }, - { SMSCB_E_CBSP_WRITE_NACK, "WRITE_NACK" }, - { SMSCB_E_CBSP_REPLACE_ACK, "REPLACE_ACK" }, - { SMSCB_E_CBSP_REPLACE_NACK, "REPLACE_NACK" }, - { SMSCB_E_CBSP_DELETE_ACK, "DELETE_ACK" }, - { SMSCB_E_CBSP_DELETE_NACK, "DELETE_NACK" }, - { SMSCB_E_CBSP_STATUS_ACK, "STATUS_ACK" }, - { SMSCB_E_CBSP_STATUS_NACK, "STATUS_NACK" }, + { SMSCB_E_CBSP_WRITE_ACK, "CBSP_WRITE_ACK" }, + { SMSCB_E_CBSP_WRITE_NACK, "CBSP_WRITE_NACK" }, + { SMSCB_E_CBSP_REPLACE_ACK, "CBSP_REPLACE_ACK" }, + { SMSCB_E_CBSP_REPLACE_NACK, "CBSP_REPLACE_NACK" }, + { SMSCB_E_CBSP_DELETE_ACK, "CBSP_DELETE_ACK" }, + { SMSCB_E_CBSP_DELETE_NACK, "CBSP_DELETE_NACK" }, + { SMSCB_E_CBSP_STATUS_ACK, "CBSP_STATUS_ACK" }, + { SMSCB_E_CBSP_STATUS_NACK, "CBSP_STATUS_NACK" }, + { SMSCB_E_SBCAP_WRITE_ACK, "SBcAP_WRITE_ACK" }, + { SMSCB_E_SBCAP_WRITE_NACK, "SBcAP_WRITE_NACK" }, + { SMSCB_E_SBCAP_DELETE_ACK, "SBcAP_DELETE_ACK" }, + { SMSCB_E_SBCAP_DELETE_NACK, "SBcAP_DELETE_NACK" }, { 0, NULL } }; @@ -300,6 +308,7 @@ static void smscb_p_fsm_wait_write_ack(struct osmo_fsm_inst *fi, uint32_t event, { struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; struct osmo_cbsp_decoded *dec = NULL; + //SBcAP_SBC_AP_PDU_t *pdu = NULL; switch (event) { case SMSCB_E_CBSP_WRITE_ACK: @@ -319,6 +328,18 @@ static void smscb_p_fsm_wait_write_ack(struct osmo_fsm_inst *fi, uint32_t event, /* Signal parent fsm about completion */ osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_CBSP_WRITE_NACK, mp); break; + case SMSCB_E_SBCAP_WRITE_ACK: + //pdu = data; + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_SBCAP_WRITE_ACK, mp); + break; + case SMSCB_E_SBCAP_WRITE_NACK: + //pdu = data; + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_SBCAP_WRITE_NACK, mp); + break; default: OSMO_ASSERT(0); } @@ -416,6 +437,7 @@ static void smscb_p_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event { struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; struct osmo_cbsp_decoded *dec = NULL; + //SBcAP_SBC_AP_PDU_t *pdu = NULL; switch (event) { case SMSCB_E_CBSP_DELETE_ACK: @@ -437,6 +459,18 @@ static void smscb_p_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event /* Signal parent fsm about completion */ osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_CBSP_DELETE_NACK, mp); break; + case SMSCB_E_SBCAP_DELETE_ACK: + //pdu = data; + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_SBCAP_DELETE_ACK, mp); + break; + case SMSCB_E_SBCAP_DELETE_NACK: + //pdu = data; + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_SBCAP_DELETE_NACK, mp); + break; default: OSMO_ASSERT(0); } @@ -446,10 +480,22 @@ static void smscb_p_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event static int smscb_p_fsm_timer_cb(struct osmo_fsm_inst *fi) { + struct cbc_message_peer *mp = (struct cbc_message_peer *)fi->priv; + int ev; switch (fi->T) { case T_WAIT_WRITE_ACK: osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_CBSP_WRITE_NACK, NULL); + switch (mp->peer->proto) { + case CBC_PEER_PROTO_CBSP: + ev = SMSCB_E_CBSP_WRITE_NACK; + break; + case CBC_PEER_PROTO_SBcAP: + ev = SMSCB_E_SBCAP_WRITE_NACK; + break; + default: + OSMO_ASSERT(0); + } + osmo_fsm_inst_dispatch(fi->proc.parent, ev, NULL); break; case T_WAIT_REPLACE_ACK: osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); @@ -461,7 +507,17 @@ static int smscb_p_fsm_timer_cb(struct osmo_fsm_inst *fi) break; case T_WAIT_DELETE_ACK: osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_E_CBSP_DELETE_NACK, NULL); + switch (mp->peer->proto) { + case CBC_PEER_PROTO_CBSP: + ev = SMSCB_E_CBSP_DELETE_NACK; + break; + case CBC_PEER_PROTO_SBcAP: + ev = SMSCB_E_SBCAP_DELETE_NACK; + break; + default: + OSMO_ASSERT(0); + } + osmo_fsm_inst_dispatch(fi->proc.parent, ev, NULL); break; default: OSMO_ASSERT(0); @@ -473,6 +529,7 @@ static void smscb_p_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void { struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; struct osmo_cbsp_decoded *cbsp; + SBcAP_SBC_AP_PDU_t *sbcap; switch (event) { case SMSCB_E_DELETE: /* send KILL to BSC */ @@ -485,19 +542,35 @@ static void smscb_p_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void default: break; } - cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_KILL); - OSMO_ASSERT(cbsp); - cbsp->u.kill.msg_id = mp->cbcmsg->msg.message_id; - cbsp->u.kill.old_serial_nr = mp->cbcmsg->msg.serial_nr; - /* TODO: we assume that the delete will always affect all original cells */ - cbsp_append_cell_list(&cbsp->u.kill.cell_list, cbsp, mp); - if (!mp->cbcmsg->msg.is_etws) { - /* Channel Indication IE is only present in CBS, not in ETWS! */ - cbsp->u.kill.channel_ind = talloc_zero(cbsp, enum cbsp_channel_ind); - OSMO_ASSERT(cbsp->u.kill.channel_ind); - *(cbsp->u.kill.channel_ind) = CBSP_CHAN_IND_BASIC; + switch (mp->peer->proto) { + case CBC_PEER_PROTO_CBSP: + cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_KILL); + OSMO_ASSERT(cbsp); + cbsp->u.kill.msg_id = mp->cbcmsg->msg.message_id; + cbsp->u.kill.old_serial_nr = mp->cbcmsg->msg.serial_nr; + /* TODO: we assume that the delete will always affect all original cells */ + cbsp_append_cell_list(&cbsp->u.kill.cell_list, cbsp, mp); + if (!mp->cbcmsg->msg.is_etws) { + /* Channel Indication IE is only present in CBS, not in ETWS! */ + cbsp->u.kill.channel_ind = talloc_zero(cbsp, enum cbsp_channel_ind); + OSMO_ASSERT(cbsp->u.kill.channel_ind); + *(cbsp->u.kill.channel_ind) = CBSP_CHAN_IND_BASIC; + } + cbsp_cbc_client_tx(mp->peer->client.cbsp, cbsp); + break; + case CBC_PEER_PROTO_SBcAP: + if ((sbcap = sbcap_gen_stop_warning_req(mp->peer, mp->cbcmsg))) { + sbcap_cbc_client_tx(mp->peer->client.sbcap, sbcap); + } else { + LOGP(DSBcAP, LOGL_ERROR, + "[%s] Tx SBc-AP Stop-Warning-Request: msg gen failed\n", + mp->peer->name); + } + break; + case CBC_PEER_PROTO_SABP: + default: + osmo_panic("SMSCB_E_DELETE not implemented for proto %u", mp->peer->proto); } - cbsp_cbc_client_tx(mp->peer->client.cbsp, cbsp); osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_DELETE_ACK, 10, T_WAIT_DELETE_ACK); break; default: @@ -522,7 +595,9 @@ static const struct osmo_fsm_state smscb_p_fsm_states[] = { [SMSCB_S_WAIT_WRITE_ACK] = { .name = "WAIT_WRITE_ACK", .in_event_mask = S(SMSCB_E_CBSP_WRITE_ACK) | - S(SMSCB_E_CBSP_WRITE_NACK), + S(SMSCB_E_CBSP_WRITE_NACK) | + S(SMSCB_E_SBCAP_WRITE_ACK) | + S(SMSCB_E_SBCAP_WRITE_NACK), .out_state_mask = S(SMSCB_S_ACTIVE) | S(SMSCB_S_WAIT_DELETE_ACK), .action = smscb_p_fsm_wait_write_ack, @@ -555,7 +630,9 @@ static const struct osmo_fsm_state smscb_p_fsm_states[] = { [SMSCB_S_WAIT_DELETE_ACK] = { .name = "WAIT_DELETE_ACK", .in_event_mask = S(SMSCB_E_CBSP_DELETE_ACK) | - S(SMSCB_E_CBSP_DELETE_NACK), + S(SMSCB_E_CBSP_DELETE_NACK) | + S(SMSCB_E_SBCAP_DELETE_ACK) | + S(SMSCB_E_SBCAP_DELETE_NACK), .out_state_mask = S(SMSCB_S_DELETED), .action = smscb_p_fsm_wait_delete_ack, },