/* * OsmocomBB <-> SDR connection bridge * * (C) 2016-2022 by Vadim Yanitskiy * Contributions by sysmocom - s.f.m.c. GmbH * * 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 General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define COPYRIGHT \ "Copyright (C) 2016-2022 by Vadim Yanitskiy \n" \ "Contributions by sysmocom - s.f.m.c. GmbH \n" \ "License GPLv2+: GNU GPL version 2 or later " \ "\n" \ "This is free software: you are free to change and redistribute it.\n" \ "There is NO WARRANTY, to the extent permitted by law.\n\n" static struct { const char *debug_mask; int daemonize; int quit; /* L1CTL specific */ unsigned int max_clients; const char *bind_socket; /* TRX specific */ const char *trx_bind_ip; const char *trx_remote_ip; uint16_t trx_base_port; uint32_t trx_fn_advance; /* GSMTAP specific */ struct gsmtap_inst *gsmtap; const char *gsmtap_ip; } app_data = { .max_clients = 1, /* only one L1CTL client by default */ .bind_socket = "/tmp/osmocom_l2", .trx_remote_ip = "127.0.0.1", .trx_bind_ip = "0.0.0.0", .trx_base_port = 6700, .trx_fn_advance = 3, }; static void *tall_trxcon_ctx = NULL; static void trxcon_gsmtap_send(const struct l1sched_lchan_desc *lchan_desc, uint32_t fn, uint8_t tn, uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr, const uint8_t *data, size_t data_len) { /* GSMTAP logging may be not enabled */ if (app_data.gsmtap == NULL) return; /* Omit frames with unknown channel type */ if (lchan_desc->gsmtap_chan_type == GSMTAP_CHANNEL_UNKNOWN) return; /* TODO: distinguish GSMTAP_CHANNEL_PCH and GSMTAP_CHANNEL_AGCH */ gsmtap_send(app_data.gsmtap, band_arfcn, tn, lchan_desc->gsmtap_chan_type, lchan_desc->ss_nr, fn, signal_dbm, snr, data, data_len); } /* External L1 API for the scheduler */ int l1sched_handle_config_req(struct l1sched_state *sched, const struct l1sched_config_req *cr) { struct trxcon_inst *trxcon = sched->priv; switch (cr->type) { case L1SCHED_CFG_PCHAN_COMB: return trx_if_cmd_setslot(trxcon->phyif, cr->pchan_comb.tn, cr->pchan_comb.pchan); default: LOGPFSML(trxcon->fi, LOGL_ERROR, "Unhandled config request (type 0x%02x)\n", cr->type); return -ENODEV; } } int l1sched_handle_burst_req(struct l1sched_state *sched, const struct l1sched_burst_req *br) { struct trxcon_inst *trxcon = sched->priv; return trx_if_tx_burst(trxcon->phyif, br); } /* External L2 API for the scheduler */ int l1sched_handle_data_ind(struct l1sched_lchan_state *lchan, const uint8_t *data, size_t data_len, int n_errors, int n_bits_total, enum l1sched_data_type dt) { const struct l1sched_meas_set *meas = &lchan->meas_avg; const struct l1sched_lchan_desc *lchan_desc; struct l1sched_state *sched = lchan->ts->sched; struct trxcon_inst *trxcon = sched->priv; int rc; lchan_desc = &l1sched_lchan_desc[lchan->type]; struct trxcon_param_rx_traffic_data_ind ind = { .chan_nr = lchan_desc->chan_nr | lchan->ts->index, .link_id = lchan_desc->link_id, .frame_nr = meas->fn, .toa256 = meas->toa256, .rssi = meas->rssi, .n_errors = n_errors, .n_bits_total = n_bits_total, .data_len = data_len, .data = data, }; switch (dt) { case L1SCHED_DT_TRAFFIC: case L1SCHED_DT_PACKET_DATA: rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_TRAFFIC_IND, &ind); break; case L1SCHED_DT_SIGNALING: rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_DATA_IND, &ind); break; case L1SCHED_DT_OTHER: if (lchan->type == L1SCHED_SCH) { if (trxcon->fi->state != TRXCON_ST_FBSB_SEARCH) return 0; rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FBSB_SEARCH_RES, NULL); break; } /* fall through */ default: LOGPFSML(trxcon->fi, LOGL_ERROR, "Unhandled L2 DATA.ind (type 0x%02x)\n", dt); return -ENODEV; } if (data != NULL && data_len > 0) { trxcon_gsmtap_send(lchan_desc, meas->fn, lchan->ts->index, trxcon->l1p.band_arfcn, meas->rssi, 0, data, data_len); } return rc; } int l1sched_handle_data_cnf(struct l1sched_lchan_state *lchan, uint32_t fn, enum l1sched_data_type dt) { const struct l1sched_lchan_desc *lchan_desc; struct l1sched_state *sched = lchan->ts->sched; struct trxcon_inst *trxcon = sched->priv; struct l1ctl_info_dl dl_hdr; const uint8_t *data; uint8_t ra_buf[2]; size_t data_len; int rc; lchan_desc = &l1sched_lchan_desc[lchan->type]; dl_hdr = (struct l1ctl_info_dl) { .chan_nr = lchan_desc->chan_nr | lchan->ts->index, .link_id = lchan_desc->link_id, .frame_nr = htonl(fn), .band_arfcn = htons(trxcon->l1p.band_arfcn), }; switch (dt) { case L1SCHED_DT_TRAFFIC: case L1SCHED_DT_PACKET_DATA: rc = l1ctl_tx_dt_conf(trxcon->l2if, &dl_hdr, true); data_len = lchan->prim->payload_len; data = lchan->prim->payload; break; case L1SCHED_DT_SIGNALING: rc = l1ctl_tx_dt_conf(trxcon->l2if, &dl_hdr, false); data_len = lchan->prim->payload_len; data = lchan->prim->payload; break; case L1SCHED_DT_OTHER: if (L1SCHED_PRIM_IS_RACH(lchan->prim)) { const struct l1sched_ts_prim_rach *rach; rach = (struct l1sched_ts_prim_rach *)lchan->prim->payload; rc = l1ctl_tx_rach_conf(trxcon->l2if, trxcon->l1p.band_arfcn, fn); if (lchan->prim->type == L1SCHED_PRIM_RACH11) { ra_buf[0] = (uint8_t)(rach->ra >> 3); ra_buf[1] = (uint8_t)(rach->ra & 0x07); data = &ra_buf[0]; data_len = 2; } else { ra_buf[0] = (uint8_t)(rach->ra); data = &ra_buf[0]; data_len = 1; } break; } /* fall through */ default: LOGPFSML(trxcon->fi, LOGL_ERROR, "Unhandled L2 DATA.cnf (type 0x%02x)\n", dt); return -ENODEV; } trxcon_gsmtap_send(lchan_desc, fn, lchan->ts->index, trxcon->l1p.band_arfcn | ARFCN_UPLINK, 0, 0, data, data_len); return rc; } struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id) { struct trxcon_inst *trxcon; trxcon = talloc_zero(ctx, struct trxcon_inst); OSMO_ASSERT(trxcon != NULL); trxcon->fi = osmo_fsm_inst_alloc(&trxcon_fsm_def, tall_trxcon_ctx, trxcon, LOGL_DEBUG, NULL); OSMO_ASSERT(trxcon->fi != NULL); osmo_fsm_inst_update_id_f(trxcon->fi, "%u", id); trxcon->id = id; /* Logging context to be used by both l1ctl and l1sched modules */ trxcon->log_prefix = talloc_asprintf(trxcon, "%s: ", osmo_fsm_inst_name(trxcon->fi)); /* Init transceiver interface */ trxcon->phyif = trx_if_open(trxcon, app_data.trx_bind_ip, app_data.trx_remote_ip, app_data.trx_base_port); if (trxcon->phyif == NULL) { trxcon_inst_free(trxcon); return NULL; } /* Init scheduler */ const struct l1sched_cfg sched_cfg = { .fn_advance = app_data.trx_fn_advance, .log_prefix = trxcon->log_prefix, }; trxcon->sched = l1sched_alloc(trxcon, &sched_cfg, trxcon); if (trxcon->sched == NULL) { trxcon_inst_free(trxcon); return NULL; } return trxcon; } void trxcon_inst_free(struct trxcon_inst *trxcon) { /* Shutdown the scheduler */ if (trxcon->sched != NULL) l1sched_free(trxcon->sched); /* Close active connections */ if (trxcon->l2if != NULL) l1ctl_client_conn_close(trxcon->l2if); if (trxcon->phyif != NULL) trx_if_close(trxcon->phyif); if (trxcon->fi != NULL) osmo_fsm_inst_free(trxcon->fi); talloc_free(trxcon); } static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c) { struct trxcon_inst *trxcon; trxcon = trxcon_inst_alloc(l1c, l1c->id); if (trxcon == NULL) { l1ctl_client_conn_close(l1c); return; } l1c->log_prefix = talloc_strdup(l1c, trxcon->log_prefix); l1c->priv = trxcon; trxcon->l2if = l1c; } static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) { struct trxcon_inst *trxcon = l1c->priv; if (trxcon == NULL) return; osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL); /* l2if is free()ed by the caller */ trxcon->l2if = NULL; trxcon_inst_free(trxcon); } static void print_usage(const char *app) { printf("Usage: %s\n", app); } static void print_help(void) { printf(" Some help...\n"); printf(" -h --help this text\n"); printf(" -d --debug Change debug flags (e.g. DL1C:DSCH)\n"); printf(" -b --trx-bind TRX bind IP address (default 0.0.0.0)\n"); printf(" -i --trx-remote TRX remote IP address (default 127.0.0.1)\n"); printf(" -p --trx-port Base port of TRX instance (default 6700)\n"); printf(" -f --trx-advance Uplink burst scheduling advance (default 3)\n"); printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\n"); printf(" -g --gsmtap-ip The destination IP used for GSMTAP (disabled by default)\n"); printf(" -C --max-clients Maximum number of L1CTL connections (default 1)\n"); printf(" -D --daemonize Run as daemon\n"); } static void handle_options(int argc, char **argv) { while (1) { int option_index = 0, c; static struct option long_options[] = { {"help", 0, 0, 'h'}, {"debug", 1, 0, 'd'}, {"socket", 1, 0, 's'}, {"trx-bind", 1, 0, 'b'}, /* NOTE: 'trx-ip' is now an alias for 'trx-remote' * due to backward compatibility reasons! */ {"trx-ip", 1, 0, 'i'}, {"trx-remote", 1, 0, 'i'}, {"trx-port", 1, 0, 'p'}, {"trx-advance", 1, 0, 'f'}, {"gsmtap-ip", 1, 0, 'g'}, {"max-clients", 1, 0, 'C'}, {"daemonize", 0, 0, 'D'}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, "d:b:i:p:f:s:g:C:Dh", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': print_usage(argv[0]); print_help(); exit(0); break; case 'd': app_data.debug_mask = optarg; break; case 'b': app_data.trx_bind_ip = optarg; break; case 'i': app_data.trx_remote_ip = optarg; break; case 'p': app_data.trx_base_port = atoi(optarg); break; case 'f': app_data.trx_fn_advance = atoi(optarg); break; case 's': app_data.bind_socket = optarg; break; case 'g': app_data.gsmtap_ip = optarg; break; case 'C': app_data.max_clients = atoi(optarg); break; case 'D': app_data.daemonize = 1; break; default: break; } } } static void signal_handler(int signum) { fprintf(stderr, "signal %u received\n", signum); switch (signum) { case SIGINT: app_data.quit++; break; case SIGABRT: /* in case of abort, we want to obtain a talloc report and * then run default SIGABRT handler, who will generate coredump * and abort the process. abort() should do this for us after we * return, but program wouldn't exit if an external SIGABRT is * received. */ talloc_report_full(tall_trxcon_ctx, stderr); signal(SIGABRT, SIG_DFL); raise(SIGABRT); break; case SIGUSR1: case SIGUSR2: talloc_report_full(tall_trxcon_ctx, stderr); break; default: break; } } int main(int argc, char **argv) { struct l1ctl_server_cfg server_cfg; struct l1ctl_server *server = NULL; int rc = 0; printf("%s", COPYRIGHT); handle_options(argc, argv); /* Track the use of talloc NULL memory contexts */ talloc_enable_null_tracking(); /* Init talloc memory management system */ tall_trxcon_ctx = talloc_init("trxcon context"); msgb_talloc_ctx_init(tall_trxcon_ctx, 0); /* Setup signal handlers */ signal(SIGINT, &signal_handler); signal(SIGABRT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); osmo_init_ignore_signals(); /* Init logging system */ trx_log_init(tall_trxcon_ctx, app_data.debug_mask); l1sched_logging_init(DSCH, DSCHD); /* Configure pretty logging */ log_set_print_extended_timestamp(osmo_stderr_target, 1); log_set_print_category_hex(osmo_stderr_target, 0); log_set_print_category(osmo_stderr_target, 1); log_set_print_level(osmo_stderr_target, 1); log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); osmo_fsm_log_timeouts(true); /* Optional GSMTAP */ if (app_data.gsmtap_ip != NULL) { app_data.gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1); if (!app_data.gsmtap) { LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP\n"); goto exit; } /* Suppress ICMP "destination unreachable" errors */ gsmtap_source_add_sink(app_data.gsmtap); } /* Start the L1CTL server */ server_cfg = (struct l1ctl_server_cfg) { .sock_path = app_data.bind_socket, .num_clients_max = app_data.max_clients, .conn_read_cb = &l1ctl_rx_cb, .conn_accept_cb = &l1ctl_conn_accept_cb, .conn_close_cb = &l1ctl_conn_close_cb, }; server = l1ctl_server_alloc(tall_trxcon_ctx, &server_cfg); if (server == NULL) { rc = EXIT_FAILURE; goto exit; } LOGP(DAPP, LOGL_NOTICE, "Init complete\n"); if (app_data.daemonize) { rc = osmo_daemonize(); if (rc < 0) { perror("Error during daemonize"); goto exit; } } /* Initialize pseudo-random generator */ srand(time(NULL)); while (!app_data.quit) osmo_select_main(0); exit: if (server != NULL) l1ctl_server_free(server); /* Deinitialize logging */ log_fini(); /** * Print report for the root talloc context in order * to be able to find and fix potential memory leaks. */ talloc_report_full(tall_trxcon_ctx, stderr); talloc_free(tall_trxcon_ctx); /* Make both Valgrind and ASAN happy */ talloc_report_full(NULL, stderr); talloc_disable_null_tracking(); return rc; }