diff options
Diffstat (limited to 'utils')
-rw-r--r-- | utils/Makefile.am | 34 | ||||
-rw-r--r-- | utils/conv_codes_gsm.py | 87 | ||||
-rw-r--r-- | utils/conv_gen.py | 4 | ||||
-rw-r--r-- | utils/osmo-aka-verify.c | 249 | ||||
-rw-r--r-- | utils/osmo-arfcn.c | 6 | ||||
-rw-r--r-- | utils/osmo-auc-gen.c | 29 | ||||
-rw-r--r-- | utils/osmo-config-merge.c | 4 | ||||
-rw-r--r-- | utils/osmo-ns-dummy-vty.c | 325 | ||||
-rw-r--r-- | utils/osmo-ns-dummy.c | 316 | ||||
-rw-r--r-- | utils/osmo-sim-test.c | 4 | ||||
-rw-r--r-- | utils/osmo-stat-dummy/Makefile.am | 10 | ||||
-rw-r--r-- | utils/osmo-stat-dummy/README.md | 12 | ||||
-rw-r--r-- | utils/osmo-stat-dummy/osmo-stat-dummy.c | 335 | ||||
-rw-r--r-- | utils/osmo-stat-dummy/osmo-stat-dummy.cfg | 40 | ||||
-rw-r--r-- | utils/osmo-stat-dummy/osmo-stat-dummy.html | 59 |
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> |