aboutsummaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/Makefile.am38
-rw-r--r--utils/conv_codes_gsm.py130
-rw-r--r--utils/conv_gen.py6
-rw-r--r--utils/gsmtap-logsend.c139
-rwxr-xr-x[-rw-r--r--]utils/gsmtap_logread.py0
-rw-r--r--utils/osmo-aka-verify.c249
-rw-r--r--utils/osmo-arfcn.c13
-rw-r--r--utils/osmo-auc-gen.c87
-rw-r--r--utils/osmo-config-merge.c4
-rw-r--r--utils/osmo-ns-dummy-vty.c325
-rw-r--r--utils/osmo-ns-dummy.c316
-rw-r--r--utils/osmo-sim-test.c470
-rw-r--r--utils/osmo-stat-dummy/Makefile.am10
-rw-r--r--utils/osmo-stat-dummy/README.md12
-rw-r--r--utils/osmo-stat-dummy/osmo-stat-dummy.c335
-rw-r--r--utils/osmo-stat-dummy/osmo-stat-dummy.cfg40
-rw-r--r--utils/osmo-stat-dummy/osmo-stat-dummy.html59
17 files changed, 2101 insertions, 132 deletions
diff --git a/utils/Makefile.am b/utils/Makefile.am
index fb791906..3ec71ea3 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -1,24 +1,46 @@
-if ENABLE_UTILITIES
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
-AM_CFLAGS = -Wall
-LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
+bin_PROGRAMS =
+noinst_PROGRAMS =
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
+LDADD = $(top_builddir)/src/core/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la $(PTHREAD_LIBS)
+
+if ENABLE_UTILITIES
EXTRA_DIST = conv_gen.py conv_codes_gsm.py
-bin_PROGRAMS = osmo-arfcn osmo-auc-gen osmo-config-merge
+bin_PROGRAMS += osmo-arfcn osmo-auc-gen osmo-config-merge osmo-aka-verify osmo-gsmtap-logsend
osmo_arfcn_SOURCES = osmo-arfcn.c
osmo_auc_gen_SOURCES = osmo-auc-gen.c
+osmo_aka_verify_SOURCES = osmo-aka-verify.c
+
+osmo_gsmtap_logsend_SOURCES = gsmtap-logsend.c
+
osmo_config_merge_SOURCES = osmo-config-merge.c
osmo_config_merge_LDADD = $(LDADD) $(TALLOC_LIBS)
-osmo_config_merge_CFLAGS = $(TALLOC_CFLAGS)
if ENABLE_PCSC
-noinst_PROGRAMS = osmo-sim-test
+noinst_PROGRAMS += osmo-sim-test
osmo_sim_test_SOURCES = osmo-sim-test.c
osmo_sim_test_LDADD = $(LDADD) $(top_builddir)/src/sim/libosmosim.la $(PCSC_LIBS)
-osmo_sim_test_CFLAGS = $(PCSC_CFLAGS)
+osmo_sim_test_CFLAGS = $(AM_CFLAGS) $(PCSC_CFLAGS)
endif
endif
+
+if ENABLE_EXT_TESTS
+SUBDIRS = \
+ osmo-stat-dummy \
+ $(NULL)
+endif
+
+if ENABLE_GB
+noinst_PROGRAMS += osmo-ns-dummy
+osmo_ns_dummy_SOURCES = osmo-ns-dummy.c osmo-ns-dummy-vty.c
+osmo_ns_dummy_LDADD = $(LDADD) $(TALLOC_LIBS) \
+ $(top_builddir)/src/gb/libosmogb.la \
+ $(top_builddir)/src/vty/libosmovty.la \
+ $(top_builddir)/src/ctrl/libosmoctrl.la \
+ $(NULL)
+endif
diff --git a/utils/conv_codes_gsm.py b/utils/conv_codes_gsm.py
index ec776605..7f621b8d 100644
--- a/utils/conv_codes_gsm.py
+++ b/utils/conv_codes_gsm.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from conv_gen import ConvolutionalCode
@@ -42,6 +42,111 @@ conv_codes = [
]
),
+ # TCH/F2.4 definition
+ ConvolutionalCode(
+ 72,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_f24",
+ description = [
+ "TCH/F2.4 convolutional code:",
+ "72 bits blocks, rate 1/6, k = 5",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ ]
+ ),
+
+ # TCH/H2.4 definition
+ ConvolutionalCode(
+ 72,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_h24",
+ description = [
+ "TCH/H2.4 convolutional code:",
+ "72 bits blocks, rate 1/3, k = 5",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ ]
+ ),
+
+ # TCH/F4.8 definition
+ ConvolutionalCode(
+ 148,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_f48",
+ description = [
+ "TCH/F4.8 convolutional code:",
+ "148 bits blocks, rate 1/3, k = 5",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ ]
+ ),
+
+ # TCH/F9.6 definition
+ ConvolutionalCode(
+ 240,
+ shared_polys["xcch"],
+ puncture = [
+ 11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176,
+ 191, 206, 221, 236, 251, 266, 281, 296, 311, 326, 341, 356,
+ 371, 386, 401, 416, 431, 446, 461, 476, -1
+ ],
+ name = "tch_f96",
+ description = [
+ "TCH/F9.6 convolutional code:",
+ "240 bits blocks, rate 1/2, k = 5",
+ "G0 = 1 + D3 + D4",
+ "G1 = 1 + D + D3 + D4",
+ ]
+ ),
+
+ # TCH/F14.4 definition
+ ConvolutionalCode(
+ 290,
+ shared_polys["xcch"],
+ puncture = [
+ 1, 6, 11, 15, 19, 24, 29, 33, 37, 42, 47, 51,
+ 55, 60, 65, 69, 73, 78, 83, 87, 91, 96, 101, 105,
+ 109, 114, 119, 123, 127, 132, 137, 141, 145, 150, 155, 159,
+ 163, 168, 173, 177, 181, 186, 191, 195, 199, 204, 209, 213,
+ 217, 222, 227, 231, 235, 240, 245, 249, 253, 258, 263, 267,
+ 271, 276, 281, 285, 289, 294, 299, 303, 307, 312, 317, 321,
+ 325, 330, 335, 339, 343, 348, 353, 357, 361, 366, 371, 375,
+ 379, 384, 389, 393, 397, 402, 407, 411, 415, 420, 425, 429,
+ 433, 438, 443, 447, 451, 456, 461, 465, 469, 474, 479, 483,
+ 487, 492, 497, 501, 505, 510, 515, 519, 523, 528, 533, 537,
+ 541, 546, 551, 555, 559, 564, 569, 573, 577, 582, 584, 587,
+ -1
+ ],
+ name = "tch_f144",
+ description = [
+ "TCH/F14.4 convolutional code:",
+ "290 bits blocks, rate 1/2, k = 5",
+ "G0 = 1 + D3 + D4",
+ "G1 = 1 + D + D3 + D4",
+ ]
+ ),
+
# RACH definition
ConvolutionalCode(
14,
@@ -50,11 +155,11 @@ conv_codes = [
description = ["RACH convolutional code"]
),
- # Extended RACH definition from 3GPP TS 45.003 §5.3.2
+ # Extended RACH definition from 3GPP TS 45.003 §5.3.2
ConvolutionalCode(
17,
shared_polys["xcch"],
- puncture = [ 0, 2, 5, 37, 39, 41, -1 ],
+ puncture = [ 0, 2, 5, 37, 39, 41, -1 ],
name = "rach_ext",
description = ["Extended RACH (11 bit) convolutional code"]
),
@@ -522,6 +627,25 @@ conv_codes = [
description = ["TCH/AHS 4.75 kbits convolutional code"]
),
+ # TCH_AXS SID UPDATE definition
+ ConvolutionalCode(
+ 49,
+ [
+ ( G1, G3 ),
+ ( G2, G3 ),
+ ( 1, 1 ),
+ ( 1, 1 ),
+ ],
+ name = 'tch_axs_sid_update',
+ description = [
+ "TCH/AFS and TCH/AHS SID UPDATE convolutional code:",
+ "G1/G3 = 1 + D + D3 + D4 / 1 + D + D2 + D3 + D4",
+ "G2/G3 = 1 + D2 + D4 / 1 + D + D2 + D3 + D4",
+ "G3/G3 = 1",
+ "G3/G3 = 1",
+ ]
+ ),
+
# EDGE MCS1_DL_HDR definition
ConvolutionalCode(
36,
diff --git a/utils/conv_gen.py b/utils/conv_gen.py
index 06283d87..27ffe625 100644
--- a/utils/conv_gen.py
+++ b/utils/conv_gen.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
mod_license = """
/*
@@ -16,10 +16,6 @@ mod_license = """
* 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, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
"""
diff --git a/utils/gsmtap-logsend.c b/utils/gsmtap-logsend.c
new file mode 100644
index 00000000..97a838b5
--- /dev/null
+++ b/utils/gsmtap-logsend.c
@@ -0,0 +1,139 @@
+/* Small program to read an input file / stdin and send each line via GSMTAP logging */
+/* (C) 2023 by Harald Welte <laforge@osmocom.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 General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <sys/uio.h>
+#include <getopt.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+
+static char *proc_name = "gsmtap-logsend";
+static char *subsys_name = "unknown";
+static char *dest_host = "localhost";
+static int dest_port = GSMTAP_UDP_PORT;
+
+static void help(void)
+{
+ printf("osmo-gsmtap-logsend Usage:\n"
+ "\t[ -r DESTADDR ] [ -p PORTNR ] [ -n PROC_NAME ] [ -s SUBSYS ] [ INFILE ]\n"
+ "\n"
+ " -a --remote-address HOSTNAME Destination IP destination address (default: localhost)\n"
+ " -p --remote-port PORTNR Destination UDP Port number (default: 4729)\n"
+ " -n --process-name PROC_NAME Process name to include in GSMTAP LOG header\n"
+ " -s --subsys-name SUBSYS Subsystem name to include in GSMTAP LOG header\n"
+ " -h --help This help message\n"
+ );
+}
+
+int main(int argc, char **argv)
+{
+ char buf[1024];
+ int gsmtap_fd;
+ FILE *infile;
+ char *line;
+ int rc;
+
+ while (1) {
+ static const struct option long_options[] = {
+ { "remote-address", 1, 0, 'a' },
+ { "remote-port", 1, 0, 'p' },
+ { "process-name", 1, 0, 'n' },
+ { "subsys-name", 1, 0, 's' },
+ { "help", 0, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+ int c, option_index;
+
+ c = getopt_long(argc, argv, "a:p:n:s:h", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ dest_host = optarg;
+ break;
+ case 'p':
+ dest_port = atoi(optarg);
+ break;
+ case 'n':
+ proc_name = optarg;
+ break;
+ case 's':
+ subsys_name = optarg;
+ break;
+ case 'h':
+ help();
+ exit(0);
+ break;
+ default:
+ help();
+ exit(1);
+ }
+ }
+
+ if (argc <= optind) {
+ infile = stdin;
+ } else {
+ infile = fopen(argv[optind], "r");
+ if (!infile) {
+ fprintf(stderr, "Unable to open %s: %s\n", argv[optind], strerror(errno));
+ exit(2);
+ }
+ }
+
+ gsmtap_fd = gsmtap_source_init_fd(dest_host, dest_port);
+ if (gsmtap_fd < 0) {
+ fprintf(stderr, "Unable to create GSMTAP soicket: %s\n", strerror(errno));
+ exit(2);
+ }
+
+ /* prepare all the data structures that don't change for each line */
+ struct {
+ struct gsmtap_hdr gsmtap;
+ struct gsmtap_osmocore_log_hdr log;
+ } __attribute__ ((packed)) hdr;
+ struct timeval tv;
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.gsmtap.version = GSMTAP_VERSION;
+ hdr.gsmtap.hdr_len = sizeof(hdr.gsmtap)/4;
+ hdr.gsmtap.type = GSMTAP_TYPE_OSMOCORE_LOG;
+
+ OSMO_STRLCPY_ARRAY(hdr.log.proc_name, proc_name);
+ OSMO_STRLCPY_ARRAY(hdr.log.subsys, subsys_name);
+ hdr.log.level = LOGL_INFO;
+
+ while ((line = fgets(buf, sizeof(buf), infile))) {
+ struct iovec iov[2] = {
+ { .iov_base = &hdr, .iov_len = sizeof(hdr) },
+ { .iov_base = buf, .iov_len = strlen(line) + 1 },
+ };
+ osmo_gettimeofday(&tv, NULL);
+ hdr.log.ts.sec = osmo_htonl(tv.tv_sec);
+ hdr.log.ts.usec = osmo_htonl(tv.tv_usec);
+
+ rc = writev(gsmtap_fd, iov, ARRAY_SIZE(iov));
+ if (rc <= 0) {
+ fprintf(stderr, "Short write on GSMTAP socket: %d (%s)\n", rc, strerror(errno));
+ exit(1);
+ }
+ }
+}
diff --git a/utils/gsmtap_logread.py b/utils/gsmtap_logread.py
index a29f1491..a29f1491 100644..100755
--- a/utils/gsmtap_logread.py
+++ b/utils/gsmtap_logread.py
diff --git a/utils/osmo-aka-verify.c b/utils/osmo-aka-verify.c
new file mode 100644
index 00000000..f23c349b
--- /dev/null
+++ b/utils/osmo-aka-verify.c
@@ -0,0 +1,249 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <getopt.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/bit64gen.h>
+
+/* Utility program for implementing the SIM-side procedures of 3GPP Authentication and Key Agreement
+ * as specified by 3GPP TS 33.102 Section 6.3.3
+ *
+ * (C) 2021 by Harald Welte <laforge@gnumonks.org>
+ * Milenage library code used from libosmocore, which inherited it from wpa_supplicant
+ */
+
+/* FIXME: libosmogsm implements those, but doesn't declare them */
+int milenage_f1(const uint8_t *opc, const uint8_t *k, const uint8_t *_rand,
+ const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t *mac_s);
+int milenage_f2345(const uint8_t *opc, const uint8_t *k, const uint8_t *_rand,
+ uint8_t *res, uint8_t *ck, uint8_t *ik, uint8_t *ak, uint8_t *akstar);
+int milenage_opc_gen(uint8_t *opc, const uint8_t *k, const uint8_t *op);
+
+static int milenage_check(const uint8_t *opc, const uint8_t *k, const uint8_t *sqn, const uint8_t *_rand,
+ const uint8_t *autn, uint8_t *ck, uint8_t *ik, uint8_t *res, size_t *res_len,
+ uint8_t *auts)
+{
+ int i;
+ uint8_t xmac[8], ak[6], rx_sqn_bin[6];
+ unsigned long long rx_sqn;
+ const uint8_t *amf;
+
+ printf("=== Static SIM parameters:\n");
+ printf("Milenage SIM K: %s\n", osmo_hexdump_nospc(k, 16));
+ printf("Milenage SIM OPc: %s\n", osmo_hexdump_nospc(opc, 16));
+ printf("Milenage SIM SQN: %s\n", osmo_hexdump_nospc(sqn, 6));
+ printf("\n");
+
+ printf("=== Authentication Tuple as received from Network:\n");
+ printf("Milenage Input RAND: %s\n", osmo_hexdump_nospc(_rand, 16));
+ printf("Milenage Input AUTN: %s\n", osmo_hexdump_nospc(autn, 16));
+ printf("\tAUTN(+)AK: %s\n", osmo_hexdump_nospc(autn, 6));
+ printf("\tAMF: %s\n", osmo_hexdump_nospc(autn+6, 2));
+ printf("\tMAC: %s\n", osmo_hexdump_nospc(autn+8, 8));
+ printf("\n");
+
+ if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL))
+ return -1;
+
+ *res_len = 8;
+ printf("Milenage f2-Computed RES: %s\n", osmo_hexdump_nospc(res, *res_len));
+ printf("Milenage f3-Computed CK: %s\n", osmo_hexdump_nospc(ck, 16));
+ printf("Milenage f4-Computed IK: %s\n", osmo_hexdump_nospc(ik, 16));
+ printf("Milenage f5-Computed AK: %s\n", osmo_hexdump_nospc(ak, 6));
+
+ /* AUTN = (SQN ^ AK) || AMF || MAC */
+ for (i = 0; i < 6; i++)
+ rx_sqn_bin[i] = autn[i] ^ ak[i];
+ rx_sqn = osmo_load64be_ext(rx_sqn_bin, 6);
+ printf("Milenage Computed SQN: %s (%llu)\n", osmo_hexdump_nospc(rx_sqn_bin, 6), rx_sqn);
+
+ if (memcmp(rx_sqn_bin, sqn, 6) <= 0) {
+ printf("Milenage: RX-SQN differs from SIM SQN: Re-Sync!\n");
+ uint8_t auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+ if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak))
+ return -1;
+ printf("Milenage Computed AK*: %s", osmo_hexdump_nospc(ak, 6));
+ for (i = 0; i < 6; i++)
+ auts[i] = sqn[i] ^ ak[i];
+ if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6))
+ return -1;
+ printf("Milenage AUTS: %s\n", osmo_hexdump_nospc(auts, 14));
+ return -2;
+ }
+
+ amf = autn + 6;
+ if (milenage_f1(opc, k, _rand, rx_sqn_bin, amf, xmac, NULL))
+ return -1;
+
+ printf("Milenage f1-Computed XMAC: %s\n", osmo_hexdump_nospc(xmac, 8));
+
+ if (memcmp(xmac, autn + 8, 8) != 0) {
+ fprintf(stderr, "Milenage: MAC mismatch!\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void help(void)
+{
+ printf( "Static SIM card parameters:\n"
+ "-k --key\tSpecify Ki / K\n"
+ "-o --opc\tSpecify OPC\n"
+ "-O --op\tSpecify OP\n"
+ "-f --amf\tSpecify AMF\n"
+ "-s --sqn\tSpecify SQN\n"
+ "\n"
+ "Authentication Tuple by network:\n"
+ //"-i --ind\tSpecify IND slot for new SQN after AUTS\n"
+ //"-l --ind-len\tSpecify IND bit length (default=5)\n"
+ "-r --rand\tSpecify RAND random value\n"
+ "-A --autn\tSpecify AUTN authentication nonce\n"
+ );
+}
+
+static uint8_t g_k[16];
+static uint8_t g_opc[16];
+static uint8_t g_rand[16];
+static uint8_t g_autn[16];
+static uint8_t g_amf[16];
+static unsigned long long g_sqn;
+
+
+static int handle_options(int argc, char **argv)
+{
+ int rc, option_index;
+ bool rand_is_set = false;
+ bool autn_is_set = false;
+ bool sqn_is_set = false;
+ bool k_is_set = false;
+ bool opc_is_set = false;
+ bool amf_is_set = false;
+ bool opc_is_op = false;
+ int64_t val64;
+
+ while (1) {
+ int c;
+ static struct option long_options[] = {
+ { "key", 1, 0, 'k' },
+ { "opc", 1, 0, 'o' },
+ { "op", 1, 0, 'O' },
+ { "amf", 1, 0, 'f' },
+ { "sqn", 1, 0, 's' },
+ { "rand", 1, 0, 'r' },
+ { "autn", 1, 0, 'A' },
+ { "help", 0, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+
+ rc = 0;
+
+ c = getopt_long(argc, argv, "k:o:O:f:s:r:A:h", long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'k':
+ rc = osmo_hexparse(optarg, g_k, sizeof(g_k));
+ k_is_set = true;
+ break;
+ case 'o':
+ rc = osmo_hexparse(optarg, g_opc, sizeof(g_opc));
+ opc_is_op = false;
+ opc_is_set = true;
+ break;
+ case 'O':
+ rc = osmo_hexparse(optarg, g_opc, sizeof(g_opc));
+ opc_is_op = true;
+ opc_is_set = true;
+ break;
+ case 'A':
+ rc = osmo_hexparse(optarg, g_autn, sizeof(g_autn));
+ autn_is_set = true;
+ break;
+ case 'f':
+ rc = osmo_hexparse(optarg, g_amf, sizeof(g_amf));
+ amf_is_set = true;
+ break;
+ case 's':
+ rc = osmo_str_to_int64(&val64, optarg, 10, 0, INT64_MAX);
+ g_sqn = (unsigned long long)val64;
+ sqn_is_set = true;
+ break;
+ case 'r':
+ rc = osmo_hexparse(optarg, g_rand, sizeof(g_rand));
+ rand_is_set = true;
+ break;
+ case 'h':
+ help();
+ exit(0);
+ default:
+ help();
+ exit(1);
+ }
+
+ if (rc < 0) {
+ help();
+ fprintf(stderr, "\nError parsing argument of option `%c'\n", c);
+ exit(2);
+ }
+ }
+
+ if (!k_is_set || !opc_is_set || !autn_is_set || !rand_is_set) {
+ fprintf(stderr, "Error: K, OP[c], AUTN and RAND are mandatory arguments\n");
+ fprintf(stderr, "\n");
+ help();
+ exit(2);
+ }
+
+ if (!sqn_is_set)
+ printf("Warning: You may want to specify SQN\n");
+
+ if (!amf_is_set)
+ printf("Warning: You may want to specify AMF\n");
+
+ if (opc_is_op) {
+ uint8_t op[16];
+ memcpy(op, g_opc, 16);
+ rc = milenage_opc_gen(g_opc, g_k, op);
+ OSMO_ASSERT(rc == 0);
+ }
+
+ return 0;
+}
+
+
+
+int main(int argc, char **argv)
+{
+ printf("osmo-aka-check (C) 2021 by Harald Welte\n");
+ printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+ handle_options(argc, argv);
+
+ printf("\n");
+
+ uint8_t ck[16];
+ uint8_t ik[16];
+ uint8_t res[16];
+ size_t res_len;
+ uint8_t auts[14];
+ uint8_t sqn_bin[6];
+ int rc;
+
+ osmo_store64be_ext(g_sqn, sqn_bin, 6);
+
+ rc = milenage_check(g_opc, g_k, sqn_bin, g_rand, g_autn, ck, ik, res, &res_len, auts);
+
+ if (rc < 0) {
+ fprintf(stderr, "Authentication FAILED!\n");
+ exit(1);
+ } else {
+ printf("Authentication SUCCEEDED\n");
+ exit(0);
+ }
+}
diff --git a/utils/osmo-arfcn.c b/utils/osmo-arfcn.c
index aee132c7..3d4fb507 100644
--- a/utils/osmo-arfcn.c
+++ b/utils/osmo-arfcn.c
@@ -15,10 +15,6 @@
* 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, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -97,10 +93,10 @@ int main(int argc, char **argv)
{
int arfcn, freq, pcs = 0, uplink = -1;
int opt;
- char *param;
+ char *param = NULL;
enum program_mode mode = MODE_NONE;
- while ((opt = getopt(argc, argv, "pa:f:ud")) != -1) {
+ while ((opt = getopt(argc, argv, "pa:f:udh")) != -1) {
switch (opt) {
case 'p':
pcs = 1;
@@ -128,6 +124,11 @@ int main(int argc, char **argv)
}
}
+ if (argc > optind) {
+ fprintf(stderr, "Unsupported positional arguments in command line\n");
+ exit(2);
+ }
+
switch (mode) {
case MODE_NONE:
help(argv[0]);
diff --git a/utils/osmo-auc-gen.c b/utils/osmo-auc-gen.c
index ec9bad88..50419a43 100644
--- a/utils/osmo-auc-gen.c
+++ b/utils/osmo-auc-gen.c
@@ -1,7 +1,7 @@
/*! \file osmo-auc-gen.c
* GSM/GPRS/3G authentication testing tool. */
/*
- * (C) 2010-2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2023 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
@@ -15,10 +15,6 @@
* 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, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -34,8 +30,20 @@
#include <osmocom/crypt/auth.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/base64.h>
#include <osmocom/gsm/gsm_utils.h>
+static void print_base64(const char *fmt, const uint8_t *data, unsigned int len)
+{
+ uint8_t outbuf[256];
+ size_t olen;
+
+ OSMO_ASSERT(osmo_base64_encode(outbuf, sizeof(outbuf), &olen, data, len) == 0);
+ OSMO_ASSERT(sizeof(outbuf) > olen);
+ outbuf[olen] = '\0';
+ printf(fmt, outbuf);
+}
+
static void dump_triplets_dat(struct osmo_auth_vector *vec)
{
if (vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
@@ -53,10 +61,17 @@ static void dump_auth_vec(struct osmo_auth_vector *vec)
printf("RAND:\t%s\n", osmo_hexdump_nospc(vec->rand, sizeof(vec->rand)));
if (vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
+ uint8_t inbuf[sizeof(vec->rand) + sizeof(vec->autn)];
+
printf("AUTN:\t%s\n", osmo_hexdump_nospc(vec->autn, sizeof(vec->autn)));
printf("IK:\t%s\n", osmo_hexdump_nospc(vec->ik, sizeof(vec->ik)));
printf("CK:\t%s\n", osmo_hexdump_nospc(vec->ck, sizeof(vec->ck)));
printf("RES:\t%s\n", osmo_hexdump_nospc(vec->res, vec->res_len));
+
+ memcpy(inbuf, vec->rand, sizeof(vec->rand));
+ memcpy(inbuf + sizeof(vec->rand), vec->autn, sizeof(vec->autn));
+ print_base64("IMS nonce:\t%s\n", inbuf, sizeof(inbuf));
+ print_base64("IMS res:\t%s\n", vec->res, vec->res_len);
}
if (vec->auth_types & OSMO_AUTH_TYPE_GSM) {
@@ -65,12 +80,12 @@ static void dump_auth_vec(struct osmo_auth_vector *vec)
}
}
-static struct osmo_sub_auth_data test_aud = {
+static struct osmo_sub_auth_data2 test_aud = {
.type = OSMO_AUTH_TYPE_NONE,
.algo = OSMO_AUTH_ALG_NONE,
};
-static void help()
+static void help(void)
{
int alg;
printf( "-2 --2g\tUse 2G (GSM) authentication\n"
@@ -83,6 +98,7 @@ static void help()
"-s --sqn\tSpecify SQN (only for 3G)\n"
"-i --ind\tSpecify IND slot for new SQN after AUTS (only for 3G)\n"
"-l --ind-len\tSpecify IND bit length (default=5) (only for 3G)\n"
+ "-L --res-len\tSpecify RES byte length (default=8) (only for 3G)\n"
"-A --auts\tSpecify AUTS (only for 3G)\n"
"-r --rand\tSpecify random value\n"
"-I --ipsec\tOutput in triplets.dat format for strongswan\n");
@@ -108,10 +124,12 @@ int main(int argc, char **argv)
int fmt_triplets_dat = 0;
uint64_t ind_mask = 0;
- printf("osmo-auc-gen (C) 2011-2012 by Harald Welte\n");
+ printf("osmo-auc-gen (C) 2011-2023 by Harald Welte\n");
printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
memset(_auts, 0, sizeof(_auts));
+ memset(vec, 0, sizeof(*vec));
+ vec->res_len = 8; /* default */
while (1) {
int c;
@@ -126,6 +144,7 @@ int main(int argc, char **argv)
{ "sqn", 1, 0, 's' },
{ "ind", 1, 0, 'i' },
{ "ind-len", 1, 0, 'l' },
+ { "res-len", 1, 0, 'L' },
{ "rand", 1, 0, 'r' },
{ "auts", 1, 0, 'A' },
{ "help", 0, 0, 'h' },
@@ -134,7 +153,7 @@ int main(int argc, char **argv)
rc = 0;
- c = getopt_long(argc, argv, "23a:k:o:f:s:i:l:r:hO:A:I", long_options,
+ c = getopt_long(argc, argv, "23a:k:o:f:s:i:l:L:r:hO:A:I", long_options,
&option_index);
if (c == -1)
@@ -159,10 +178,21 @@ int main(int argc, char **argv)
case OSMO_AUTH_TYPE_GSM:
rc = osmo_hexparse(optarg, test_aud.u.gsm.ki,
sizeof(test_aud.u.gsm.ki));
+ if (rc != sizeof(test_aud.u.gsm.ki)) {
+ fprintf(stderr, "Invalid Ki length %d\n", rc);
+ exit(2);
+ }
break;
case OSMO_AUTH_TYPE_UMTS:
rc = osmo_hexparse(optarg, test_aud.u.umts.k,
sizeof(test_aud.u.umts.k));
+ /* 3GPP TS 33.102 6.3.7: "The authentication key (K) shall have a length of
+ * 128 bits or 256 bits." */
+ if (rc != 16 && rc != 32) {
+ fprintf(stderr, "Invalid K length %d\n", rc);
+ exit(2);
+ }
+ test_aud.u.umts.k_len = rc;
break;
default:
fprintf(stderr, "please specify 2g/3g first!\n");
@@ -175,6 +205,11 @@ int main(int argc, char **argv)
}
rc = osmo_hexparse(optarg, test_aud.u.umts.opc,
sizeof(test_aud.u.umts.opc));
+ if (rc != 16 && rc != 32) {
+ fprintf(stderr, "Invalid OPC length %d\n", rc);
+ exit(2);
+ }
+ test_aud.u.umts.opc_len = rc;
test_aud.u.umts.opc_is_op = 0;
break;
case 'O':
@@ -184,6 +219,11 @@ int main(int argc, char **argv)
}
rc = osmo_hexparse(optarg, test_aud.u.umts.opc,
sizeof(test_aud.u.umts.opc));
+ if (rc != 16 && rc != 32) {
+ fprintf(stderr, "Invalid OP length %d\n", rc);
+ exit(2);
+ }
+ test_aud.u.umts.opc_len = rc;
test_aud.u.umts.opc_is_op = 1;
break;
case 'A':
@@ -201,13 +241,17 @@ int main(int argc, char **argv)
}
rc = osmo_hexparse(optarg, test_aud.u.umts.amf,
sizeof(test_aud.u.umts.amf));
+ if (rc != 2) {
+ fprintf(stderr, "Invalid AMF length %d\n", rc);
+ exit(2);
+ }
break;
case 's':
if (test_aud.type != OSMO_AUTH_TYPE_UMTS) {
fprintf(stderr, "Only UMTS has SQN\n");
exit(2);
}
- sqn = strtoull(optarg, 0, 10);
+ sqn = strtoull(optarg, 0, 0);
sqn_is_set = 1;
break;
case 'i':
@@ -225,8 +269,20 @@ int main(int argc, char **argv)
}
test_aud.u.umts.ind_bitlen = atoi(optarg);
break;
+ case 'L':
+ rc = atoi(optarg);
+ if (rc != 4 && rc != 8 && rc != 16) {
+ fprintf(stderr, "Invalid RES length %u\n", rc);
+ exit(2);
+ }
+ vec->res_len = rc;
+ break;
case 'r':
rc = osmo_hexparse(optarg, _rand, sizeof(_rand));
+ if (rc != sizeof(_rand)) {
+ fprintf(stderr, "Invalid RAND length %d\n", rc);
+ exit(2);
+ }
rand_is_set = 1;
break;
case 'I':
@@ -247,6 +303,11 @@ int main(int argc, char **argv)
}
}
+ if (argc > optind) {
+ fprintf(stderr, "Unsupported positional arguments in command line\n");
+ exit(2);
+ }
+
if (!rand_is_set) {
rc = osmo_get_rand_id(_rand, 16);
if (rc < 0) {
@@ -264,8 +325,6 @@ int main(int argc, char **argv)
exit(2);
}
- memset(vec, 0, sizeof(*vec));
-
if (test_aud.type == OSMO_AUTH_TYPE_UMTS) {
uint64_t seq_1 = 1LL << test_aud.u.umts.ind_bitlen;
ind_mask = seq_1 - 1;
@@ -296,9 +355,9 @@ int main(int argc, char **argv)
}
if (!auts_is_set)
- rc = osmo_auth_gen_vec(vec, &test_aud, _rand);
+ rc = osmo_auth_gen_vec2(vec, &test_aud, _rand);
else
- rc = osmo_auth_gen_vec_auts(vec, &test_aud, _auts, _rand, _rand);
+ rc = osmo_auth_gen_vec_auts2(vec, &test_aud, _auts, _rand, _rand);
if (rc < 0) {
if (!auts_is_set)
fprintf(stderr, "error generating auth vector\n");
diff --git a/utils/osmo-config-merge.c b/utils/osmo-config-merge.c
index 477bb028..ed2039ac 100644
--- a/utils/osmo-config-merge.c
+++ b/utils/osmo-config-merge.c
@@ -15,10 +15,6 @@
* 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, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*
diff --git a/utils/osmo-ns-dummy-vty.c b/utils/osmo-ns-dummy-vty.c
new file mode 100644
index 00000000..2e59b11b
--- /dev/null
+++ b/utils/osmo-ns-dummy-vty.c
@@ -0,0 +1,325 @@
+/* VTY for osmo-ns-dummy */
+
+/* (C) 2021 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+
+extern struct gprs_ns2_inst *g_nsi;
+static struct llist_head g_ns_traf_gens = LLIST_HEAD_INIT(g_ns_traf_gens);
+int g_mirror_mode;
+
+/* one NS traffic generator instance. You can have as many of these as you want,
+ * just as long as they have unique names */
+struct ns_traf_gen {
+ struct llist_head list;
+ const char *name;
+ struct {
+ uint16_t nsei;
+ uint16_t bvci;
+ /* size of each packet */
+ uint16_t pkt_size;
+ /* interval between packets in us */
+ uint32_t interval_us;
+ /* fixed (false) or random (true) LSP */
+ bool lsp_randomize;
+ /* (fixeD) Link Selector Parameter */
+ uint32_t lsp;
+ } cfg;
+ struct osmo_fd timerfd;
+ bool running;
+};
+
+#define LOGNTG(ntg, lvl, fmt, args ...) \
+ LOGP(DLGLOBAL, lvl, "traf-gen(%s): " fmt, (ntg)->name, ## args)
+
+/* allocate and transmit one NS message */
+static int ntg_tx_one(struct ns_traf_gen *ntg)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ struct msgb *msg = msgb_alloc_headroom(3072, 20, "NS traffic gen");
+
+ if (!msg)
+ return -ENOMEM;
+ msgb_put(msg, ntg->cfg.pkt_size);
+ nsp.bvci = ntg->cfg.bvci;
+ nsp.nsei = ntg->cfg.nsei;
+ if (ntg->cfg.lsp_randomize)
+ nsp.u.unitdata.link_selector = rand();
+ else
+ nsp.u.unitdata.link_selector = ntg->cfg.lsp;
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg);
+ return gprs_ns2_recv_prim(g_nsi, &nsp.oph);
+}
+
+/* call-back from transmit timer-fd */
+static int ntg_timerfd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ uint64_t expire_count;
+ struct ns_traf_gen *ntg = ofd->data;
+ unsigned int i;
+ int rc;
+
+ OSMO_ASSERT(what & OSMO_FD_READ);
+
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ for (i = 0; i < expire_count; i++)
+ ntg_tx_one(ntg);
+
+ return 0;
+}
+
+static struct ns_traf_gen *ns_traf_gen_find(const char *name)
+{
+ struct ns_traf_gen *ntg;
+
+ llist_for_each_entry(ntg, &g_ns_traf_gens, list) {
+ if (!strcmp(ntg->name, name))
+ return ntg;
+ }
+ return NULL;
+}
+
+static struct ns_traf_gen *ns_traf_gen_find_or_alloc(const char *name)
+{
+ struct ns_traf_gen *ntg;
+ int rc;
+
+ ntg = ns_traf_gen_find(name);
+ if (ntg)
+ return ntg;
+
+ ntg = talloc_zero(g_nsi, struct ns_traf_gen);
+ OSMO_ASSERT(ntg);
+ ntg->name = talloc_strdup(ntg, name);
+ ntg->timerfd.fd = -1;
+ rc = osmo_timerfd_setup(&ntg->timerfd, ntg_timerfd_cb, ntg);
+ OSMO_ASSERT(rc >= 0);
+ llist_add_tail(&ntg->list, &g_ns_traf_gens);
+
+ return ntg;
+}
+
+enum nodes {
+ NTG_NODE = _LAST_OSMOVTY_NODE + 1,
+};
+
+static struct cmd_node ntg_node = {
+ NTG_NODE,
+ "%s(config-ns-traf-gen)# ",
+ 1,
+};
+
+static int config_write_ntg(struct vty *vty)
+{
+ struct ns_traf_gen *ntg;
+
+ llist_for_each_entry(ntg, &g_ns_traf_gens, list) {
+ vty_out(vty, "ns-traffic-generator %s%s", ntg->name, VTY_NEWLINE);
+ vty_out(vty, " nsei %u%s", ntg->cfg.nsei, VTY_NEWLINE);
+ vty_out(vty, " bvci %u%s", ntg->cfg.bvci, VTY_NEWLINE);
+ vty_out(vty, " packet-size %u%s", ntg->cfg.pkt_size, VTY_NEWLINE);
+ vty_out(vty, " interval-us %u%s", ntg->cfg.interval_us, VTY_NEWLINE);
+ vty_out(vty, " lsp %u%s", ntg->cfg.lsp, VTY_NEWLINE);
+ vty_out(vty, " lsp-mode %s%s", ntg->cfg.lsp_randomize ? "randomized" : "fixed", VTY_NEWLINE);
+ }
+ vty_out(vty, "mirror-mode %s%s", g_mirror_mode ? "enable" : "disable", VTY_NEWLINE);
+
+ return 0;
+}
+
+DEFUN(ntg_start, ntg_start_stop_cmd,
+ "ns-traffic-generator (start|stop) NAME",
+ "Control named NS traffic generator\n"
+ "Start generating traffic in this traffic generator\n"
+ "Stop generating traffic in this traffic generator\n"
+ "Name of NS traffic generator to start\n")
+{
+ struct ns_traf_gen *ntg = ns_traf_gen_find(argv[1]);
+ if (!ntg) {
+ vty_out(vty, "NS Traffic generator '%s' doesn't exist%s", argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "start")) {
+ struct timespec interval;
+ if (ntg->running) {
+ vty_out(vty, "NS Traffic generator was already started%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ interval.tv_sec = ntg->cfg.interval_us / 1000000;
+ interval.tv_nsec = (ntg->cfg.interval_us % 1000000) * 1000;
+ osmo_timerfd_schedule(&ntg->timerfd, NULL, &interval);
+ ntg->running = true;
+ } else {
+ if (!ntg->running) {
+ vty_out(vty, "NS Traffic generator was already stopped%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ osmo_timerfd_disable(&ntg->timerfd);
+ ntg->running = false;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_nsei, ntg_nsei_cmd,
+ "nsei <0-65535>",
+ "NSEI to use when generating traffic\n"
+ "NSEI to use when generating traffic\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.nsei = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_bvci, ntg_bvci_cmd,
+ "bvci <0-65535>",
+ "BVCI to use when generating traffic\n"
+ "BVCI to use when generating traffic\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.bvci = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_pkt_size, ntg_pkt_size_cmd,
+ "packet-size <0-2048>",
+ "Packet size for generated NS-UNITDATA payload\n"
+ "Packet size for generated NS-UNITDATA payload\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.pkt_size = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_pkt_intv_us, ntg_pkt_intv_us_cmd,
+ "interval-us <0-1000000>",
+ "Interval between packets in microseconds\n"
+ "Interval between packets in microseconds\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.interval_us = atoi(argv[0]);
+ if (ntg->running) {
+ /* TODO: update timer */
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_lsp, ntg_lsp_cmd,
+ "lsp <0-4294967295>",
+ "Link Selector Parameter (only used in fixed mode)\n"
+ "Link Selector Parameter (only used in fixed mode)\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.lsp = strtoul(argv[0], NULL, 10);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_lsp_mode, ntg_lsp_mode_cmd,
+ "lsp-mode (fixed|randomized)",
+ "Link Selector Parameter Mode\n"
+ "Fixed / Staic LSP\n"
+ "Randomized LSP\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ if (!strcmp(argv[0], "randomized"))
+ ntg->cfg.lsp_randomize = true;
+ else
+ ntg->cfg.lsp_randomize = false;
+ return CMD_SUCCESS;
+}
+
+DEFUN(gen_traffic, gen_traffic_cmd,
+ "ns-traffic-generator NAME",
+ "Configure a given NS traffic generator\n" "Name of NS traffic generator\n")
+{
+ struct ns_traf_gen *ntg = ns_traf_gen_find_or_alloc(argv[0]);
+
+ if (!ntg)
+ return CMD_WARNING;
+
+ vty->index = ntg;
+ vty->node = NTG_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(mirror_mode, mirror_mode_cmd,
+ "mirror-mode (enable|disable)",
+ "Configure mirroring of incoming NS-UNITDATA\n"
+ "Enable mirroring of incoming NS-UNITDATA\n"
+ "Disable mirroring of incoming NS-UNITDATA\n")
+{
+ if (!strcmp(argv[0], "enable"))
+ g_mirror_mode = true;
+ else
+ g_mirror_mode = false;
+
+ return CMD_SUCCESS;
+}
+
+
+int nsdummy_vty_init(void)
+{
+ /* configuration of traffic generators via CONFIG / NTG node */
+ install_element(CONFIG_NODE, &gen_traffic_cmd);
+ install_element(CONFIG_NODE, &mirror_mode_cmd);
+ install_node(&ntg_node, config_write_ntg);
+ install_element(NTG_NODE, &ntg_nsei_cmd);
+ install_element(NTG_NODE, &ntg_bvci_cmd);
+ install_element(NTG_NODE, &ntg_pkt_size_cmd);
+ install_element(NTG_NODE, &ntg_pkt_intv_us_cmd);
+ install_element(NTG_NODE, &ntg_lsp_cmd);
+ install_element(NTG_NODE, &ntg_lsp_mode_cmd);
+
+ /* starting/stopping the traffic generators is in 'enable' mode, not 'config' */
+ install_element(ENABLE_NODE, &ntg_start_stop_cmd);
+
+ return 0;
+}
diff --git a/utils/osmo-ns-dummy.c b/utils/osmo-ns-dummy.c
new file mode 100644
index 00000000..890444cf
--- /dev/null
+++ b/utils/osmo-ns-dummy.c
@@ -0,0 +1,316 @@
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <signal.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/tdef_vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+
+#include "config.h"
+
+void *tall_nsdummy_ctx = NULL;
+static struct log_info log_info = {};
+static bool quit = false;
+static bool config_given = false;
+static bool daemonize = false;
+static int vty_port = 0;
+static int ctrl_port = 0;
+static char *config_file = NULL;
+struct gprs_ns2_inst *g_nsi;
+
+static const char vty_copyright[] =
+ "Copyright (C) 2020 by by sysmocom - s.f.m.c. GmbH\r\n"
+ "Author: Alexander Couzens <lynxis@fe80.eu>\r\n"
+ "License GNU GPL version 2 or later\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoNSdummy",
+ .version = PACKAGE_VERSION,
+ .copyright = vty_copyright,
+};
+
+static void print_help(void)
+{
+ printf( "Some useful options:\n"
+ " -h --help This text\n"
+ " -c --config-file Specify the filename of the config file\n"
+ " -V --version Print version\n"
+ " -D --daemonize Fork the process into a background daemon\n"
+ " -p --vty-port PORT Set the vty port to listen on.\n"
+ " -r --ctrl-port PORT Set the ctrl port to listen on.\n"
+ "\nVTY reference generation:\n"
+ " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"
+ " --vty-ref-xml Generate the VTY reference XML output and exit.\n"
+ );
+}
+
+static void handle_long_options(const char *prog_name, const int long_option)
+{
+ static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
+
+ switch (long_option) {
+ case 1:
+ vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
+ if (vty_ref_mode < 0) {
+ fprintf(stderr, "%s: Unknown VTY reference generation "
+ "mode '%s'\n", prog_name, optarg);
+ exit(2);
+ }
+ break;
+ case 2:
+ fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
+ get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
+ get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
+ vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
+ exit(0);
+ default:
+ fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
+ exit(2);
+ }
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static int long_option = 0;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "config-file", 1, 0, 'c' },
+ { "version", 0, 0, 'V' },
+ { "daemonize", 0, 0, 'D' },
+ { "vty-port", 1, 0, 'p' },
+ { "ctrl-port", 1, 0, 'r' },
+ { "vty-ref-mode", 1, &long_option, 1 },
+ { "vty-ref-xml", 0, &long_option, 2 },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "hc:p:r:VD",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 0:
+ handle_long_options(argv[0], long_option);
+ break;
+ case 'c':
+ if (config_file)
+ free(config_file);
+ config_file = optarg;
+ config_given = true;
+ break;
+ case 'p':
+ vty_port = atoi(optarg);
+ if (vty_port < 0 || vty_port > 65535) {
+ fprintf(stderr, "Invalid VTY port %d given!\n", vty_port);
+ exit(1);
+ }
+ break;
+ case 'r':
+ ctrl_port = atoi(optarg);
+ if (ctrl_port < 0 || ctrl_port > 65535) {
+ fprintf(stderr, "Invalid CTRL port %d given!\n", ctrl_port);
+ exit(1);
+ }
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ case 'D':
+ daemonize = true;
+ break;
+ default:
+ fprintf(stderr, "Unknown option '%c'\n", c);
+ exit(0);
+ break;
+ }
+ }
+
+ if (!config_file)
+ config_file = "osmo-ns-dummy.cfg";
+ if (!vty_port) {
+ fprintf(stderr, "A vty port need to be specified (-p)\n");
+ exit(1);
+ }
+}
+
+void sighandler(int sigset)
+{
+ if (sigset == SIGPIPE)
+ return;
+
+ fprintf(stderr, "Signal %d received.\n", sigset);
+
+ switch (sigset) {
+ case SIGINT:
+ case SIGTERM:
+ /* If another signal is received afterwards, the program
+ * is terminated without finishing shutdown process.
+ */
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGABRT, SIG_DFL);
+ signal(SIGUSR1, SIG_DFL);
+ signal(SIGUSR2, SIG_DFL);
+
+ quit = 1;
+ 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_nsdummy_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_nsdummy_ctx, stderr);
+ break;
+ }
+}
+
+extern int g_mirror_mode;
+
+/* called by the ns layer */
+int gprs_ns_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_gprs_ns2_prim *nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph);
+
+ switch (oph->primitive) {
+ case GPRS_NS2_PRIM_UNIT_DATA:
+ if (g_mirror_mode) {
+ /* simply switch indication->request and resubmit */
+ oph->operation = PRIM_OP_REQUEST;
+ msgb_pull_to_l3(oph->msg);
+ nsp->u.unitdata.link_selector = rand(); /* ensure random distribution */
+ return gprs_ns2_recv_prim(g_nsi, oph);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (oph->msg)
+ msgb_free(oph->msg);
+
+ return 0;
+}
+
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ return 0;
+}
+
+extern int nsdummy_vty_init(void);
+
+int main (int argc, char *argv[])
+{
+ void *ctx = tall_nsdummy_ctx = talloc_named_const(NULL, 0, "osmo-ns-dummy");
+ struct ctrl_handle *ctrl;
+ int rc = 0;
+
+ osmo_init_logging2(ctx, &log_info);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_set_log_level(osmo_stderr_target, LOGL_INFO);
+ msgb_talloc_ctx_init(ctx, 0);
+ osmo_stats_init(ctx);
+ rate_ctr_init(ctx);
+
+ vty_info.tall_ctx = ctx;
+ vty_init(&vty_info);
+ ctrl_vty_init(ctx);
+ logging_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+
+ handle_options(argc, argv);
+
+ g_nsi = gprs_ns2_instantiate(ctx, gprs_ns_prim_cb, NULL);
+ if (!g_nsi) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NS instance\n");
+ exit(1);
+ }
+
+ gprs_ns2_vty_init(g_nsi);
+ nsdummy_vty_init();
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0 && config_given) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ config_file);
+ exit(1);
+ }
+ if (rc < 0)
+ fprintf(stderr, "No config file: '%s' Using default config.\n",
+ config_file);
+
+ rc = telnet_init_default(ctx, NULL, vty_port);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ if (ctrl_port > 0) {
+ ctrl = ctrl_interface_setup(NULL, ctrl_port, NULL);
+ if (!ctrl) {
+ fprintf(stderr, "Failed to initialize control interface. Exiting.\n");
+ exit(1);
+ }
+ }
+
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+ signal(SIGABRT, sighandler);
+ signal(SIGUSR1, sighandler);
+ signal(SIGUSR2, sighandler);
+ osmo_init_ignore_signals();
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (!quit) {
+ osmo_select_main(0);
+ }
+
+ telnet_exit();
+ gprs_ns2_free(g_nsi);
+
+ talloc_report_full(tall_nsdummy_ctx, stderr);
+ talloc_free(tall_nsdummy_ctx);
+
+ return 0;
+}
diff --git a/utils/osmo-sim-test.c b/utils/osmo-sim-test.c
index 5588294a..2c6c7641 100644
--- a/utils/osmo-sim-test.c
+++ b/utils/osmo-sim-test.c
@@ -1,5 +1,5 @@
/* libosmosim test application - currently simply dumps a USIM */
-/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2012-2020 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
@@ -12,18 +12,21 @@
* 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, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
+#include <getopt.h>
#include <arpa/inet.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <limits.h>
+
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/sim/sim.h>
@@ -32,6 +35,9 @@
/* FIXME: this needs to be moved to card_fs_uicc.c */
+static uint8_t g_class = 0x00; /* UICC/USIM */
+static const char *g_output_dir;
+
/* 11.1.1 */
static struct msgb *_select_file(struct osim_chan_hdl *st, uint8_t p1, uint8_t p2,
const uint8_t *data, uint8_t data_len)
@@ -39,7 +45,7 @@ static struct msgb *_select_file(struct osim_chan_hdl *st, uint8_t p1, uint8_t p
struct msgb *msg;
uint8_t *dst;
- msg = osim_new_apdumsg(0x00, 0xA4, p1, p2, data_len, 256);
+ msg = osim_new_apdumsg(g_class, 0xA4, p1, p2, data_len, 256);
dst = msgb_put(msg, data_len);
memcpy(dst, data, data_len);
@@ -58,10 +64,16 @@ static struct msgb *select_adf(struct osim_chan_hdl *st, const uint8_t *adf, uin
static struct msgb *select_file(struct osim_chan_hdl *st, uint16_t fid)
{
uint16_t cfid = htons(fid);
+ uint8_t p2 = 0x04;
- return _select_file(st, 0x00, 0x04, (uint8_t *)&cfid, 2);
+ /* Classic SIM cards don't support 0x04 (Return FCP) */
+ if (g_class == 0xA0)
+ p2 = 0x00;
+
+ return _select_file(st, 0x00, p2, (uint8_t *)&cfid, 2);
}
+#if 0
/* 11.1.9 */
static int verify_pin(struct osim_chan_hdl *st, uint8_t pin_nr, char *pin)
{
@@ -71,7 +83,7 @@ static int verify_pin(struct osim_chan_hdl *st, uint8_t pin_nr, char *pin)
if (strlen(pin) > 8)
return -EINVAL;
- msg = osim_new_apdumsg(0x00, 0x20, 0x00, pin_nr, 8, 0);
+ msg = osim_new_apdumsg(g_class, 0x20, 0x00, pin_nr, 8, 0);
pindst = (char *) msgb_put(msg, 8);
memset(pindst, 0xFF, 8);
/* Do not copy the terminating \0 */
@@ -79,13 +91,14 @@ static int verify_pin(struct osim_chan_hdl *st, uint8_t pin_nr, char *pin)
return osim_transceive_apdu(st, msg);
}
+#endif
/* 11.1.5 */
static struct msgb *read_record_nr(struct osim_chan_hdl *st, uint8_t rec_nr, uint16_t rec_size)
{
struct msgb *msg;
- msg = osim_new_apdumsg(0x00, 0xB2, rec_nr, 0x04, 0, rec_size);
+ msg = osim_new_apdumsg(g_class, 0xB2, rec_nr, 0x04, 0, rec_size);
osim_transceive_apdu(st, msg);
@@ -100,7 +113,7 @@ static struct msgb *read_binary(struct osim_chan_hdl *st, uint16_t offset, uint1
if (offset > 0x7fff || len > 256)
return NULL;
- msg = osim_new_apdumsg(0x00, 0xB0, offset >> 8, offset & 0xff, 0, len & 0xff);
+ msg = osim_new_apdumsg(g_class, 0xB0, offset >> 8, offset & 0xff, 0, len & 0xff);
osim_transceive_apdu(st, msg);
@@ -174,39 +187,111 @@ static int osim_fcp_fd_decode(struct osim_fcp_fd_decoded *ofd, const uint8_t *fc
return 0;
}
-extern struct osim_card_profile *osim_cprof_usim(void *ctx);
+/* TS 51.011 Section 9.3 Type of File */
+static const enum osim_file_type sim2ftype[8] = {
+ [1] = TYPE_MF,
+ [2] = TYPE_DF,
+ [4] = TYPE_EF,
+};
-static struct msgb *try_select_adf_usim(struct osim_chan_hdl *st)
+/* TS 51.011 Section 9.3 Structure of File */
+static const enum osim_ef_type sim2eftype[8] = {
+ [0] = EF_TYPE_TRANSP,
+ [1] = EF_TYPE_RECORD_FIXED,
+ [3] = EF_TYPE_RECORD_CYCLIC,
+};
+
+/* TS 51.011 Section 9.2.1 */
+static int osim_fcp_fd_decode_sim(struct osim_fcp_fd_decoded *ofd, const uint8_t *fcp, int fcp_len)
+{
+ memset(ofd, 0, sizeof(*ofd));
+
+ if (fcp_len < 14)
+ return -EINVAL;
+
+ ofd->type = sim2ftype[fcp[6] & 7];
+ switch (ofd->type) {
+ case TYPE_EF:
+ ofd->ef_type = sim2eftype[fcp[13] & 7];
+ if (fcp_len < 13 + fcp[12])
+ return -EINVAL;
+ switch (ofd->ef_type) {
+ case EF_TYPE_RECORD_FIXED:
+ case EF_TYPE_RECORD_CYCLIC:
+ if (fcp_len < 15)
+ return -EINVAL;
+ ofd->rec_len = fcp[14];
+ ofd->num_rec = ntohs(*(uint16_t *)(fcp+2)) / ofd->rec_len;
+ break;
+ default:
+ break;
+ }
+ break;
+ case TYPE_MF:
+ case TYPE_DF:
+ if (fcp_len < 22)
+ return -EINVAL;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*! scan an UICC for all installed apps; allocate osim_card_app_hdl for each of them */
+static int osim_uicc_scan_apps(struct osim_chan_hdl *st)
{
struct tlv_parsed tp;
struct osim_fcp_fd_decoded ofd;
- struct msgb *msg, *msg2;
+ struct msgb *msg;
uint8_t *cur;
int rc, i;
+ /* we don't know where we currently might be; go back to MF */
+ msg = select_file(st, 0x3f00);
+ if (!msg)
+ return -EIO;
+ if (msgb_apdu_sw(msg) != 0x9000)
+ return -msgb_apdu_sw(msg);
+
+ /* select EF.DIR */
msg = select_file(st, 0x2f00);
+ if (!msg)
+ return -EIO;
+ /* return status word in case of error */
+ if (msgb_apdu_sw(msg) != 0x9000)
+ return -msgb_apdu_sw(msg);
+
+ /* various FCP related sanity checks */
rc = tlv_parse(&tp, &ts102221_fcp_tlv_def, msgb_apdu_de(msg)+2, msgb_apdu_le(msg)-2, 0, 0);
- if (rc < 0)
- return NULL;
+ if (rc < 0) {
+ fprintf(stderr, "Error decoding EF.DIR FCP TLV\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
dump_fcp_template(&tp);
if (!TLVP_PRESENT(&tp, UICC_FCP_T_FILE_DESC) ||
TLVP_LEN(&tp, UICC_FCP_T_FILE_DESC) < 5) {
+ fprintf(stderr, "No EF.DIR FCP file description\n");
msgb_free(msg);
- return NULL;
+ return -EINVAL;
}
rc = osim_fcp_fd_decode(&ofd, TLVP_VAL(&tp, UICC_FCP_T_FILE_DESC),
TLVP_LEN(&tp, UICC_FCP_T_FILE_DESC));
if (rc < 0) {
+ fprintf(stderr, "Error decoding EF.DIR FCP file description\n");
msgb_free(msg);
- return NULL;
+ return -EINVAL;
}
if (ofd.type != TYPE_EF || ofd.ef_type != EF_TYPE_RECORD_FIXED) {
+ fprintf(stderr, "EF.DIR is not a fixed record EF!?!\n");
msgb_free(msg);
- return NULL;
+ return -EINVAL;
}
msgb_free(msg);
@@ -214,109 +299,147 @@ static struct msgb *try_select_adf_usim(struct osim_chan_hdl *st)
printf("ofd rec_len = %u, num_rec = %u\n", ofd.rec_len, ofd.num_rec);
for (i = 0; i < ofd.num_rec; i++) {
+ const uint8_t *aid;
+ uint8_t aid_len;
msg = read_record_nr(st, i+1, ofd.rec_len);
- if (!msg)
- return NULL;
+ if (!msg) {
+ fprintf(stderr, "Error reading Record %u of EF.DIR, skipping\n", i+1);
+ continue;
+ }
+
+ /* Entries look like this:
+ * 61194f10 a0000000871002ffffffff8907090000 5005 5553696d31 ffffffffffffffffffffff */
cur = msgb_apdu_de(msg);
if (msgb_apdu_le(msg) < 5) {
+ fprintf(stderr, "Record length %u too short for EF.DIR, skipping\n", msgb_apdu_le(msg));
msgb_free(msg);
- return NULL;
+ continue;
}
if (cur[0] != 0x61 || cur[1] < 0x03 || cur[1] > 0x7f ||
cur[2] != 0x4F || cur[3] < 0x01 || cur[3] > 0x10) {
+ fprintf(stderr, "Unexpected/unknown record in EF.DIR: %s, skipping\n",
+ osmo_hexdump_nospc(msgb_apdu_de(msg), msgb_apdu_le(msg)));
msgb_free(msg);
- return NULL;
+ continue;
}
+ aid_len = cur[3];
+ aid = cur+4;
- /* FIXME: actually check if it is an AID that we support, or
- * iterate until we find one that we support */
-
- msg2 = select_adf(st, cur+4, cur[3]);
-
- /* attach the USIM profile, FIXME: do this based on AID match */
- st->card->prof = osim_cprof_usim(st->card);
- st->cwd = osim_file_desc_find_name(st->card->prof->mf, "ADF.USIM");
-
- msgb_free(msg);
-
- return msg2;
+ /* FIXME: parse / pass label*/
+ printf("Detected AID %s\n", osmo_hexdump_nospc(aid, aid_len));
+ osim_card_hdl_add_app(st->card, aid, aid_len, NULL);
}
- return NULL;
+ return i;
}
-static int dump_file(struct osim_chan_hdl *chan, uint16_t fid)
+
+extern struct osim_card_profile *osim_cprof_sim(void *ctx);
+extern struct osim_card_profile *osim_cprof_uicc(void *ctx, bool have_df_gsm);
+
+static int dump_file(struct osim_chan_hdl *chan, const char *short_name, uint16_t fid)
{
struct tlv_parsed tp;
struct osim_fcp_fd_decoded ffdd;
struct msgb *msg, *rmsg;
int rc, i, offset;
+ FILE *f_data = NULL;
+ /* Select the file */
msg = select_file(chan, fid);
if (!msg) {
- printf("Unable to select file\n");
+ fprintf(stderr, "Unable to select file\n");
return -EIO;
}
- printf("SW: %s\n", osim_print_sw(chan->card, msgb_apdu_sw(msg)));
if (msgb_apdu_sw(msg) != 0x9000) {
- printf("status 0x%04x selecting file\n", msgb_apdu_sw(msg));
+ fprintf(stderr, "status 0x%04x selecting file\n", msgb_apdu_sw(msg));
goto out;
}
- rc = tlv_parse(&tp, &ts102221_fcp_tlv_def, msgb_apdu_de(msg)+2, msgb_apdu_le(msg)-2, 0, 0);
- if (rc < 0) {
- printf("Unable to parse FCP\n");
- goto out;
- }
+ if (g_class != 0xA0) {
+ rc = tlv_parse(&tp, &ts102221_fcp_tlv_def, msgb_apdu_de(msg)+2, msgb_apdu_le(msg)-2, 0, 0);
+ if (rc < 0) {
+ fprintf(stderr, "Unable to parse FCP: %s\n", msgb_hexdump(msg));
+ goto out;
+ }
- if (!TLVP_PRESENT(&tp, UICC_FCP_T_FILE_DESC) ||
- TLVP_LEN(&tp, UICC_FCP_T_FILE_DESC) < 2) {
- printf("No file descriptor present ?!?\n");
- goto out;
+ if (!TLVP_PRESENT(&tp, UICC_FCP_T_FILE_DESC) ||
+ TLVP_LEN(&tp, UICC_FCP_T_FILE_DESC) < 2) {
+ fprintf(stderr, "No file descriptor present ?!?\n");
+ goto out;
+ }
+
+ rc = osim_fcp_fd_decode(&ffdd, TLVP_VAL(&tp, UICC_FCP_T_FILE_DESC),
+ TLVP_LEN(&tp, UICC_FCP_T_FILE_DESC));
+ } else {
+ rc = osim_fcp_fd_decode_sim(&ffdd, msgb_apdu_de(msg), msgb_apdu_le(msg));
}
- rc = osim_fcp_fd_decode(&ffdd, TLVP_VAL(&tp, UICC_FCP_T_FILE_DESC),
- TLVP_LEN(&tp, UICC_FCP_T_FILE_DESC));
if (rc < 0) {
- printf("Unable to decode File Descriptor\n");
+ fprintf(stderr, "Unable to decode File Descriptor\n");
goto out;
}
if (ffdd.type != TYPE_EF) {
- printf("File Type != EF\n");
+ fprintf(stderr, "File Type != EF\n");
goto out;
}
+ if (g_output_dir) {
+ f_data = fopen(short_name, "w");
+ if (!f_data) {
+ fprintf(stderr, "Couldn't create '%s': %s\n", short_name, strerror(errno));
+ goto out;
+ }
+ }
+
printf("EF type: %u\n", ffdd.ef_type);
switch (ffdd.ef_type) {
case EF_TYPE_RECORD_FIXED:
for (i = 0; i < ffdd.num_rec; i++) {
+ const char *hex;
rmsg = read_record_nr(chan, i+1, ffdd.rec_len);
- if (!rmsg)
+ if (!rmsg) {
+ if (f_data)
+ fclose(f_data);
return -EIO;
- printf("SW: %s\n", osim_print_sw(chan->card, msgb_apdu_sw(msg)));
- printf("Rec %03u: %s\n", i+1,
- osmo_hexdump(msgb_apdu_de(rmsg), msgb_apdu_le(rmsg)));
+ }
+ printf("SW: %s\n", osim_print_sw(chan, msgb_apdu_sw(msg)));
+
+ hex = osmo_hexdump_nospc(msgb_apdu_de(rmsg), msgb_apdu_le(rmsg));
+ printf("Rec %03u: %s\n", i+1, hex);
+ if (f_data)
+ fprintf(f_data, "%s\n", hex);
}
break;
case EF_TYPE_TRANSP:
- if (!TLVP_PRESENT(&tp, UICC_FCP_T_FILE_SIZE))
+ if (g_class != 0xA0) {
+ if (!TLVP_PRESENT(&tp, UICC_FCP_T_FILE_SIZE))
+ goto out;
+ i = ntohs(*(uint16_t *)TLVP_VAL(&tp, UICC_FCP_T_FILE_SIZE));
+ printf("File size: %d bytes\n", i);
+ } else {
+ fprintf(stderr, "Can not determine file size, invalid EF-type!\n");
goto out;
- i = ntohs(*(uint16_t *)TLVP_VAL(&tp, UICC_FCP_T_FILE_SIZE));
- printf("File size: %d bytes\n", i);
-
+ }
for (offset = 0; offset < i-1; ) {
uint16_t remain_len = i - offset;
uint16_t read_len = OSMO_MIN(remain_len, 256);
+ const char *hex;
rmsg = read_binary(chan, offset, read_len);
- if (!rmsg)
+ if (!rmsg) {
+ if (f_data)
+ fclose(f_data);
return -EIO;
+ }
offset += read_len;
- printf("Content: %s\n",
- osmo_hexdump(msgb_apdu_de(rmsg), msgb_apdu_le(rmsg)));
+ hex = osmo_hexdump_nospc(msgb_apdu_de(rmsg), msgb_apdu_le(rmsg));
+ printf("Content: %s\n", hex);
+ if (f_data)
+ fprintf(f_data, "%s", hex);
}
break;
default:
@@ -324,18 +447,191 @@ static int dump_file(struct osim_chan_hdl *chan, uint16_t fid)
}
out:
+ if (f_data)
+ fclose(f_data);
msgb_free(msg);
return -EINVAL;
+
+}
+
+static void print_help(void)
+{
+ printf( "osmo-sim-test Usage:\n"
+ " -h --help This message\n"
+ " -n --reader-num NR Open reader number NR\n"
+ " -o --output-dir DIR To-be-created output directory for filesystem dump\n"
+ );
}
+static int readernum = 0;
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "reader-num", 1, 0, 'n' },
+ { "output-dir", 1, 0, 'o' },
+ {0,0,0,0}
+ };
+
+ c = getopt_long(argc, argv, "hn:o:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 'n':
+ readernum = atoi(optarg);
+ break;
+ case 'o':
+ g_output_dir = optarg;
+ break;
+ default:
+ exit(2);
+ break;
+ }
+ }
+
+ if (argc > optind) {
+ fprintf(stderr, "Unsupported positional arguments on command line\n");
+ exit(2);
+ }
+}
+
+
+static void mkdir_and_chdir(const char *name, mode_t mode)
+{
+ int rc;
+ rc = mkdir(name, mode);
+ if (rc < 0) {
+ fprintf(stderr, "Cannot create '%s': %s\n", name, strerror(errno));
+ exit(24);
+ }
+ rc = chdir(name);
+ if (rc < 0) {
+ fprintf(stderr, "Cannot change to just-created '%s': %s\n", name, strerror(errno));
+ exit(24);
+ }
+}
+
+
+static void iterate_fs(struct osim_chan_hdl *chan)
+{
+ const struct osim_file_desc *prev_cwd;
+ struct osim_file_desc *ofd;
+
+ /* iterate over all files in current working directory */
+ llist_for_each_entry(ofd, &chan->cwd->child_list, list) {
+ struct msgb *m;
+ char prev_dir[PATH_MAX];
+
+ printf("\n\n================ %s (%s) ==================\n",
+ ofd->short_name, ofd->long_name);
+
+ m = select_file(chan, ofd->fid);
+ if (msgb_apdu_sw(m) != 0x9000) {
+ msgb_free(m);
+ continue;
+ }
+ dump_fcp_template_msg(m);
+ msgb_free(m);
+
+ /* If this is a DF, recurse into it */
+ switch (ofd->type) {
+ case TYPE_DF:
+ /* the select above has just changed into this directory */
+ prev_cwd = chan->cwd;
+ chan->cwd = ofd;
+ if (g_output_dir) {
+ if (!getcwd(prev_dir, sizeof(prev_dir))) {
+ fprintf(stderr, "Cannot determine cwd: %s\n", strerror(errno));
+ exit(23);
+ continue;
+ }
+ mkdir_and_chdir(ofd->short_name, 0750);
+ }
+ iterate_fs(chan);
+ /* "pop" the directory from the stack */
+ chan->cwd = prev_cwd;
+ if (g_output_dir)
+ OSMO_ASSERT(chdir("..") == 0);
+ break;
+ default:
+ dump_file(chan, ofd->short_name, ofd->fid);
+ break;
+ }
+ }
+}
+
+static void iterate_apps(struct osim_chan_hdl *chan)
+{
+ struct osim_card_app_hdl *cah;
+
+ llist_for_each_entry(cah, &chan->card->apps, list) {
+ const struct osim_card_app_profile *cap = cah->prof;
+ struct msgb *msg;
+
+ if (!cap) {
+ fprintf(stderr, "Unknown AID %s; skipping\n",
+ osmo_hexdump_nospc(cah->aid, cah->aid_len));
+ continue;
+ }
+
+ msg = select_adf(chan, cah->aid, cah->aid_len);
+ if (!msg) {
+ fprintf(stderr, "Error selectiong ADF for AID %s; skipping\n",
+ osmo_hexdump_nospc(cah->aid, cah->aid_len));
+ continue;
+ }
+ printf("SW: %s\n", osim_print_sw(chan, msgb_apdu_sw(msg)));
+ chan->cur_app = cah;
+ chan->cwd = cap->adf;
+
+ if (g_output_dir)
+ mkdir_and_chdir(cap->adf->short_name, 0750);
+
+ iterate_fs(chan);
+
+ if (g_output_dir)
+ OSMO_ASSERT(chdir("..") == 0);
+ }
+}
+
+
int main(int argc, char **argv)
{
struct osim_reader_hdl *reader;
struct osim_card_hdl *card;
struct osim_chan_hdl *chan;
- struct msgb *msg;
+ int rc;
- reader = osim_reader_open(OSIM_READER_DRV_PCSC, 0, "", NULL);
+ handle_options(argc, argv);
+
+ osim_init(NULL);
+
+ if (g_output_dir) {
+ int rc;
+ rc = mkdir(g_output_dir, 0750);
+ if (rc < 0) {
+ fprintf(stderr, "Cannot create directory '%s': %s\n", g_output_dir,
+ strerror(errno));
+ exit(5);
+ }
+ rc = chdir(g_output_dir);
+ if (rc < 0) {
+ fprintf(stderr, "Cannot change to just-created directory '%s': %s\n",
+ g_output_dir, strerror(errno));
+ exit(5);
+ }
+ }
+
+ reader = osim_reader_open(OSIM_READER_DRV_PCSC, readernum, "", NULL);
if (!reader)
exit(1);
card = osim_card_open(reader, OSIM_PROTO_T0);
@@ -345,36 +641,26 @@ int main(int argc, char **argv)
if (!chan)
exit(3);
- msg = try_select_adf_usim(chan);
- if (!msg || msgb_apdu_sw(msg) != 0x9000)
+ //verify_pin(chan, 1, "1653");
+
+ rc = osim_uicc_scan_apps(chan);
+ if (rc >= 0) {
+ chan->card->prof = osim_cprof_uicc(chan->card, true);
+ chan->cwd = chan->card->prof->mf;
+ } else if (rc == -0x6e00) {
+ /* CLA not supported: must be classic SIM, not USIM */
+ g_class = 0xA0;
+ chan->card->prof = osim_cprof_sim(chan->card);
+ chan->cwd = chan->card->prof->mf;
+ } else if (rc < 0) {
exit(4);
- dump_fcp_template_msg(msg);
- msgb_free(msg);
-
- msg = select_file(chan, 0x6fc5);
- dump_fcp_template_msg(msg);
- printf("SW: %s\n", osim_print_sw(chan->card, msgb_apdu_sw(msg)));
- msgb_free(msg);
-
- verify_pin(chan, 1, "1653");
-
- msg = select_file(chan, 0x6f06);
- dump_fcp_template_msg(msg);
- msgb_free(msg);
+ }
- {
- struct osim_file_desc *ofd;
- llist_for_each_entry(ofd, &chan->cwd->child_list, list) {
- struct msgb *m;
- printf("\n\n================ %s (%s) ==================\n",
- ofd->short_name, ofd->long_name);
+ /* first iterate over normal file system */
+ iterate_fs(chan);
- m = select_file(chan, ofd->fid);
- dump_fcp_template_msg(m);
- msgb_free(m);
- dump_file(chan, ofd->fid);
- }
- }
+ /* then itereate over all apps and their file system */
+ iterate_apps(chan);
exit(0);
}
diff --git a/utils/osmo-stat-dummy/Makefile.am b/utils/osmo-stat-dummy/Makefile.am
new file mode 100644
index 00000000..0232736b
--- /dev/null
+++ b/utils/osmo-stat-dummy/Makefile.am
@@ -0,0 +1,10 @@
+if ENABLE_UTILITIES
+noinst_PROGRAMS = osmo-stat-dummy
+osmo_stat_dummy_SOURCES = osmo-stat-dummy.c
+osmo_stat_dummy_LDADD = $(TALLOC_LIBS) \
+ $(top_builddir)/src/vty/libosmovty.la \
+ $(top_builddir)/src/ctrl/libosmoctrl.la \
+ $(top_builddir)/src/core/libosmocore.la
+osmo_stat_dummy_CFLAGS = -Wall $(TALLOC_CFLAGS)
+osmo_stat_dummy_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+endif
diff --git a/utils/osmo-stat-dummy/README.md b/utils/osmo-stat-dummy/README.md
new file mode 100644
index 00000000..34ffbb4c
--- /dev/null
+++ b/utils/osmo-stat-dummy/README.md
@@ -0,0 +1,12 @@
+# Osmocom utilities
+
+* osmo-stat-dummy: utility for rate counter and statsd testing
+
+It has 2 rate counters: one ticks twice a seconds, another one can be manually updated with 'update-rate-ctr' command via vty.
+
+The raw value is sent via statsd protocol. If you install "netdata" monitoring tool than you can open http://localhost:19999 in browser
+and observe live counters monitoring under "StatsD dummy" without any additional setup.
+
+Opening osmo-stat-dummy.html in browser while both netdata and osmo-stat-dummy are running will show dimensioned (per min/hour/day) rate counters as well as raw data.
+
+The latter is handy for troubleshooting and comparing libosmocore's internal rate counter computation.
diff --git a/utils/osmo-stat-dummy/osmo-stat-dummy.c b/utils/osmo-stat-dummy/osmo-stat-dummy.c
new file mode 100644
index 00000000..37da7b32
--- /dev/null
+++ b/utils/osmo-stat-dummy/osmo-stat-dummy.c
@@ -0,0 +1,335 @@
+/* Rate counter and statsd test application */
+/* (C) 2022 by 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 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.
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/tdef_vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+
+#include "config.h"
+
+void *tall_statdummy_ctx = NULL;
+static bool quit = false;
+static bool config_given = false;
+struct rate_ctr_group *g_ctrg;
+
+enum dummy_rate_ctr_idx {
+ DUMMY_VTY = 0,
+ DUMMY_AUTO,
+};
+
+static void print_help(void)
+{
+ printf("Some useful options:\n"
+ " -h --help This text\n"
+ " -c --config-file Specify the filename of the config file\n"
+ " -V --version Print version\n"
+ "\nVTY reference generation:\n"
+ " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"
+ " --vty-ref-xml Generate the VTY reference XML output and exit.\n"
+ );
+}
+
+static void handle_long_options(const char *prog_name, const int long_option)
+{
+ static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
+
+ switch (long_option) {
+ case 1:
+ vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
+ if (vty_ref_mode < 0) {
+ fprintf(stderr, "%s: Unknown VTY reference generation mode '%s'\n", prog_name, optarg);
+ exit(2);
+ }
+ break;
+ case 2:
+ fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
+ get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
+ get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
+ vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
+ exit(0);
+ default:
+ fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
+ exit(2);
+ }
+}
+
+static char *handle_options(int argc, char **argv)
+{
+ char *config_file = NULL;
+
+ while (1) {
+ int option_idx = 0, c;
+ static int long_option = 0;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "config-file", 1, 0, 'c' },
+ { "version", 0, 0, 'V' },
+ { "vty-ref-mode", 1, &long_option, 1 },
+ { "vty-ref-xml", 0, &long_option, 2 },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "hc:V", long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 0:
+ handle_long_options(argv[0], long_option);
+ break;
+ case 'c':
+ if (config_file)
+ free(config_file);
+ config_file = optarg;
+ config_given = true;
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ default:
+ fprintf(stderr, "Unknown option '%c'\n", c);
+ exit(0);
+ break;
+ }
+ }
+
+ if (!config_file)
+ return "osmo-stat-dummy.cfg";
+
+ return config_file;
+}
+
+void sighandler(int sigset)
+{
+ if (sigset == SIGPIPE)
+ return;
+
+ fprintf(stderr, "Signal %d received.\n", sigset);
+
+ switch (sigset) {
+ case SIGINT:
+ case SIGTERM:
+ /* If another signal is received afterwards, the program
+ * is terminated without finishing shutdown process.
+ */
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGABRT, SIG_DFL);
+ signal(SIGUSR1, SIG_DFL);
+ signal(SIGUSR2, SIG_DFL);
+
+ quit = 1;
+ 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_statdummy_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_statdummy_ctx, stderr);
+ break;
+ }
+}
+
+static int rate_ctr_timer_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ uint64_t expire_count;
+ int rc;
+
+ /* check that the timer has actually expired */
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ /* read from timerfd: number of expirations of periodic timer */
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
+
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ if (expire_count > 1)
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Stats timer expire_count=%" PRIu64 ": We missed %" PRIu64 " timers\n",
+ expire_count, expire_count-1);
+
+ /* Increment the counter value */
+ rate_ctr_inc(rate_ctr_group_get_ctr(g_ctrg, DUMMY_AUTO));
+
+ return 0;
+}
+
+DEFUN(update_rate_ctr, update_rate_ctr_cmd,
+ "update-rate-ctr <0-100000>",
+ "Update dummy rate counter\n"
+ "Value to add to rate counter\n")
+{
+ rate_ctr_add(rate_ctr_group_get_ctr(g_ctrg, DUMMY_VTY), atoi(argv[0]));
+
+ return CMD_SUCCESS;
+}
+
+static int statdummy_vty_init(void)
+{
+ install_element_ve(&update_rate_ctr_cmd);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct log_info log_info = {};
+ char *config_file;
+ void *ctx = tall_statdummy_ctx = talloc_named_const(NULL, 0, "osmo-stat-dummy");
+ struct ctrl_handle *ctrl;
+ struct osmo_fd rate_ctr_timer = { .fd = -1 };
+ struct timespec ts_interval = { .tv_sec = 0, .tv_nsec = 500000000 }; /* 0.5 seconds */
+ int rc = 0;
+
+ const char vty_copyright[] =
+ "Copyright (C) 2022 by by sysmocom - s.f.m.c. GmbH\r\n"
+ "Author: Max Suraev <msuraev@sysmocom.de>\r\n"
+ "License GNU GPL version 3 or later\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+ struct vty_app_info vty_info = {
+ .name = "OsmoSTATdummy",
+ .version = PACKAGE_VERSION,
+ .copyright = vty_copyright,
+ .tall_ctx = ctx
+ };
+
+ const struct rate_ctr_desc dummy_ctr_desc[] = {
+ [DUMMY_VTY] = { "dummy:vty", "Dummy counter updated via VTY" },
+ [DUMMY_AUTO] = { "dummy:auto", "Dummy counter autoupdated via timer" },
+ };
+
+ const struct rate_ctr_group_desc dummy_ctrg_desc = {
+ "dummy",
+ "dummy stat tester",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(dummy_ctr_desc),
+ dummy_ctr_desc,
+ };
+
+ osmo_init_logging2(ctx, &log_info);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+ msgb_talloc_ctx_init(ctx, 0);
+
+ vty_init(&vty_info);
+ ctrl_vty_init(ctx);
+ logging_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+
+ config_file = handle_options(argc, argv);
+
+ statdummy_vty_init();
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ if (config_given) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+ exit(1);
+ }
+ fprintf(stderr, "No config file: '%s' Using default config.\n", config_file);
+ }
+
+ rc = telnet_init_default(ctx, NULL, -1);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ ctrl = ctrl_interface_setup(NULL, 1234, NULL);
+ if (!ctrl) {
+ fprintf(stderr, "Failed to initialize control interface. Exiting.\n");
+ exit(1);
+ }
+
+ g_ctrg = rate_ctr_group_alloc(ctx, &dummy_ctrg_desc, 0);
+ if (!g_ctrg) {
+ fprintf(stderr, "Failed to initialize rate counters. Exiting.\n");
+ return -1;
+ }
+
+ osmo_stats_init(ctx);
+ rate_ctr_init(ctx);
+
+ rc = osmo_timerfd_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Failed to setup the timer with error code %d (fd=%d)\n",
+ rc, rate_ctr_timer.fd);
+ return rc;
+ }
+
+ rc = osmo_timerfd_schedule(&rate_ctr_timer, NULL, &ts_interval);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Failed to schedule the timer with error code %d (fd=%d)\n",
+ rc, rate_ctr_timer.fd);
+ }
+
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+ signal(SIGABRT, sighandler);
+ signal(SIGUSR1, sighandler);
+ signal(SIGUSR2, sighandler);
+ osmo_init_ignore_signals();
+
+ while (!quit) {
+ osmo_select_main(0);
+ }
+
+ telnet_exit();
+
+ talloc_report_full(tall_statdummy_ctx, stderr);
+ talloc_free(tall_statdummy_ctx);
+
+ return 0;
+}
diff --git a/utils/osmo-stat-dummy/osmo-stat-dummy.cfg b/utils/osmo-stat-dummy/osmo-stat-dummy.cfg
new file mode 100644
index 00000000..559fe4ed
--- /dev/null
+++ b/utils/osmo-stat-dummy/osmo-stat-dummy.cfg
@@ -0,0 +1,40 @@
+log stderr
+ logging filter all 1
+ logging color 0
+ logging timestamp 0
+ logging print extended-timestamp 0
+ logging print category-hex 0
+ logging print category 1
+ !logging print file basename
+ ! log-levels defined by libosmocore and hence available everywhere, can be overridden by inidividual per-app configs below
+ logging level lstats notice
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+ logging level lctrl notice
+ logging level lgtp notice
+ logging level lgsup notice
+ logging level loap notice
+ logging level lss7 error
+ logging level lsccp notice
+ logging level lsua notice
+ logging level lm3ua notice
+ logging level lmgcp notice
+ logging level ljibuf notice
+ logging level lrspro notice
+stats reporter statsd
+ !use default https://learn.netdata.cloud/ statsd plugin:
+ remote-ip 127.0.0.1
+ remote-port 8125
+ level global
+ no prefix
+ enable
+stats interval 1
+line vty
+ bind 127.0.0.2 6969
+ctrl
+ bind 127.0.0.11 1234
diff --git a/utils/osmo-stat-dummy/osmo-stat-dummy.html b/utils/osmo-stat-dummy/osmo-stat-dummy.html
new file mode 100644
index 00000000..fc772573
--- /dev/null
+++ b/utils/osmo-stat-dummy/osmo-stat-dummy.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+</head>
+<body>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="raw data (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ ></div>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="aggregated per minute (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ data-method="average"
+ data-gtime="60"
+ data-units="events/min"
+ ></div>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="aggregated per hour (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ data-method="average"
+ data-gtime="3600"
+ data-units="events/hour"
+ ></div>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="aggregated per day (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ data-method="average"
+ data-gtime="86400"
+ data-units="events/day"
+ ></div>
+
+ <div data-netdata="statsd_dummy.0.dummy.vty_counter"
+ data-chart-library="dygraph"
+ data-title="raw data (updated manually via vty command)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ ></div>
+</body>
+<script type="text/javascript" src="http://localhost:19999/dashboard.js"></script>
+</html>