From 09462c3b04454b46edbac9b40128492dff01fef0 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 6 Mar 2020 18:37:51 +0100 Subject: [PATCH] initial checkin of 'osmo-rtpsource' program osmo-rtpsource is a small utility program which is generating RTP flows at a constant rate of 20ms, as required in most IP based telphony. The payload currently is hard-coded. Change-Id: Id4e292ddfd5aa58754382b2380558993b2ddf07a --- rtpsource/Makefile | 15 +++ rtpsource/ctrl_if.c | 131 ++++++++++++++++++++++ rtpsource/internal.h | 46 ++++++++ rtpsource/rtpsource.c | 248 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 440 insertions(+) create mode 100644 rtpsource/Makefile create mode 100644 rtpsource/ctrl_if.c create mode 100644 rtpsource/internal.h create mode 100644 rtpsource/rtpsource.c diff --git a/rtpsource/Makefile b/rtpsource/Makefile new file mode 100644 index 0000000..9972325 --- /dev/null +++ b/rtpsource/Makefile @@ -0,0 +1,15 @@ +OSMO_CFLAGS:=$(shell pkg-config --cflags libosmocore libosmoctrl libosmotrau) +OSMO_LIBS:=$(shell pkg-config --libs libosmocore libosmoctrl libosmotrau) + +CFLAGS:= -g -Wall $(OSMO_CFLAGS) +LIBS:= $(OSMO_LIBS) + +rtpsource: rtpsource.o ctrl_if.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $^ + +clean: + @rm -f rtpsource *.o diff --git a/rtpsource/ctrl_if.c b/rtpsource/ctrl_if.c new file mode 100644 index 0000000..708fb02 --- /dev/null +++ b/rtpsource/ctrl_if.c @@ -0,0 +1,131 @@ +/* CTRL interface of rtpsource program + * + * (C) 2020 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "internal.h" + +CTRL_CMD_DEFINE_WO_NOVRF(rtp_create, "rtp_create"); +static int set_rtp_create(struct ctrl_cmd *cmd, void *data) +{ + struct rtp_connection *conn; + const char *cname = cmd->value; + + if (find_connection_by_cname(g_rss, cname)) { + cmd->reply = "Connection already exists for cname"; + return CTRL_CMD_ERROR; + } + + conn = create_connection(g_rss, cname); + if (!conn) { + cmd->reply = "Error creating RTP connection"; + return CTRL_CMD_ERROR; + } + + /* Respond */ + cmd->reply = talloc_asprintf(cmd, "%s,%s,%d", conn->cname, conn->local_host, conn->local_port); + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(rtp_connect, "rtp_connect"); +static int set_rtp_connect(struct ctrl_cmd *cmd, void *data) +{ + struct rtp_connection *conn; + const char *cname, *remote_host, *remote_port, *pt; + char *tmp, *saveptr; + int rc; + + tmp = talloc_strdup(cmd, cmd->value); + OSMO_ASSERT(tmp); + + /* FIXME: parse command */ + cname = strtok_r(tmp, ",", &saveptr); + remote_host = strtok_r(NULL, ",", &saveptr); + remote_port = strtok_r(NULL, ",", &saveptr); + pt = strtok_r(NULL, ",", &saveptr); + + if (!cname || !remote_host || !remote_port || !pt) { + cmd->reply = "Format is cname,remote_host,remote_port,pt"; + talloc_free(tmp); + return CTRL_CMD_ERROR; + } + + conn = find_connection_by_cname(g_rss, cname); + if (!conn) { + cmd->reply = "Error finding RTP connection for connect"; + talloc_free(tmp); + return CTRL_CMD_ERROR; + } + + rc = connect_connection(conn, remote_host, atoi(remote_port), atoi(pt)); + if (rc < 0) { + cmd->reply = "Error binding RTP connection"; + talloc_free(tmp); + return CTRL_CMD_ERROR; + } + + /* Respond */ + talloc_free(tmp); + cmd->reply = talloc_asprintf(cmd, "%s,%s,%d,%d", conn->cname, conn->remote_host, + conn->remote_port, conn->rtp_pt); + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(rtp_delete, "rtp_delete"); +static int set_rtp_delete(struct ctrl_cmd *cmd, void *data) +{ + struct rtp_connection *conn; + const char *cname = cmd->value; + + conn = find_connection_by_cname(g_rss, cname); + if (!conn) { + cmd->reply = "Error finding RTP connection for delete"; + return CTRL_CMD_ERROR; + } + cmd->reply = talloc_asprintf(cmd, "%s", conn->cname); + + delete_connection(conn); + + /* Respond */ + return CTRL_CMD_REPLY; +} + + + + + +int rtpsource_ctrl_cmds_install(void) +{ + int rc; + + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rtp_create); + if (rc) + goto end; + + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rtp_connect); + if (rc) + goto end; + + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rtp_delete); + if (rc) + goto end; +end: + return rc; +} diff --git a/rtpsource/internal.h b/rtpsource/internal.h new file mode 100644 index 0000000..788b695 --- /dev/null +++ b/rtpsource/internal.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include + +enum { + DMAIN, +}; + +struct rtp_connection { + struct llist_head list; + + struct osmo_rtp_socket *rtp_sock; + char *cname; + + char *local_host; + uint16_t local_port; + + char *remote_host; + uint16_t remote_port; + + uint8_t rtp_pt; +}; + +struct rtpsource_state { + struct llist_head connections; + struct osmo_fd timer_ofd; + struct ctrl_handle *ctrl; +}; +extern struct rtpsource_state *g_rss; + +struct rtp_connection *find_connection_by_cname(struct rtpsource_state *rss, const char *cname); + +struct rtp_connection *create_connection(struct rtpsource_state *rss, const char *cname); + +int connect_connection(struct rtp_connection *conn, const char *remote_host, + uint16_t remote_port, uint8_t pt); + +void delete_connection(struct rtp_connection *conn); + + +int rtpsource_ctrl_cmds_install(void); + +#define CLOGP(conn, subsys, lvl, fmt, args ...) \ + LOGP(subsys, lvl, "[%s]: " fmt, (conn)->cname, ## args) diff --git a/rtpsource/rtpsource.c b/rtpsource/rtpsource.c new file mode 100644 index 0000000..58e6bf0 --- /dev/null +++ b/rtpsource/rtpsource.c @@ -0,0 +1,248 @@ +/* rtpsource program: RTP load generator + * + * This program binds a CTRL interface to 127.0.0.1:11111 and waits for + * an external entity to issue CTRL commands, such as rtp_create, rtp_connect + * and rtp_delete. Those commands are used to create+bind, connect and destroy + * local RTP connections. + * + * Each connection will send a RTP frame with dummy payload every 20ms. + * + * This is useful for load testing scenarios + * + * (C) 2020 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include "internal.h" + + +/* find a connection based on its CNAME */ +struct rtp_connection *find_connection_by_cname(struct rtpsource_state *rss, const char *cname) +{ + struct rtp_connection *conn; + llist_for_each_entry(conn, &rss->connections, list) { + if (!strcmp(cname, conn->cname)) + return conn; + } + return NULL; +} + +/* create a new RTP connection for given CNAME; includes binding of local RTP port */ +struct rtp_connection *create_connection(struct rtpsource_state *rss, const char *cname) +{ + struct rtp_connection *conn; + const char *host; + int port; + int rc; + + OSMO_ASSERT(!find_connection_by_cname(rss, cname)); + + conn = talloc_zero(rss, struct rtp_connection); + OSMO_ASSERT(conn); + conn->cname = talloc_strdup(conn, cname); + + conn->rtp_sock = osmo_rtp_socket_create(conn, OSMO_RTP_F_POLL); + OSMO_ASSERT(conn->rtp_sock); + + rc = osmo_rtp_socket_bind(conn->rtp_sock, "127.23.23.23", -1); + OSMO_ASSERT(rc == 0); + + rc = osmo_rtp_get_bound_addr(conn->rtp_sock, &host, &port); + OSMO_ASSERT(rc == 0); + OSMO_ASSERT(port >= 0 && port <= 0xffff); + conn->local_port = port; + conn->local_host = talloc_strdup(conn, host); + + osmo_rtp_set_source_desc(conn->rtp_sock, conn->cname, "rtpsource", NULL, NULL, + NULL, "osmo-rtpsource", NULL); + + llist_add_tail(&conn->list, &rss->connections); + + CLOGP(conn, DMAIN, LOGL_INFO, "Created RTP connection; local=%s:%u\n", + conn->local_host, conn->local_port); + + + return conn; +} + +/* connect a RTP connection to a given remote peer */ +int connect_connection(struct rtp_connection *conn, const char *remote_host, + uint16_t remote_port, uint8_t pt) +{ + int rc; + + conn->remote_host = talloc_strdup(conn, remote_host); + conn->remote_port = remote_port; + conn->rtp_pt = pt; + + rc = osmo_rtp_socket_connect(conn->rtp_sock, conn->remote_host, conn->remote_port); + OSMO_ASSERT(rc == 0); + rc = osmo_rtp_socket_set_pt(conn->rtp_sock, conn->rtp_pt); + OSMO_ASSERT(rc == 0); + + CLOGP(conn, DMAIN, LOGL_INFO, "Connected RTP connection; remote=%s:%u\n", + conn->remote_host, conn->remote_port); + + return 0; +} + +/* delete a RTP connection */ +void delete_connection(struct rtp_connection *conn) +{ + char *prefix = talloc_asprintf(conn, "[%s]: STATS: ", conn->cname); + osmo_rtp_socket_log_stats(conn->rtp_sock, DMAIN, LOGL_INFO, prefix); + talloc_free(prefix); + osmo_rtp_socket_free(conn->rtp_sock); + conn->rtp_sock = NULL; + + CLOGP(conn, DMAIN, LOGL_INFO, "Deleted RTP connection\n"); + + llist_del(&conn->list); + talloc_free(conn); +} + + + + +/* called every 20ms at timerfd expiration */ +static int timerfd_cb(struct osmo_fd *ofd, unsigned int priv_nr) +{ + struct rtpsource_state *rss = ofd->data; + struct rtp_connection *conn; + uint64_t expire_count; + int rc; + + /* 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; + + if (expire_count > 1) + LOGP(DMAIN, LOGL_ERROR, "Timer expire_count=%"PRIu64" != 1\n", expire_count); + + /* iterate over all RTP connections and send one frame each */ + llist_for_each_entry(conn, &rss->connections, list) { + int i; + /* TODO: have different sources (file+name, ...) */ + uint8_t payload[33]; + memset(payload, 0, sizeof(payload)); + payload[0] = (payload[0] & 0x0f) | 0xD0; /* mask in first four bit for FR */ + osmo_rtp_send_frame_ext(conn->rtp_sock, payload, sizeof(payload), 160, false); + /* make sure RTP clock advances correctly, even if we missed transmit of some */ + for (i = 1; i < expire_count; i++) + osmo_rtp_skipped_frame(conn->rtp_sock, 160); + } + return 0; +} + +static const struct log_info_cat rtpsource_cat[] = { + [DMAIN] = { + .name = "DMAIN", + .description ="Main Program", + .enabled = 1, + .loglevel = LOGL_INFO, + }, +}; + +const struct log_info rtpsource_log_info = { + .filter_fn = NULL, + .cat = rtpsource_cat, + .num_cat = ARRAY_SIZE(rtpsource_cat), +}; + +struct rtpsource_state *g_rss; +static void *g_tall_ctx; + +static void signal_handler(int signal) +{ + switch (signal) { + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report_full(g_tall_ctx, stderr); + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + struct timespec interval = { + .tv_sec = 0, + .tv_nsec = 20*1000*1000, /* every 20ms */ + }; + int rc; + + signal(SIGUSR1, &signal_handler); + signal(SIGABRT, &signal_handler); + + talloc_enable_null_tracking(); + g_tall_ctx = talloc_named_const(NULL, 1, "rtpsource"); + OSMO_ASSERT(g_tall_ctx); + + msgb_talloc_ctx_init(g_tall_ctx, 0); + //osmo_signal_talloc_ctx_init(g_tall_ctx); + osmo_init_logging2(g_tall_ctx, &rtpsource_log_info); + osmo_fsm_log_timeouts(true); + osmo_fsm_log_addr(true); + osmo_stats_init(g_tall_ctx); + osmo_rtp_init(g_tall_ctx); + + g_rss = talloc_zero(g_tall_ctx, struct rtpsource_state); + OSMO_ASSERT(g_rss); + INIT_LLIST_HEAD(&g_rss->connections); + + /* Create CTRL interface */ + //g_rss->ctrl = ctrl_interface_setup_dynip(g_rss, ctrl_vty_get_bind_addr(), 11111, NULL); + g_rss->ctrl = ctrl_interface_setup_dynip(g_rss, "127.0.0.1", 11111, NULL); + OSMO_ASSERT(g_rss->ctrl); + rc = rtpsource_ctrl_cmds_install(); + OSMO_ASSERT(rc == 0); + + /* create + register timerfd to expire every 20ms */ + g_rss->timer_ofd.fd = -1; + rc = osmo_timerfd_setup(&g_rss->timer_ofd, timerfd_cb, g_rss); + OSMO_ASSERT(rc == 0); + + rc = osmo_timerfd_schedule(&g_rss->timer_ofd, NULL, &interval); + OSMO_ASSERT(rc == 0); + + while (1) { + osmo_select_main(0); + } +}