aboutsummaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/Makefile.am34
-rw-r--r--utils/conv_codes_gsm.py87
-rw-r--r--utils/conv_gen.py4
-rw-r--r--utils/osmo-aka-verify.c249
-rw-r--r--utils/osmo-arfcn.c6
-rw-r--r--utils/osmo-auc-gen.c29
-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.c4
-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
15 files changed, 1484 insertions, 30 deletions
diff --git a/utils/Makefile.am b/utils/Makefile.am
index 653b7190..6e11dcd9 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 $(PTHREAD_CFLAGS)
-LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la $(PTHREAD_LIBS)
+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_arfcn_SOURCES = osmo-arfcn.c
osmo_auc_gen_SOURCES = osmo-auc-gen.c
+osmo_aka_verify_SOURCES = osmo-aka-verify.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)
endif
endif
+
+if ENABLE_EXT_TESTS
+SUBDIRS = \
+ osmo-stat-dummy \
+ $(NULL)
+
+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 \
+ $(top_builddir)/src/gsm/libosmogsm.la
+osmo_ns_dummy_CFLAGS = $(TALLOC_CFLAGS)
+endif
+endif
diff --git a/utils/conv_codes_gsm.py b/utils/conv_codes_gsm.py
index 42f340b9..a6e471e3 100644
--- a/utils/conv_codes_gsm.py
+++ b/utils/conv_codes_gsm.py
@@ -42,6 +42,93 @@ 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/F4.8 definition
+ ConvolutionalCode(
+ 152,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_f48",
+ description = [
+ "TCH/F4.8 convolutional code:",
+ "152 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,
diff --git a/utils/conv_gen.py b/utils/conv_gen.py
index d2eda152..27ffe625 100644
--- a/utils/conv_gen.py
+++ b/utils/conv_gen.py
@@ -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/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 5f138f8c..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,7 +93,7 @@ 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:udh")) != -1) {
diff --git a/utils/osmo-auc-gen.c b/utils/osmo-auc-gen.c
index 65cfa310..e3e1b431 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-2021 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) {
@@ -70,7 +85,7 @@ static struct osmo_sub_auth_data test_aud = {
.algo = OSMO_AUTH_ALG_NONE,
};
-static void help()
+static void help(void)
{
int alg;
printf( "-2 --2g\tUse 2G (GSM) authentication\n"
@@ -207,7 +222,7 @@ int main(int argc, char **argv)
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':
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 ae55b83e..2c6c7641 100644
--- a/utils/osmo-sim-test.c
+++ b/utils/osmo-sim-test.c
@@ -12,10 +12,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>
diff --git a/utils/osmo-stat-dummy/Makefile.am b/utils/osmo-stat-dummy/Makefile.am
new file mode 100644
index 00000000..f1faa3d5
--- /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 = $(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) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOCTRL_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>