/* RTP proxy handling for ip.access nanoBTS */ /* (C) 2009 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include static LLIST_HEAD(rtp_sockets); enum rtp_bfd_priv { RTP_PRIV_NONE, RTP_PRIV_RTP, RTP_PRIV_RTCP }; #define RTP_ALLOC_SIZE 1500 /* read from incoming RTP/RTCP socket */ static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss) { int rc; struct msgb *msg = msgb_alloc(RTP_ALLOC_SIZE, "RTP/RTCP"); struct rtp_sub_socket *other_rss; if (!msg) return -ENOMEM; rc = read(rss->bfd.fd, msg->data, RTP_ALLOC_SIZE); if (rc <= 0) { rss->bfd.when &= ~BSC_FD_READ; return rc; } msgb_put(msg, rc); switch (rs->rx_action) { case RTP_PROXY: if (rss->bfd.priv_nr == RTP_PRIV_RTP) other_rss = &rs->proxy.other_sock->rtp; else if (rss->bfd.priv_nr == RTP_PRIV_RTCP) other_rss = &rs->proxy.other_sock->rtcp; else { msgb_free(msg); return -EINVAL; } msgb_enqueue(&other_rss->tx_queue, msg); other_rss->bfd.when |= BSC_FD_WRITE; break; /* FIXME: other cases */ } return rc; } /* write from tx_queue to RTP/RTCP socket */ static int rtp_socket_write(struct rtp_socket *rs, struct rtp_sub_socket *rss) { struct msgb *msg; int written; msg = msgb_dequeue(&rss->tx_queue); if (!msg) { rss->bfd.when &= ~BSC_FD_WRITE; return 0; } written = write(rss->bfd.fd, msg->data, msg->len); if (written < msg->len) { perror("short write"); msgb_free(msg); return -EIO; } msgb_free(msg); return 0; } /* callback for the select.c:bfd_* layer */ static int rtp_bfd_cb(struct bsc_fd *bfd, unsigned int flags) { struct rtp_socket *rs = bfd->data; struct rtp_sub_socket *rss; switch (bfd->priv_nr) { case RTP_PRIV_RTP: rss = &rs->rtp; break; case RTP_PRIV_RTCP: rss = &rs->rtcp; break; default: return -EINVAL; } if (flags & BSC_FD_READ) rtp_socket_read(rs, rss); if (flags & BSC_FD_WRITE) rtp_socket_write(rs, rss); return 0; } static void init_rss(struct rtp_sub_socket *rss, struct rtp_socket *rs, int fd, int priv_nr) { /* initialize bfd */ rss->bfd.fd = fd; rss->bfd.data = rs; rss->bfd.priv_nr = priv_nr; rss->bfd.cb = rtp_bfd_cb; } struct rtp_socket *rtp_socket_create(void) { int rc; struct rtp_socket *rs; rs = talloc_zero(tall_bsc_ctx, struct rtp_socket); if (!rs) return NULL; INIT_LLIST_HEAD(&rs->rtp.tx_queue); INIT_LLIST_HEAD(&rs->rtcp.tx_queue); rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (rc < 0) goto out_free; init_rss(&rs->rtp, rs, rc, RTP_PRIV_RTP); rc = bsc_register_fd(&rs->rtp.bfd); if (rc < 0) goto out_rtp_socket; rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (rc < 0) goto out_rtp_bfd; init_rss(&rs->rtcp, rs, rc, RTP_PRIV_RTCP); rc = bsc_register_fd(&rs->rtcp.bfd); if (rc < 0) goto out_rtcp_socket; return rs; out_rtcp_socket: close(rs->rtcp.bfd.fd); out_rtp_bfd: bsc_unregister_fd(&rs->rtp.bfd); out_rtp_socket: close(rs->rtp.bfd.fd); out_free: talloc_free(rs); return NULL; } static int rtp_sub_socket_bind(struct rtp_sub_socket *rss, u_int32_t ip, u_int16_t port) { rss->sin_local.sin_family = AF_INET; rss->sin_local.sin_addr.s_addr = htonl(ip); rss->sin_local.sin_port = htons(port); rss->bfd.when |= BSC_FD_READ; return bind(rss->bfd.fd, (struct sockaddr *)&rss->sin_local, sizeof(rss->sin_local)); } #define RTP_PORT_BASE 30000 static u_int16_t next_udp_port = RTP_PORT_BASE; /* bind a RTP socket to a local address */ int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip) { int rc; /* try to bind to a consecutive pair of ports */ for (next_udp_port; next_udp_port < 0xffff; next_udp_port += 2) { rc = rtp_sub_socket_bind(&rs->rtp, ip, next_udp_port); if (rc != 0) continue; rc = rtp_sub_socket_bind(&rs->rtcp, ip, next_udp_port+1); if (rc == 0) break; } if (rc < 0) return rc; return ntohs(rs->rtp.sin_local.sin_port); } static int rtp_sub_socket_connect(struct rtp_sub_socket *rss, u_int32_t ip, u_int16_t port) { rss->sin_remote.sin_family = AF_INET; rss->sin_remote.sin_addr.s_addr = htonl(ip); rss->sin_remote.sin_port = htons(port); return connect(rss->bfd.fd, (struct sockaddr *) &rss->sin_remote, sizeof(rss->sin_remote)); } /* 'connect' a RTP socket to a remote peer */ int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port) { int rc; rc = rtp_sub_socket_connect(&rs->rtp, ip, port); if (rc < 0) return rc; return rtp_sub_socket_connect(&rs->rtcp, ip, port+1); } /* bind two RTP/RTCP sockets together */ int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other) { this->rx_action = RTP_PROXY; this->proxy.other_sock = other; other->rx_action = RTP_PROXY; other->proxy.other_sock = this; return 0; } static void free_tx_queue(struct rtp_sub_socket *rss) { struct msgb *msg; while ((msg = msgb_dequeue(&rss->tx_queue))) msgb_free(msg); } int rtp_socket_free(struct rtp_socket *rs) { /* make sure we don't leave references dangling to us */ if (rs->rx_action == RTP_PROXY && rs->proxy.other_sock) rs->proxy.other_sock->proxy.other_sock = NULL; bsc_unregister_fd(&rs->rtp.bfd); close(rs->rtp.bfd.fd); free_tx_queue(&rs->rtp); bsc_unregister_fd(&rs->rtcp.bfd); close(rs->rtcp.bfd.fd); free_tx_queue(&rs->rtcp); talloc_free(rs); return 0; }