From a9370b6b65e3610d78cbf2b805220df923bc2cd5 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Wed, 7 Sep 2016 13:39:07 +0200 Subject: libmgcp: add mgcpgw client API Add an API to send MGCP messages to an MGCP GW, from the perspective of an MSC instructing the GW to setup RTP streams. Rationale: the mgcp_protocol.h is mostly for the MGCP GW itself, other implementations forward incoming MGCP messages. So a simpler approach for an MGCP GW client is useful. Add general VTY commands that can be used to configure mgcpgw_client. osmo-cscn is going to use this to route RTP streams (for 3G at first). Change-Id: I6fe365c4c89207f2172943cc456b508a207b1135 --- openbsc/include/openbsc/Makefile.am | 1 + openbsc/include/openbsc/mgcpgw_client.h | 47 +++++ openbsc/src/libmgcp/Makefile.am | 2 + openbsc/src/libmgcp/mgcpgw_client.c | 309 ++++++++++++++++++++++++++++++++ openbsc/src/libmgcp/mgcpgw_client_vty.c | 116 ++++++++++++ 5 files changed, 475 insertions(+) create mode 100644 openbsc/include/openbsc/mgcpgw_client.h create mode 100644 openbsc/src/libmgcp/mgcpgw_client.c create mode 100644 openbsc/src/libmgcp/mgcpgw_client_vty.c diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am index 3f6ba412a..ac3a2d081 100644 --- a/openbsc/include/openbsc/Makefile.am +++ b/openbsc/include/openbsc/Makefile.am @@ -48,6 +48,7 @@ noinst_HEADERS = \ mgcp.h \ mgcp_internal.h \ mgcp_transcode.h \ + mgcpgw_client.h \ misdn.h \ mncc.h \ mncc_int.h \ diff --git a/openbsc/include/openbsc/mgcpgw_client.h b/openbsc/include/openbsc/mgcpgw_client.h new file mode 100644 index 000000000..c21011898 --- /dev/null +++ b/openbsc/include/openbsc/mgcpgw_client.h @@ -0,0 +1,47 @@ +#pragma once + +#include +enum mgcp_connection_mode; + +struct msgb; +struct mgcpgw_client; + +#define MGCPGW_CLIENT_LOCAL_ADDR_DEFAULT "0.0.0.0" +#define MGCPGW_CLIENT_LOCAL_PORT_DEFAULT 0 +#define MGCPGW_CLIENT_REMOTE_ADDR_DEFAULT "127.0.0.1" +#define MGCPGW_CLIENT_REMOTE_PORT_DEFAULT 2427 + +typedef void (* mgcp_rx_cb_t )(struct msgb *msg, void *priv); + +struct mgcpgw_client_conf { + const char *local_addr; + int local_port; + const char *remote_addr; + int remote_port; +}; + +void mgcpgw_client_conf_init(struct mgcpgw_client_conf *conf); + +struct mgcpgw_client *mgcpgw_client_init(void *ctx, + struct mgcpgw_client_conf *conf, + mgcp_rx_cb_t rx_cb, void *rx_cb_priv); + +const char *mgcpgw_client_remote_addr_str(struct mgcpgw_client *mgcp); +uint16_t mgcpgw_client_remote_port(struct mgcpgw_client *mgcp); +uint32_t mgcpgw_client_remote_addr_n(struct mgcpgw_client *mgcp); + +unsigned int mgcpgw_client_next_endpoint(struct mgcpgw_client *client); + +int mgcpgw_client_tx_crcx(struct mgcpgw_client *client, + uint16_t rtp_endpoint, unsigned int call_id, + enum mgcp_connection_mode mode); +int mgcpgw_client_tx_mdcx(struct mgcpgw_client *client, uint16_t rtp_endpoint, + const char *rtp_conn_addr, uint16_t rtp_port, + enum mgcp_connection_mode mode); + +int mgcpgw_client_tx_str(struct mgcpgw_client *mgcp, const char *fmt, ...); +int mgcpgw_client_tx_buf(struct mgcpgw_client *mgcp, const char *buf, int len); +int mgcpgw_client_tx(struct mgcpgw_client *mgcp, struct msgb *msg); + +void mgcpgw_client_vty_init(int node, struct mgcpgw_client_conf *conf); +int mgcpgw_client_config_write(struct vty *vty, const char *indent); diff --git a/openbsc/src/libmgcp/Makefile.am b/openbsc/src/libmgcp/Makefile.am index 34a8fb743..5d7844da4 100644 --- a/openbsc/src/libmgcp/Makefile.am +++ b/openbsc/src/libmgcp/Makefile.am @@ -36,6 +36,8 @@ libmgcp_a_SOURCES = \ mgcp_vty.c \ mgcp_osmux.c \ mgcp_sdp.c \ + mgcpgw_client.c \ + mgcpgw_client_vty.c \ $(NULL) if BUILD_MGCP_TRANSCODING libmgcp_a_SOURCES += \ diff --git a/openbsc/src/libmgcp/mgcpgw_client.c b/openbsc/src/libmgcp/mgcpgw_client.c new file mode 100644 index 000000000..025bed136 --- /dev/null +++ b/openbsc/src/libmgcp/mgcpgw_client.c @@ -0,0 +1,309 @@ +/* mgcp_utils - common functions to setup an MGCP connection + */ +/* (C) 2016 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 Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +struct mgcpgw_client { + struct mgcpgw_client_conf actual; + uint32_t remote_addr; + struct osmo_wqueue wq; + mgcp_rx_cb_t rx_cb; + void *rx_cb_priv; + unsigned int next_trans_id; + uint16_t next_endpoint; +}; + +void mgcpgw_client_conf_init(struct mgcpgw_client_conf *conf) +{ + /* NULL and -1 default to MGCPGW_CLIENT_*_DEFAULT values */ + *conf = (struct mgcpgw_client_conf){ + .local_addr = NULL, + .local_port = -1, + .remote_addr = NULL, + .remote_port = -1, + }; +} + +unsigned int mgcpgw_client_next_endpoint(struct mgcpgw_client *client) +{ + return client->next_endpoint ++; +} + +static int mgcp_do_read(struct osmo_fd *fd) +{ + struct mgcpgw_client *mgcp = fd->data; + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(4096, 128, "mgcp_from_gw"); + if (!msg) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n"); + return -1; + } + + ret = read(fd->fd, msg->data, 4096 - 128); + if (ret <= 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno)); + msgb_free(msg); + return -1; + } else if (ret > 4096 - 128) { + LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret); + msgb_free(msg); + return -1; + } + + msg->l2h = msgb_put(msg, ret); + if (mgcp->rx_cb) + mgcp->rx_cb(msg, mgcp->rx_cb_priv); + return 0; +} + +static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg) +{ + int ret; + static char strbuf[4096]; + unsigned int l = msg->len < sizeof(strbuf)-1 ? msg->len : sizeof(strbuf)-1; + strncpy(strbuf, (const char*)msg->data, l); + strbuf[l] = '\0'; + DEBUGP(DMGCP, "Tx MGCP msg to MGCP GW: '%s'\n", strbuf); + + LOGP(DMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len); + + ret = write(fd->fd, msg->data, msg->len); + if (ret != msg->len) + LOGP(DMGCP, LOGL_ERROR, "Failed to forward message to MGCP" + " GW: %s\n", strerror(errno)); + + return ret; +} + +struct mgcpgw_client *mgcpgw_client_init(void *ctx, + struct mgcpgw_client_conf *conf, + mgcp_rx_cb_t rx_cb, void *rx_cb_priv) +{ + int on; + struct sockaddr_in addr; + struct mgcpgw_client *mgcp; + struct osmo_wqueue *wq; + + mgcp = talloc_zero(ctx, struct mgcpgw_client); + + mgcp->next_trans_id = 1; + mgcp->next_endpoint = 1; + + mgcp->actual.local_addr = conf->local_addr ? conf->local_addr : + MGCPGW_CLIENT_LOCAL_ADDR_DEFAULT; + mgcp->actual.local_port = conf->local_port >= 0 ? (uint16_t)conf->local_port : + MGCPGW_CLIENT_LOCAL_PORT_DEFAULT; + + mgcp->actual.remote_addr = conf->remote_addr ? conf->remote_addr : + MGCPGW_CLIENT_REMOTE_ADDR_DEFAULT; + mgcp->actual.remote_port = conf->remote_port >= 0 ? (uint16_t)conf->remote_port : + MGCPGW_CLIENT_REMOTE_PORT_DEFAULT; + + mgcp->rx_cb = rx_cb; + mgcp->rx_cb_priv = rx_cb_priv; + wq = &mgcp->wq; + + wq->bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (wq->bfd.fd < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno); + goto error_free; + } + + on = 1; + if (setsockopt(wq->bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to initialize socket for MGCP GW: %s\n", + strerror(errno)); + goto error_close_fd; + } + + /* bind socket */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + inet_aton(mgcp->actual.local_addr, &addr.sin_addr); + addr.sin_port = htons(mgcp->actual.local_port); + if (bind(wq->bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to bind for MGCP GW to %s %u\n", + mgcp->actual.local_addr, mgcp->actual.local_port); + goto error_close_fd; + } + + /* connect to the remote */ + inet_aton(mgcp->actual.remote_addr, &addr.sin_addr); + addr.sin_port = htons(mgcp->actual.remote_port); + if (connect(wq->bfd.fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to connect to MGCP GW at %s %u: %s\n", + mgcp->actual.remote_addr, mgcp->actual.remote_port, + strerror(errno)); + goto error_close_fd; + } + + mgcp->remote_addr = htonl(addr.sin_addr.s_addr); + + osmo_wqueue_init(wq, 10); + wq->bfd.when = BSC_FD_READ; + wq->bfd.data = mgcp; + wq->read_cb = mgcp_do_read; + wq->write_cb = mgcp_do_write; + + if (osmo_fd_register(&wq->bfd) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n"); + goto error_close_fd; + } + LOGP(DMGCP, LOGL_INFO, "MGCP GW connection: %s:%u -> %s:%u\n", + mgcp->actual.local_addr, mgcp->actual.local_port, + mgcp->actual.remote_addr, mgcp->actual.remote_port); + + return mgcp; +error_close_fd: + close(wq->bfd.fd); + wq->bfd.fd = -1; +error_free: + talloc_free(mgcp); + return NULL; +} + +const char *mgcpgw_client_remote_addr_str(struct mgcpgw_client *mgcp) +{ + return mgcp->actual.remote_addr; +} + +uint16_t mgcpgw_client_remote_port(struct mgcpgw_client *mgcp) +{ + return mgcp->actual.remote_port; +} + +/* Return the MGCP GW binary IPv4 address in network byte order. */ +uint32_t mgcpgw_client_remote_addr_n(struct mgcpgw_client *mgcp) +{ + return mgcp->remote_addr; +} + +int mgcpgw_client_tx(struct mgcpgw_client *mgcp, struct msgb *msg) +{ + int rc; + + if (msgb_l2len(msg) > 4096) { + LOGP(DMGCP, LOGL_ERROR, + "Cannot send, MGCP message too large: %u\n", + msgb_l2len(msg)); + msgb_free(msg); + return -EINVAL; + } + + rc = osmo_wqueue_enqueue(&mgcp->wq, msg); + if (rc) { + LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW\n"); + msgb_free(msg); + return rc; + } else + LOGP(DMGCP, LOGL_INFO, "Queued %u bytes for MGCP GW\n", + msgb_l2len(msg)); + return 0; +} + +int mgcpgw_client_tx_buf(struct mgcpgw_client *mgcp, const char *buf, int len) +{ + struct msgb *msg; + + if (len > (4096 - 128)) { + LOGP(DMGCP, LOGL_ERROR, "Cannot send to MGCP GW:" + " message too large: %d\n", len); + return -ENOTSUP; + } + + msg = msgb_alloc_headroom(4096, 128, "MGCP Tx"); + OSMO_ASSERT(msg); + + char *dst = (char*)msgb_put(msg, len); + memcpy(dst, buf, len); + msg->l2h = msg->data; + + return mgcpgw_client_tx(mgcp, msg); +} + +int mgcpgw_client_tx_str(struct mgcpgw_client *mgcp, const char *fmt, ...) +{ + char compose[4096 - 128]; + va_list ap; + int len; + OSMO_ASSERT(fmt); + + va_start(ap, fmt); + len = vsnprintf(compose, sizeof(compose), fmt, ap); + va_end(ap); + if (len >= sizeof(compose)) + return -EMSGSIZE; + if (len < 1) + return -EIO; + return mgcpgw_client_tx_buf(mgcp, compose, len); +} + +int mgcpgw_client_tx_crcx(struct mgcpgw_client *client, + uint16_t rtp_endpoint, unsigned int call_id, + enum mgcp_connection_mode mode) +{ + return mgcpgw_client_tx_str(client, + "CRCX %u %x@mgw MGCP 1.0\r\n" + "C: %x\r\n" + "L: p:20, a:AMR, nt:IN\r\n" + "M: %s\r\n" + , + client->next_trans_id ++, + rtp_endpoint, + call_id, + mgcp_cmode_name(mode)); +} + +int mgcpgw_client_tx_mdcx(struct mgcpgw_client *client, uint16_t rtp_endpoint, + const char *rtp_conn_addr, uint16_t rtp_port, + enum mgcp_connection_mode mode) +{ + return mgcpgw_client_tx_str(client, + "MDCX %u %x@mgw MGCP 1.0\r\n" + "M: %s\r\n" + "\r\n" + "c=IN IP4 %s\r\n" + "m=audio %u RTP/AVP 255\r\n" + , + client->next_trans_id ++, + rtp_endpoint, + mgcp_cmode_name(mode), + rtp_conn_addr, + rtp_port); +} diff --git a/openbsc/src/libmgcp/mgcpgw_client_vty.c b/openbsc/src/libmgcp/mgcpgw_client_vty.c new file mode 100644 index 000000000..a42ee4e5d --- /dev/null +++ b/openbsc/src/libmgcp/mgcpgw_client_vty.c @@ -0,0 +1,116 @@ +/* MGCPGW client interface to quagga VTY */ +/* (C) 2016 by sysmocom s.m.f.c. GmbH + * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c) + * (C) 2009 by Harald Welte + * (C) 2009-2011 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include + +#include +#include + +#define MGCPGW_STR "MGCP gateway configuration for RTP streams\n" + +struct mgcpgw_client_conf *global_mgcpgw_client_conf = NULL; + +DEFUN(cfg_mgcpgw_local_ip, cfg_mgcpgw_local_ip_cmd, + "mgcpgw local-ip A.B.C.D", + MGCPGW_STR "local bind to connect to MGCP gateway with\n" + "local bind IP address\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->local_addr = + talloc_strdup(gsmnet_from_vty(vty), argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcpgw_local_port, cfg_mgcpgw_local_port_cmd, + "mgcpgw local-port <0-65535>", + MGCPGW_STR "local bind to connect to MGCP gateway with\n" + "local bind port\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->local_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcpgw_remote_ip, cfg_mgcpgw_remote_ip_cmd, + "mgcpgw remote-ip A.B.C.D", + MGCPGW_STR "remote bind to connect to MGCP gateway with\n" + "remote bind IP address\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->remote_addr = + talloc_strdup(gsmnet_from_vty(vty), argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcpgw_remote_port, cfg_mgcpgw_remote_port_cmd, + "mgcpgw remote-port <0-65535>", + MGCPGW_STR "remote bind to connect to MGCP gateway with\n" + "remote bind port\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->remote_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +int mgcpgw_client_config_write(struct vty *vty, const char *indent) +{ + const char *addr; + int port; + + addr = global_mgcpgw_client_conf->local_addr; + if (addr) + vty_out(vty, "%smgcpgw local-ip %s%s", indent, addr, + VTY_NEWLINE); + port = global_mgcpgw_client_conf->local_port; + if (port >= 0) + vty_out(vty, "%smgcpgw local-port %u%s", indent, + (uint16_t)port, VTY_NEWLINE); + + addr = global_mgcpgw_client_conf->remote_addr; + if (addr) + vty_out(vty, "%smgcpgw remote-ip %s%s", indent, addr, + VTY_NEWLINE); + port = global_mgcpgw_client_conf->remote_port; + if (port >= 0) + vty_out(vty, "%smgcpgw remote-port %u%s", indent, + (uint16_t)port, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +void mgcpgw_client_vty_init(int node, struct mgcpgw_client_conf *conf) +{ + global_mgcpgw_client_conf = conf; + + install_element(node, &cfg_mgcpgw_local_ip_cmd); + install_element(node, &cfg_mgcpgw_local_port_cmd); + install_element(node, &cfg_mgcpgw_remote_ip_cmd); + install_element(node, &cfg_mgcpgw_remote_port_cmd); +} -- cgit v1.2.3