/* SPDX-License-Identifier: GPL-2.0 */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal.h" #include "netns.h" #include "gtp.h" static void *g_tall_ctx; static char *g_config_file = "osmo-uecups-daemon.cfg"; static int g_daemonize; extern struct vty_app_info g_vty_info; #include static void sigchild_cb(struct osmo_signalfd *osfd, const struct signalfd_siginfo *fdsi) { struct gtp_daemon *d = osfd->data; int pid, status; OSMO_ASSERT(fdsi->ssi_signo == SIGCHLD); /* it is known that classic signals coalesce: If you get multiple signals of the * same type before a process is scheduled, the subsequent signals are dropped. This * makes sense for SIGINT or something like this, but for SIGCHLD carrying the PID of * the terminated process, it doesn't really. Linux had the chance to fix this when * introducing signalfd() - but the developers decided not to fix it. So the signalfd_siginfo * contains the PID of one process that terminated - but there may be any number of other * processes that also have terminated, and for which we don't get events this way. */ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) child_terminated(d, pid, status); } static void signal_cb(struct osmo_signalfd *osfd, const struct signalfd_siginfo *fdsi) { switch (fdsi->ssi_signo) { case SIGCHLD: sigchild_cb(osfd, fdsi); break; case SIGUSR1: talloc_report_full(g_tall_ctx, stderr); break; default: break; } } static const struct log_info_cat log_categories[] = { [DTUN] = { .name ="DTUN", .description = "Tunnel interface (tun device)", .enabled = 1, .loglevel = LOGL_INFO, }, [DEP] = { .name = "DEP", .description = "GTP endpoint (UDP socket)", .enabled = 1, .loglevel = LOGL_INFO, }, [DGT] = { .name = "DGT", .description = "GTP tunnel (session)", .enabled = 1, .loglevel = LOGL_INFO, }, [DUECUPS] = { .name = "DUECUPS", .description = "UE Control User Plane Separation", .enabled = 1, .loglevel = LOGL_DEBUG, }, }; static const struct log_info log_info = { .cat = log_categories, .num_cat = ARRAY_SIZE(log_categories), }; static void print_help() { printf("Some useful options:\n"); printf(" -h --help is printing this text.\n"); printf(" -c --config-file filename The config file to use.\n"); printf(" -s --disable-color\n"); printf(" -D --daemonize Fork the process into a background daemon\n"); printf(" -V --version Print the version number\n"); printf("\nVTY reference generation:\n"); printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"); printf(" --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_index = 0, c; static int long_option = 0; static struct option long_options[] = { {"help", 0, 0, 'h'}, {"config-file", 1, 0, 'c'}, {"daemonize", 0, 0, 'D'}, {"version", 0, 0, 'V'}, {"disable-color", 0, 0, 's'}, {"vty-ref-mode", 1, &long_option, 1}, {"vty-ref-xml", 0, &long_option, 2}, {0, 0, 0, 0}, }; c = getopt_long(argc, argv, "hc:sVD", long_options, &option_index); 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': g_config_file = talloc_strdup(g_tall_ctx, optarg); break; case 's': log_set_use_color(osmo_stderr_target, 0); break; case 'V': print_version(1); exit(0); break; case 'D': g_daemonize = 1; break; default: /* ignore */ break; }; } if (argc > optind) { fprintf(stderr, "Unsupported positional arguments on command line\n"); exit(2); } } int main(int argc, char **argv) { int rc; g_tall_ctx = talloc_named_const(NULL, 0, "root"); g_vty_info.tall_ctx = g_tall_ctx; osmo_init_ignore_signals(); osmo_init_logging2(g_tall_ctx, &log_info); log_enable_multithread(); g_daemon = gtp_daemon_alloc(g_tall_ctx); OSMO_ASSERT(g_daemon); msgb_talloc_ctx_init(g_tall_ctx, 10); osmo_stats_init(g_tall_ctx); vty_init(&g_vty_info); logging_vty_add_cmds(); osmo_talloc_vty_add_cmds(); osmo_stats_vty_add_cmds(); rate_ctr_init(g_tall_ctx); gtpud_vty_init(); handle_options(argc, argv); init_netns(); rc = vty_read_config_file(g_config_file, NULL); if (rc < 0) { fprintf(stderr, "Failed to open config file: '%s'\n", g_config_file); exit(2); } rc = telnet_init_default(g_daemon, NULL, OSMO_VTY_PORT_UECUPS); if (rc < 0) exit(1); /* UECUPS socket for control from control plane side */ g_daemon->cups_link = cups_srv_link_create(g_daemon); if (!g_daemon->cups_link) { fprintf(stderr, "Failed to create CUPS socket %s:%u (%s)\n", g_daemon->cfg.cups_local_ip, g_daemon->cfg.cups_local_port, strerror(errno)); exit(1); } /* block SIGCHLD via normal delivery; redirect it to signalfd */ sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGCHLD); sigaddset(&sigset, SIGUSR1); sigprocmask(SIG_BLOCK, &sigset, NULL); g_daemon->signalfd = osmo_signalfd_setup(g_daemon, sigset, signal_cb, g_daemon); osmo_init_ignore_signals(); if (g_daemonize) { rc = osmo_daemonize(); if (rc < 0) { perror("Error during daemonize"); exit(1); } } while (1) { osmo_select_main(0); } }