From 77911b00915192ce0d15bda8f9dc046ed55bd13a Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Tue, 14 Aug 2018 23:47:30 +0200 Subject: [PATCH] Add initial remsim-bankd skeleton This is not a complete program yet, but a rough initial skeleton with the key data structures in place, as well as the thread / locking model in place. Change-Id: I5ad5a1a4918b8eacdaeb7e709ff05dc056346752 --- configure.ac | 3 + include/osmocom/rspro/Makefile.am | 6 + include/osmocom/rspro/rspro_client.h | 59 ++++++ src/Makefile.am | 20 +- src/bankd.h | 118 +++++++++++ src/bankd_main.c | 285 ++++++++++++++++++++++++++ src/bankd_slotmap.c | 100 +++++++++ src/driver_pcsc.c | 4 +- src/main.c | 7 +- src/rspro_client.c | 296 +++++++++++++++++++++++++++ src/rspro_util.c | 2 - 11 files changed, 893 insertions(+), 7 deletions(-) create mode 100644 include/osmocom/rspro/rspro_client.h create mode 100644 src/bankd.h create mode 100644 src/bankd_main.c create mode 100644 src/bankd_slotmap.c create mode 100644 src/rspro_client.c diff --git a/configure.ac b/configure.ac index c3ec5ad..5332db8 100644 --- a/configure.ac +++ b/configure.ac @@ -33,7 +33,10 @@ fi PKG_PROG_PKG_CONFIG([0.20]) PKG_CHECK_MODULES(OSMOCORE, libosmocore >= 0.11.0) +PKG_CHECK_MODULES(OSMOGSM, libosmogsm >= 0.11.0) +PKG_CHECK_MODULES(OSMOABIS, libosmoabis) PKG_CHECK_MODULES(ASN1C, libasn1c >= 0.9.30) +PKG_CHECK_MODULES(PCSC, libpcsclite) AC_CONFIG_MACRO_DIR([m4]) diff --git a/include/osmocom/rspro/Makefile.am b/include/osmocom/rspro/Makefile.am index 0bb7b82..fee7733 100644 --- a/include/osmocom/rspro/Makefile.am +++ b/include/osmocom/rspro/Makefile.am @@ -36,3 +36,9 @@ noinst_HEADERS = \ TpduFlags.h \ TpduModemToCard.h \ $(NULL) + + +rspro_HEADERS = \ + rspro_client.h + +rsprodir = $(includedir)/osmocom/rspro diff --git a/include/osmocom/rspro/rspro_client.h b/include/osmocom/rspro/rspro_client.h new file mode 100644 index 0000000..76f3da6 --- /dev/null +++ b/include/osmocom/rspro/rspro_client.h @@ -0,0 +1,59 @@ +/* Remote SIM Protocol client */ + +/* (C) 2018 by Harald Welte + * All Rights Reserved + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once + +#include + +/* a loss of RSPRO is considered quite serious, let's try to recover as quickly as + * possible. Even one new connection attempt per second should be quite acceptable until the link is + * re-established */ +#define OSMO_RSPRO_CLIENT_RECONNECT_INTERVAL 1 +#define OSMO_RSPRO_CLIENT_PING_INTERVAL 20 + +struct msgb; +struct ipa_client_conn; +struct osmo_rspro_client; + +/* Expects message in msg->l2h */ +typedef int (*osmo_rspro_client_read_cb_t)(struct osmo_rspro_client *rsproc, struct msgb *msg); + +struct osmo_rspro_client { + const char *unit_name; + + struct ipa_client_conn *link; + osmo_rspro_client_read_cb_t read_cb; + void *data; + + struct osmo_timer_list ping_timer; + struct osmo_timer_list connect_timer; + int is_connected; + int got_ipa_pong; +}; + +struct osmo_rspro_client *osmo_rspro_client_create(void *talloc_ctx, + const char *unit_name, + const char *ip_addr, + unsigned int tcp_port, + osmo_rspro_client_read_cb_t read_cb); + +void osmo_rspro_client_destroy(struct osmo_rspro_client *rsproc); +int osmo_rspro_client_send(struct osmo_rspro_client *rsproc, struct msgb *msg); +struct msgb *osmo_rspro_client_msgb_alloc(void); + diff --git a/src/Makefile.am b/src/Makefile.am index 05ac9ae..f1731fa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,10 +1,24 @@ SUBDIRS = rspro AM_CFLAGS = -Wall -I$(top_srcdir)/include -I$(top_builddir)/include \ - $(OSMOCORE_CFLAGS) $(ASN1C_CFLAGS) + $(OSMOCORE_CFLAGS) $(OSMOGSM_CFLAGS) $(OSMOABIS_CFLAGS) \ + $(ASN1C_CFLAGS) $(PCSC_CFLAGS) RSPRO_LIBVERSION=0:0:0 lib_LTLIBRARIES = libosmo-rspro.la libosmo_rspro_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(RSPRO_LIBVERSION) -libosmo_rspro_la_LIBADD = $(OSMOCORE_LIBS) $(ASN1C_LIBS) rspro/libosmo-asn1-rspro.la -libosmo_rspro_la_SOURCES = rspro_util.c +libosmo_rspro_la_LIBADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \ + $(ASN1C_LIBS) rspro/libosmo-asn1-rspro.la +libosmo_rspro_la_SOURCES = rspro_util.c rspro_client.c + +noinst_HEADERS = bankd.h internal.h + +bin_PROGRAMS = pcsc_test remsim-bankd + +pcsc_test_SOURCES = driver_core.c driver_pcsc.c main.c +pcsc_test_LDADD = $(OSMOCORE_LIBS) \ + $(ASN1C_LIBS) $(PCSC_LIBS) libosmo-rspro.la + +remsim_bankd_SOURCES = bankd_slotmap.c bankd_main.c +remsim_bankd_LDADD = $(OSMOCORE_LIBS) \ + $(ASN1C_LIBS) $(PCSC_LIBS) libosmo-rspro.la diff --git a/src/bankd.h b/src/bankd.h new file mode 100644 index 0000000..eabf132 --- /dev/null +++ b/src/bankd.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +struct bankd; + +struct bank_slot { + uint16_t bank_id; + uint16_t slot_nr; +}; + +static inline bool bank_slot_equals(const struct bank_slot *a, const struct bank_slot *b) +{ + if (a->bank_id == b->bank_id && a->slot_nr == b->slot_nr) + return true; + else + return false; +} + +struct client_slot { + uint16_t client_id; + uint16_t slot_nr; +}; + +static inline bool client_slot_equals(const struct client_slot *a, const struct client_slot *b) +{ + if (a->client_id == b->client_id && a->slot_nr == b->slot_nr) + return true; + else + return false; +} + +/* slot mappings are created / removed by the server */ +struct bankd_slot_mapping { + /* global lits of bankd slot mappings */ + struct llist_head list; + /* slot on bank side */ + struct bank_slot bank; + /* slot on client side */ + struct client_slot client; +}; + +/* thread-safe lookup of map by client:slot */ +struct bankd_slot_mapping *bankd_slotmap_by_client(struct bankd *bankd, + const struct client_slot *client); + +/* thread-safe lookup of map by bank:slot */ +struct bankd_slot_mapping *bankd_slotmap_by_bank(struct bankd *bankd, const struct bank_slot *bank); + +/* thread-safe creating of a new bank<->client map */ +int bankd_slotmap_add(struct bankd *bankd, const struct bank_slot *bank, + const struct client_slot *client); + +/* thread-safe removal of a bank<->client map */ +void bankd_slotmap_del(struct bankd *bankd, struct bankd_slot_mapping *map); + + +/* bankd worker instance; one per card/slot, includes thread */ +struct bankd_worker { + /* global list of workers */ + struct llist_head list; + /* back-pointer to bankd */ + struct bankd *bankd; + + /* slot number we are representing */ + struct bank_slot slot; + + /* thread of this worker. */ + pthread_t thread; + + /* File descriptor of the TCP connection to the remsim-client (modem) */ + struct { + int fd; + struct sockaddr_storage peer_addr; + socklen_t peer_addr_len; + } client; + + struct { + const char *name; + union { + struct { + /* PC/SC context / application handle */ + SCARDCONTEXT hContext; + /* PC/SC card handle */ + SCARDHANDLE hCard; + } pcsc; + }; + } reader; +}; + + +/* global bank deamon */ +struct bankd { + struct { + uint16_t bank_id; + } cfg; + + /* TCP socket at which we are listening */ + int accept_fd; + + /* list of slit mappings. only ever modified in main thread! */ + struct llist_head slot_mappings; + pthread_rwlock_t slot_mappings_rwlock; + + /* list of bankd_workers. accessed/modified by multiple threads; protected by mutex */ + struct llist_head workers; + pthread_mutex_t workers_mutex; +}; diff --git a/src/bankd_main.c b/src/bankd_main.c new file mode 100644 index 0000000..f6eb64f --- /dev/null +++ b/src/bankd_main.c @@ -0,0 +1,285 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include "bankd.h" + +__thread void *talloc_asn1_ctx; + +static void *worker_main(void *arg); + +/*********************************************************************** +* bankd core / main thread +***********************************************************************/ + +static void bankd_init(struct bankd *bankd) +{ + /* intialize members of 'bankd' */ + INIT_LLIST_HEAD(&bankd->slot_mappings); + pthread_rwlock_init(&bankd->slot_mappings_rwlock, NULL); + INIT_LLIST_HEAD(&bankd->workers); + pthread_mutex_init(&bankd->workers_mutex, NULL); +} + +/* create + start a new bankd_worker thread */ +static struct bankd_worker *bankd_create_worker(struct bankd *bankd) +{ + struct bankd_worker *worker; + int rc; + + worker = talloc_zero(bankd, struct bankd_worker); + if (!worker) + return NULL; + + worker->bankd = bankd; + + /* in the initial state, the worker has no client.fd, bank_slot or pcsc handle yet */ + + rc = pthread_create(&worker->thread, NULL, worker_main, worker); + if (rc != 0) { + talloc_free(worker); + return NULL; + } + + pthread_mutex_lock(&bankd->workers_mutex); + llist_add_tail(&worker->list, &bankd->workers); + pthread_mutex_unlock(&bankd->workers_mutex); + + return worker; +} + +static bool terminate = false; + +int main(int argc, char **argv) +{ + struct bankd *bankd = talloc_zero(NULL, struct bankd); + int i, rc; + + OSMO_ASSERT(bankd); + bankd_init(bankd); + + for (i = 0; i < 10; i++) { + struct bankd_worker *w; + w = bankd_create_worker(bankd); + if (!w) + exit(21); + } + + while (1) { + if (terminate) + break; + } + + talloc_free(bankd); + exit(0); +} + + + +/*********************************************************************** + * bankd worker thread + ***********************************************************************/ + +#define PCSC_ERROR(rv, text) \ +if (rv != SCARD_S_SUCCESS) { \ + fprintf(stderr, text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv); \ + goto end; \ +} else { \ + printf(text ": OK\n\n"); \ +} + + +static void worker_cleanup(void *arg) +{ + struct bankd_worker *worker = (struct bankd_worker *) arg; + struct bankd *bankd = worker->bankd; + + /* FIXME: should we still do this? in the thread ?!? */ + pthread_mutex_lock(&bankd->workers_mutex); + llist_del(&worker->list); + talloc_free(worker); /* FIXME: is this safe? */ + pthread_mutex_unlock(&bankd->workers_mutex); +} + + +#if 0 +/* function running inside a worker thread; doing some initialization */ +static void worker_init(struct bankd_worker *worker) +{ + int rc; + + /* push cleanup helper */ + pthread_cleanup_push(&worker_cleanup, worker); + + /* The PC/SC context must be created inside the thread where we'll later use it */ + rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &worker->reader.pcsc.hContext); + PCSC_ERROR(rc, "SCardEstablishContext") + + rc = SCardConnect(worker->reader.pcsc.hContext, worker->reader.name, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0, &worker->reader.pcsc.hCard, NULL); + PCSC_ERROR(rc, "SCardConnect") + + return; +end: + pthread_exit(NULL); +} +#endif + + +static int blocking_ipa_read(int fd, uint8_t *buf, unsigned int buf_size) +{ + struct ipaccess_head *hh; + uint16_t len; + int needed, rc; + + if (buf_size < sizeof(*hh)) + return -1; + + hh = (struct ipaccess_head *) buf; + + /* 1) blocking read from the socket (IPA header) */ + rc = read(fd, buf, sizeof(*hh)); + if (rc < sizeof(*hh)) + return -2; + + len = ntohs(hh->len); + needed = len; //- sizeof(*hh); + + /* 2) blocking read from the socket (payload) */ + rc = read(fd, buf+sizeof(*hh), needed); + if (rc < needed) + return -3; + + return len; +} + +/* handle one incoming RSPRO message from a client inside a worker thread */ +static int worker_handle_rspro(struct bankd_worker *worker, const RsproPDU_t *pdu) +{ + switch (pdu->msg.present) { + case RsproPDUchoice_PR_connectClientReq: + /* FIXME */ + break; + case RsproPDUchoice_PR_tpduModemToCard: + /* FIXME */ + break; + case RsproPDUchoice_PR_clientSlotStatusInd: + /* FIXME */ + break; + default: + return -100; + } + + return 0; +} + +/* body of the main transceive loop */ +static int worker_transceive_loop(struct bankd_worker *worker) +{ + struct ipaccess_head *hh; + struct ipaccess_head_ext *hh_ext; + uint8_t buf[65536]; /* maximum length expressed in 16bit length field */ + asn_dec_rval_t rval; + int data_len, rc; + RsproPDU_t *pdu; + + /* 1) blocking read of entire IPA message from the socket */ + rc = blocking_ipa_read(worker->client.fd, buf, sizeof(buf)); + if (rc < 0) + return rc; + data_len = rc; + + hh = (struct ipaccess_head *) buf; + if (hh->proto != IPAC_PROTO_OSMO) + return -4; + + hh_ext = (struct ipaccess_head_ext *) buf + sizeof(*hh); + if (data_len < sizeof(*hh_ext)) + return -5; + data_len -= sizeof(*hh_ext); + if (hh_ext->proto != IPAC_PROTO_EXT_RSPRO) + return -6; + + /* 2) ASN1 BER decode of the message */ + rval = ber_decode(NULL, &asn_DEF_RsproPDU, (void **) &pdu, hh_ext->data, data_len); + if (rval.code != RC_OK) + return -7; + + /* 3) handling of the message, possibly resulting in PCSC commands */ + rc = worker_handle_rspro(worker, pdu); + ASN_STRUCT_FREE(asn_DEF_RsproPDU, pdu); + if (rc < 0) + return rc; + + /* everything OK if we reach here */ + return 0; +} + +/* worker thread main function */ +static void *worker_main(void *arg) +{ + struct bankd_worker *worker = (struct bankd_worker *) arg; + void *top_ctx; + int rc; + + /* not permitted in multithreaded environment */ + talloc_disable_null_tracking(); + top_ctx = talloc_named_const(NULL, 0, "top"); + talloc_asn1_ctx = talloc_named_const(top_ctx, 0, "asn1"); + + /* push cleanup helper */ + pthread_cleanup_push(&worker_cleanup, worker); + + /* we continuously perform the same loop here, recycling the worker thread + * once the client connection is gone or we have some trouble with the card/reader */ + while (1) { + worker->client.peer_addr_len = sizeof(worker->client.peer_addr); + + /* first wait for an incoming TCP connection */ + rc = accept(worker->bankd->accept_fd, (struct sockaddr *) &worker->client.peer_addr, + &worker->client.peer_addr_len); + if (rc < 0) { + continue; + } + worker->client.fd = rc; + + /* run the main worker transceive loop body until there was some error */ + while (1) { + rc = worker_transceive_loop(worker); + if (rc < 0) + break; + } + + /* clean-up: reset to sane state */ + if (worker->reader.pcsc.hCard) { + SCardDisconnect(worker->reader.pcsc.hCard, SCARD_UNPOWER_CARD); + worker->reader.pcsc.hCard = 0; + } + if (worker->reader.pcsc.hContext) { + SCardReleaseContext(worker->reader.pcsc.hContext); + worker->reader.pcsc.hContext = 0; + } + if (worker->client.fd >= 0) + close(worker->client.fd); + worker->client.fd = -1; + } + + pthread_cleanup_pop(1); + talloc_free(top_ctx); + pthread_exit(NULL); +} diff --git a/src/bankd_slotmap.c b/src/bankd_slotmap.c new file mode 100644 index 0000000..373399b --- /dev/null +++ b/src/bankd_slotmap.c @@ -0,0 +1,100 @@ + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "bankd.h" + +/* thread-safe lookup of map by client:slot */ +struct bankd_slot_mapping *bankd_slotmap_by_client(struct bankd *bankd, const struct client_slot *client) +{ + struct bankd_slot_mapping *map; + + pthread_rwlock_rdlock(&bankd->slot_mappings_rwlock); + llist_for_each_entry(map, &bankd->slot_mappings, list) { + if (client_slot_equals(&map->client, client)) { + pthread_rwlock_unlock(&bankd->slot_mappings_rwlock); + return map; + } + } + pthread_rwlock_unlock(&bankd->slot_mappings_rwlock); + return NULL; +} + +/* thread-safe lookup of map by bank:slot */ +struct bankd_slot_mapping *bankd_slotmap_by_bank(struct bankd *bankd, const struct bank_slot *bank) +{ + struct bankd_slot_mapping *map; + + pthread_rwlock_rdlock(&bankd->slot_mappings_rwlock); + llist_for_each_entry(map, &bankd->slot_mappings, list) { + if (bank_slot_equals(&map->bank, bank)) { + pthread_rwlock_unlock(&bankd->slot_mappings_rwlock); + return map; + } + } + pthread_rwlock_unlock(&bankd->slot_mappings_rwlock); + return NULL; + +} + +/* thread-safe creating of a new bank<->client map */ +int bankd_slotmap_add(struct bankd *bankd, const struct bank_slot *bank, const struct client_slot *client) +{ + struct bankd_slot_mapping *map; + + /* We assume a single thread (main thread) will ever update the mappings, + * and hence we don't have any races by first grabbing + releasing the read + * lock twice before grabbing the writelock below */ + + map = bankd_slotmap_by_bank(bankd, bank); + if (map) { + fprintf(stderr, "BANKD %u:%u already in use, cannot add new map\n", + bank->bank_id, bank->slot_nr); + return -EBUSY; + } + + map = bankd_slotmap_by_client(bankd, client); + if (map) { + fprintf(stderr, "CLIENT %u:%u already in use, cannot add new map\n", + client->client_id, client->slot_nr); + return -EBUSY; + } + + /* allocate new mapping and add to list of mappings */ + map = talloc_zero(bankd, struct bankd_slot_mapping); + if (!map) + return -ENOMEM; + + map->bank = *bank; + map->client = *client; + + pthread_rwlock_wrlock(&bankd->slot_mappings_rwlock); + llist_add_tail(&map->list, &bankd->slot_mappings); + pthread_rwlock_unlock(&bankd->slot_mappings_rwlock); + + printf("Added Slot Map B(%u:%u) <-> C(%u:%u)\n", + map->client.client_id, map->client.slot_nr, map->bank.bank_id, map->bank.slot_nr); + + return 0; +} + +/* thread-safe removal of a bank<->client map */ +void bankd_slotmap_del(struct bankd *bankd, struct bankd_slot_mapping *map) +{ + printf("Deleting Slot Map B(%u:%u) <-> C(%u:%u)\n", + map->client.client_id, map->client.slot_nr, map->bank.bank_id, map->bank.slot_nr); + + pthread_rwlock_wrlock(&bankd->slot_mappings_rwlock); + llist_del(&map->list); + pthread_rwlock_unlock(&bankd->slot_mappings_rwlock); + + talloc_free(map); +} diff --git a/src/driver_pcsc.c b/src/driver_pcsc.c index d028e55..5102512 100644 --- a/src/driver_pcsc.c +++ b/src/driver_pcsc.c @@ -83,6 +83,7 @@ end: static int pcsc_reader_open_slot(struct card_reader_slot *slot) { +#if 0 struct osim_card_hdl *card; LONG rc; @@ -101,7 +102,8 @@ static int pcsc_reader_open_slot(struct card_reader_slot *slot) rh->card = card; end: - return NULL; +#endif + return -1; } diff --git a/src/main.c b/src/main.c index 977785c..67b7a41 100644 --- a/src/main.c +++ b/src/main.c @@ -36,7 +36,12 @@ #include "internal.h" +static void *g_ctx; +__thread void *talloc_asn1_ctx; + int main(int argc, char **argv) { - card_readers_probe(NULL); + g_ctx = talloc_named_const(NULL, 0, "main"); + talloc_asn1_ctx = talloc_named_const(g_ctx, 0, "asn1_context"); + card_readers_probe(g_ctx); } diff --git a/src/rspro_client.c b/src/rspro_client.c new file mode 100644 index 0000000..dcc9ed9 --- /dev/null +++ b/src/rspro_client.c @@ -0,0 +1,296 @@ +/* Generic Subscriber Update Protocol client */ + +/* (C) 2018 by Harald Welte + * All Rights Reserved + * + * 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 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 + +static void start_test_procedure(struct osmo_rspro_client *rsproc); + +static void rspro_client_send_ping(struct osmo_rspro_client *rsproc) +{ + struct msgb *msg = osmo_rspro_client_msgb_alloc(); + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = IPAC_MSGT_PING; + ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS); + ipa_client_conn_send(rsproc->link, msg); +} + +static int rspro_client_connect(struct osmo_rspro_client *rsproc) +{ + int rc; + + if (rsproc->is_connected) + return 0; + + if (osmo_timer_pending(&rsproc->connect_timer)) { + LOGP(DLRSPRO, LOGL_DEBUG, + "RSPRO connect: connect timer already running\n"); + osmo_timer_del(&rsproc->connect_timer); + } + + if (osmo_timer_pending(&rsproc->ping_timer)) { + LOGP(DLRSPRO, LOGL_DEBUG, + "RSPRO connect: ping timer already running\n"); + osmo_timer_del(&rsproc->ping_timer); + } + + if (ipa_client_conn_clear_queue(rsproc->link) > 0) + LOGP(DLRSPRO, LOGL_DEBUG, "RSPRO connect: discarded stored messages\n"); + + rc = ipa_client_conn_open(rsproc->link); + + if (rc >= 0) { + LOGP(DLRSPRO, LOGL_NOTICE, "RSPRO connecting to %s:%d\n", + rsproc->link->addr, rsproc->link->port); + return 0; + } + + LOGP(DLRSPRO, LOGL_ERROR, "RSPRO failed to connect to %s:%d: %s\n", + rsproc->link->addr, rsproc->link->port, strerror(-rc)); + + if (rc == -EBADF || rc == -ENOTSOCK || rc == -EAFNOSUPPORT || + rc == -EINVAL) + return rc; + + osmo_timer_schedule(&rsproc->connect_timer, + OSMO_RSPRO_CLIENT_RECONNECT_INTERVAL, 0); + + LOGP(DLRSPRO, LOGL_INFO, "Scheduled timer to retry RSPRO connect to %s:%d\n", + rsproc->link->addr, rsproc->link->port); + + return 0; +} + +static void connect_timer_cb(void *rsproc_) +{ + struct osmo_rspro_client *rsproc = rsproc_; + + if (rsproc->is_connected) + return; + + rspro_client_connect(rsproc); +} + +static void client_send(struct osmo_rspro_client *rsproc, int proto_ext, + struct msgb *msg_tx) +{ + ipa_prepend_header_ext(msg_tx, proto_ext); + ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO); + ipa_client_conn_send(rsproc->link, msg_tx); + /* msg_tx is now queued and will be freed. */ +} + +static void rspro_client_updown_cb(struct ipa_client_conn *link, int up) +{ + struct osmo_rspro_client *rsproc = link->data; + + LOGP(DLRSPRO, LOGL_INFO, "RSPRO link to %s:%d %s\n", + link->addr, link->port, up ? "UP" : "DOWN"); + + rsproc->is_connected = up; + + if (up) { + start_test_procedure(rsproc); + osmo_timer_del(&rsproc->connect_timer); + } else { + osmo_timer_del(&rsproc->ping_timer); + + osmo_timer_schedule(&rsproc->connect_timer, + OSMO_RSPRO_CLIENT_RECONNECT_INTERVAL, 0); + } +} + +static int rspro_client_read_cb(struct ipa_client_conn *link, struct msgb *msg) +{ + struct ipaccess_head *hh = (struct ipaccess_head *) msg->data; + struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) msgb_l2(msg); + struct osmo_rspro_client *rsproc = (struct osmo_rspro_client *)link->data; + int rc; + struct ipaccess_unit ipa_dev = { + /* see rspro_client_create() on const vs non-const */ + .unit_name = (char*)rsproc->unit_name, + }; + + OSMO_ASSERT(ipa_dev.unit_name); + + msg->l2h = &hh->data[0]; + + rc = ipaccess_bts_handle_ccm(link, &ipa_dev, msg); + + if (rc < 0) { + LOGP(DLRSPRO, LOGL_NOTICE, + "RSPRO received an invalid IPA/CCM message from %s:%d\n", + link->addr, link->port); + /* Link has been closed */ + rsproc->is_connected = 0; + msgb_free(msg); + return -1; + } + + if (rc == 1) { + uint8_t msg_type = *(msg->l2h); + /* CCM message */ + if (msg_type == IPAC_MSGT_PONG) { + LOGP(DLRSPRO, LOGL_DEBUG, "RSPRO receiving PONG\n"); + rsproc->got_ipa_pong = 1; + } + + msgb_free(msg); + return 0; + } + + if (hh->proto != IPAC_PROTO_OSMO) + goto invalid; + + if (!he || msgb_l2len(msg) < sizeof(*he)) + goto invalid; + + msg->l2h = &he->data[0]; + + if (he->proto == IPAC_PROTO_EXT_RSPRO) { + OSMO_ASSERT(rsproc->read_cb != NULL); + rsproc->read_cb(rsproc, msg); + /* expecting read_cb() to free msg */ + } else + goto invalid; + + return 0; + +invalid: + LOGP(DLRSPRO, LOGL_NOTICE, + "RSPRO received an invalid IPA message from %s:%d, size = %d\n", + link->addr, link->port, msgb_length(msg)); + + msgb_free(msg); + return -1; +} + +static void ping_timer_cb(void *rsproc_) +{ + struct osmo_rspro_client *rsproc = rsproc_; + + LOGP(DLRSPRO, LOGL_INFO, "RSPRO ping callback (%s, %s PONG)\n", + rsproc->is_connected ? "connected" : "not connected", + rsproc->got_ipa_pong ? "got" : "didn't get"); + + if (rsproc->got_ipa_pong) { + start_test_procedure(rsproc); + return; + } + + LOGP(DLRSPRO, LOGL_NOTICE, "RSPRO ping timed out, reconnecting\n"); + ipa_client_conn_close(rsproc->link); + rsproc->is_connected = 0; + + rspro_client_connect(rsproc); +} + +static void start_test_procedure(struct osmo_rspro_client *rsproc) +{ + osmo_timer_setup(&rsproc->ping_timer, ping_timer_cb, rsproc); + + rsproc->got_ipa_pong = 0; + osmo_timer_schedule(&rsproc->ping_timer, OSMO_RSPRO_CLIENT_PING_INTERVAL, 0); + LOGP(DLRSPRO, LOGL_DEBUG, "RSPRO sending PING\n"); + rspro_client_send_ping(rsproc); +} + +struct osmo_rspro_client *osmo_rspro_client_create(void *talloc_ctx, + const char *unit_name, + const char *ip_addr, + unsigned int tcp_port, + osmo_rspro_client_read_cb_t read_cb) +{ + struct osmo_rspro_client *rsproc; + int rc; + + rsproc = talloc_zero(talloc_ctx, struct osmo_rspro_client); + OSMO_ASSERT(rsproc); + + /* struct ipaccess_unit has a non-const unit_name, so let's copy to be + * able to have a non-const unit_name here as well. To not taint the + * public rspro_client API, let's store it in a const char* anyway. */ + rsproc->unit_name = talloc_strdup(rsproc, unit_name); + OSMO_ASSERT(rsproc->unit_name); + + rsproc->link = ipa_client_conn_create(rsproc, + /* no e1inp */ NULL, + 0, + ip_addr, tcp_port, + rspro_client_updown_cb, + rspro_client_read_cb, + /* default write_cb */ NULL, + rsproc); + if (!rsproc->link) + goto failed; + + osmo_timer_setup(&rsproc->connect_timer, connect_timer_cb, rsproc); + + rc = rspro_client_connect(rsproc); + if (rc < 0) + goto failed; + + rsproc->read_cb = read_cb; + + return rsproc; + +failed: + osmo_rspro_client_destroy(rsproc); + return NULL; +} + +void osmo_rspro_client_destroy(struct osmo_rspro_client *rsproc) +{ + osmo_timer_del(&rsproc->connect_timer); + osmo_timer_del(&rsproc->ping_timer); + + if (rsproc->link) { + ipa_client_conn_close(rsproc->link); + ipa_client_conn_destroy(rsproc->link); + rsproc->link = NULL; + } + talloc_free(rsproc); +} + +int osmo_rspro_client_send(struct osmo_rspro_client *rsproc, struct msgb *msg) +{ + if (!rsproc || !rsproc->is_connected) { + LOGP(DLRSPRO, LOGL_ERROR, "RSPRO not connected, unable to send %s\n", msgb_hexdump(msg)); + msgb_free(msg); + return -ENOTCONN; + } + + client_send(rsproc, IPAC_PROTO_EXT_RSPRO, msg); + + return 0; +} + +struct msgb *osmo_rspro_client_msgb_alloc(void) +{ + return msgb_alloc_headroom(4000, 64, __func__); +} diff --git a/src/rspro_util.c b/src/rspro_util.c index 7a53859..7ff93c3 100644 --- a/src/rspro_util.c +++ b/src/rspro_util.c @@ -152,5 +152,3 @@ RsproPDU_t *rspro_gen_SetAtrReq(uint16_t client_id, uint16_t slot_nr, const uint return pdu; } - -