jolly has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-mgw/+/33548 )
Change subject: ASCI: Support conference briding with 1..n connections ......................................................................
ASCI: Support conference briding with 1..n connections
An RTP bridge may have 1 or many endpoints. Each endpoint may receive, send or echo audio back. Because transcoding is not supported, it must be assumed that only connection receives RTP in a conference with more than 2 connections.
A connection that receives RTP into the endpont will have "send" pointer set to (one of) the other connection that shall send out RTP. All connection that send RTP have "send_next" pointer set to the next or first connection that sends RTP. They are linked in a ring.
Regular bridge with two connections: Each connection forwards audio towards the other connection and only towards the other connection. The "send_next" pointers are set to NULL or pointing to the other endpoint.
connection A connection B ("sendrecv") ("sendrecv") conn->send ---------------------------> <------------------------------------- conn->send conn->send_next ----------------------> <------------------------------------- conn->send_next
Conference bridge with three connections: Each connection forwards audio to itself and also towards all other connection.
connection A connection B connection C ("confecho") ("confecho") ("confecho"); conn->send --\ conn->send --\ conn->send --\ <-----------/ <-----------/ <-----------/ conn->send_next --> conn->send_next --> <------------------------------------- conn->send_next
The same conference bridge but without echo: Each connection forwards auto to all other connection but not to itself.
connection A connection B connection C ("confecho") ("confecho") ("confecho"); conn->send -------> conn->send -------> <------------------------------------- conn->send conn->send_next --> conn->send_next --> <------------------------------------- conn->send_next
Change-Id: Ic99a55ab5a3a6170e940403fadd52697e99f2f3a --- M include/osmocom/mgcp/mgcp_trunk.h M src/libosmo-mgcp/mgcp_endp.c M src/libosmo-mgcp/mgcp_network.c M src/libosmo-mgcp/mgcp_protocol.c M src/libosmo-mgcp/mgcp_trunk.c 5 files changed, 190 insertions(+), 60 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-mgw refs/changes/48/33548/1
diff --git a/include/osmocom/mgcp/mgcp_trunk.h b/include/osmocom/mgcp/mgcp_trunk.h index e608161..7dff2b5 100644 --- a/include/osmocom/mgcp/mgcp_trunk.h +++ b/include/osmocom/mgcp/mgcp_trunk.h @@ -79,6 +79,8 @@ struct mgcp_trunk *mgcp_trunk_by_name(const struct mgcp_config *cfg, const char *epname); int e1_trunk_nr_from_epname(unsigned int *trunk_nr, const char *epname); struct mgcp_trunk *mgcp_trunk_by_line_num(const struct mgcp_config *cfg, unsigned int num); +int mgcp_trunk_connect(struct mgcp_endpoint *endp, struct mgcp_conn *exclude_conn); +void mgcp_trunk_endp_update(struct mgcp_endpoint *endp);
/* The virtual trunk is always created on trunk id 0 for historical reasons, * use this define constant as ID when allocating a virtual trunk. Other diff --git a/src/libosmo-mgcp/mgcp_endp.c b/src/libosmo-mgcp/mgcp_endp.c index 20088b7..4114cf9 100644 --- a/src/libosmo-mgcp/mgcp_endp.c +++ b/src/libosmo-mgcp/mgcp_endp.c @@ -629,7 +629,7 @@ /* Allocate resources */ switch (endp->trunk->trunk_type) { case MGCP_TRUNK_VIRTUAL: - /* No updating initaliziation required for virtual endpoints. */ + mgcp_trunk_endp_update(endp); break; case MGCP_TRUNK_E1: mgcp_e1_endp_update(endp); diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c index 8850670..a03f697 100644 --- a/src/libosmo-mgcp/mgcp_network.c +++ b/src/libosmo-mgcp/mgcp_network.c @@ -1292,6 +1292,7 @@ struct mgcp_conn *conn_dst; struct osmo_sockaddr *from_addr = mc->from_addr; char ipbuf[INET6_ADDRSTRLEN]; + int rc = 0;
/*! NOTE: This callback function implements the endpoint specific * dispatch behaviour of an rtp bridge/proxy endpoint. It is assumed @@ -1323,36 +1324,25 @@ return mgcp_conn_rtp_dispatch_rtp(conn_src, msg); }
- /* Find a destination connection. */ - /* NOTE: This code path runs every time an RTP packet is received. The - * function mgcp_find_dst_conn() we use to determine the detination - * connection will iterate the connection list inside the endpoint. - * Since list iterations are quite costly, we will figure out the - * destination only once and use the optional private data pointer of - * the connection to cache the destination connection pointer. */ - if (!conn->send) { - conn_dst = mgcp_find_dst_conn(conn); - conn->send = conn_dst; - } else { - conn_dst = conn->send; + /* Dispatch RTP packet to destination RTP connection(s). + * Only if reception is enabled and there is at least one destination, + * conn->send is set to that destination. TRP is forwarded to that + * destination. If there are multiple destinations (conference), + * conn->send_next points to the next destination. The loop is executed + * until data is sent to all destinations. If conn->send points to the + * receiving conn (this conn), data is also echoed back to the sender. + * + * The loop terminates if there is no other destination (conn_dst == NULL) + * or if it reaches the originating connection (conn_dst == conn) + * or if it reaches the destination where it started (conn_dst == conn->send). + */ + if ((conn_dst = conn->send)) { + do { + rc = mgcp_conn_rtp_dispatch_rtp(&conn_dst->u.rtp, msg); + conn_dst = conn_dst->send_next; + } while (conn_dst && conn_dst != conn && conn_dst != conn->send); } - - /* There is no destination conn, stop here */ - if (!conn_dst) { - LOGPCONN(conn, DRTP, LOGL_DEBUG, - "no connection to forward an incoming RTP packet to\n"); - return -1; - } - - /* The destination conn is not an RTP connection */ - if (conn_dst->type != MGCP_CONN_TYPE_RTP) { - LOGPCONN(conn, DRTP, LOGL_ERROR, - "unable to find suitable destination conn\n"); - return -1; - } - - /* Dispatch RTP packet to destination RTP connection */ - return mgcp_conn_rtp_dispatch_rtp(&conn_dst->u.rtp, msg); + return rc; }
/*! dispatch incoming RTP packet to E1 subslot, handle RTCP packets locally. @@ -1397,18 +1387,8 @@ * \param[in] conn Connection that is about to be removed (ignored). */ void mgcp_cleanup_rtp_bridge_cb(struct mgcp_endpoint *endp, struct mgcp_conn *conn) { - struct mgcp_conn *conn_cleanup; - - /* In mgcp_dispatch_rtp_bridge_cb() we use conn->priv to cache the - * pointer to the destination connection, so that we do not have - * to go through the list every time an RTP packet arrives. To prevent - * a use-after-free situation we invalidate this information for all - * connections present when one connection is removed from the - * endpoint. */ - llist_for_each_entry(conn_cleanup, &endp->conns, entry) { - if (conn_cleanup->send == conn) - conn_cleanup->send = NULL; - } + /* Restructure connections with this connection excluded. */ + mgcp_trunk_connect(endp, conn); }
/*! cleanup an endpoint when a connection on an E1 endpoint is removed. diff --git a/src/libosmo-mgcp/mgcp_protocol.c b/src/libosmo-mgcp/mgcp_protocol.c index 6223ffa..c4149ab 100644 --- a/src/libosmo-mgcp/mgcp_protocol.c +++ b/src/libosmo-mgcp/mgcp_protocol.c @@ -966,24 +966,6 @@ return create_err_response(endp, endp, 517, "CRCX", pdata->trans); }
- /* Check if we are able to accept the creation of another connection */ - if (llist_count(&endp->conns) >= endp->type->max_conns) { - LOGPENDP(endp, DLMGCP, LOGL_ERROR, - "CRCX: endpoint full, max. %i connections allowed!\n", - endp->type->max_conns); - if (trunk->force_realloc) { - /* There is no more room for a connection, make some - * room by blindly tossing the oldest of the two two - * connections */ - mgcp_conn_free_oldest(endp); - } else { - /* There is no more room for a connection, leave - * everything as it is and return with an error */ - rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_LIMIT_EXCEEDED)); - return create_err_response(endp, endp, 540, "CRCX", pdata->trans); - } - } - /* Check if this endpoint already serves a call, if so, check if the * callids match up so that we are sure that this is our call */ if (endp->callid && mgcp_verify_call_id(endp, callid)) { diff --git a/src/libosmo-mgcp/mgcp_trunk.c b/src/libosmo-mgcp/mgcp_trunk.c index 69750f8..da685e1 100644 --- a/src/libosmo-mgcp/mgcp_trunk.c +++ b/src/libosmo-mgcp/mgcp_trunk.c @@ -24,6 +24,7 @@ #include <osmocom/mgcp/mgcp.h> #include <osmocom/mgcp/mgcp_protocol.h> #include <osmocom/mgcp/mgcp_endp.h> +#include <osmocom/mgcp/mgcp_conn.h> #include <osmocom/mgcp/mgcp_trunk.h> #include <osmocom/mgcp/mgcp_e1.h> #include <osmocom/abis/e1_input.h> @@ -306,3 +307,115 @@
return NULL; } + +/* Interconnect payload of connections depending on their mode. + * Optionally exclude a connection that shall be removed from the endpoint. + * + * A connection that receives RTP into the endpont will have "send" pointer + * set to (one of) the other connection that shall send out RTP. + * + * All connection that send RTP have "send_next" pointer set to the next + * or first connection that sends RTP. They are linked in a ring. + * + * Regular bridge with two connections: Each connection forwards audio + * towards the other connection and only towards the other connection. + * The "send_next" pointers are pointing to the other endpoint. + + * connection A connection B + * ("sendrecv") ("sendrecv") + * conn->send ---------------------------> + * <------------------------------------- conn->send + * conn->send_next ----------------------> + * <------------------------------------- conn->send_next + * + * Conference bridge with three connections: Each connection forwards + * audio to itself and also towards all other connection. + * + * connection A connection B connection C + * ("confecho") ("confecho") ("confecho"); + * conn->send --\ conn->send --\ conn->send --\ + * <-----------/ <-----------/ <-----------/ + * conn->send_next --> + * conn->send_next --> + * <------------------------------------- conn->send_next + * + * The same conference bridge but without echo: Each connection forwards + * auto to all other connection but not to itself. + * + * connection A connection B connection C + * ("confecho") ("confecho") ("confecho"); + * conn->send -------> conn->send -------> + * <------------------------------------- conn->send + * conn->send_next --> + * conn->send_next --> + * <------------------------------------- conn->send_next + * + */ +int mgcp_trunk_connect(struct mgcp_endpoint *endp, struct mgcp_conn *exclude_conn) +{ + struct mgcp_conn *conn = NULL, *first_conn = NULL, *previous_conn, *send_conn; + + /* Link all _sending_ connection in a circular linked list, if there is + * more than one sending endpoint. */ + llist_for_each_entry(conn, &endp->conns, entry) { + if (conn == exclude_conn) + continue; + if (conn->type != MGCP_CONN_TYPE_RTP) + continue; + if (!(conn->mode & MGCP_CONN_SEND_ONLY)) + continue; + if (!first_conn) + first_conn = conn; + else + previous_conn->send_next = conn; + previous_conn = conn; + } + if (first_conn && previous_conn != first_conn) + previous_conn->send_next = first_conn; + + /* Link all _receiving_ connections to a sending connection, if any. + * If the receiving connection also gets looped back what it receives, it is linked to itsef, so that the + * payload is also forwarded to itself. */ + llist_for_each_entry(conn, &endp->conns, entry) { + if (conn == exclude_conn) + continue; + if (conn->type != MGCP_CONN_TYPE_RTP) + continue; + if (!(conn->mode & MGCP_CONN_RECV_ONLY)) + continue; + /* If we are also sending to ourself, our connection is the first sending endpoint. */ + if ((conn->mode & MGCP_CONN_LOOPBACK) && (conn->mode & MGCP_CONN_SEND_ONLY)) + conn->send = conn; + /* If we are not sending to ourself, select the next sending connection in the circular list. */ + else if (conn->send_next) + conn->send = conn->send_next; + /* I case we are not in the ciruclar list, search for the first node in that list. */ + else { + llist_for_each_entry(send_conn, &endp->conns, entry) { + if ((send_conn->mode & MGCP_CONN_SEND_ONLY)) + conn->send = send_conn; + } + } + } + + /* Debug the result. */ + LOGP(DLMGCP, LOGL_DEBUG, "Endpoint %s has the following interconnections:\n", endp->name); + llist_for_each_entry(conn, &endp->conns, entry) { + LOGP(DLMGCP, LOGL_DEBUG, " -> Connection ID %s %s incoming payload.\n", conn->id, + (conn->mode & MGCP_CONN_RECV_ONLY) ? "receives" : "ignores"); + send_conn = conn->send; + while (send_conn) { + LOGP(DLMGCP, LOGL_DEBUG, " Payload is forwarded to ID %s\n", send_conn->id); + send_conn = send_conn->send_next; + if (send_conn == conn || send_conn == conn->send) + break; + } + } + + return 0; +} + +void mgcp_trunk_endp_update(struct mgcp_endpoint *endp) +{ + mgcp_trunk_connect(endp, NULL); +}