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: I5ad5a1a4918b8eacdaeb7e709ff05dc056346752laforge/bankd-fsm-mistake
parent
e224912d4a
commit
77911b0091
@ -0,0 +1,59 @@ |
||||
/* Remote SIM Protocol client */ |
||||
|
||||
/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
#pragma once |
||||
|
||||
#include <osmocom/core/timer.h> |
||||
|
||||
/* 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); |
||||
|
@ -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
|
||||
|
@ -0,0 +1,118 @@ |
||||
#pragma once |
||||
|
||||
#include <stdbool.h> |
||||
#include <sys/socket.h> |
||||
#include <arpa/inet.h> |
||||
#include <netinet/in.h> |
||||
|
||||
#include <pthread.h> |
||||
|
||||
#include <wintypes.h> |
||||
#include <winscard.h> |
||||
|
||||
#include <osmocom/core/linuxlist.h> |
||||
|
||||
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; |
||||
}; |
@ -0,0 +1,285 @@ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <stdint.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <pthread.h> |
||||
|
||||
#include <wintypes.h> |
||||
#include <winscard.h> |
||||
#include <pcsclite.h> |
||||
|
||||
#include <osmocom/core/linuxlist.h> |
||||
|
||||
#include <osmocom/gsm/ipa.h> |
||||
#include <osmocom/gsm/protocol/ipaccess.h> |
||||
|
||||
#include <asn1c/asn_application.h> |
||||
#include <osmocom/rspro/RsproPDU.h> |
||||
|
||||
#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); |
||||
} |
@ -0,0 +1,100 @@ |
||||
|
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <errno.h> |
||||
|
||||
#include <pthread.h> |
||||
|
||||
#include <talloc.h> |
||||
|
||||
#include <osmocom/core/linuxlist.h> |
||||
|
||||
#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); |
||||
} |
@ -0,0 +1,296 @@ |
||||
/* Generic Subscriber Update Protocol client */ |
||||
|
||||
/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
#include <osmocom/rspro/rspro_client.h> |
||||
|
||||
#include <osmocom/abis/ipa.h> |
||||
#include <osmocom/gsm/protocol/ipaccess.h> |
||||
#include <osmocom/core/msgb.h> |
||||
#include <osmocom/core/logging.h> |
||||
|
||||
#include <errno.h> |
||||
#include <string.h> |
||||
|
||||
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__); |
||||
} |
Loading…
Reference in new issue