blob: 9b834cf1b3a08c99a2ac7b5db3c560819e191d1a [file] [log] [blame]
/* GStreamer
* Copyright (C) <2007> Leandro Melo de Sales <leandroal@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdccp.h"
#ifdef HAVE_FIONREAD_IN_SYS_FILIO
#include <sys/filio.h>
#endif
/*
* Resolves host to IP address
* @param element - the element
* @return a gchar pointer containing the ip address or NULL if it
* couldn't resolve the host to a IP adress
*/
gchar *
gst_dccp_host_to_ip (GstElement * element, const gchar * host)
{
struct hostent *hostinfo;
char **addrs;
gchar *ip;
struct in_addr addr;
GST_DEBUG_OBJECT (element, "resolving host %s", host);
/* first check if it already is an IP address */
#ifndef G_OS_WIN32
if (inet_aton (host, &addr)) {
#else
if ((addr.S_un.S_addr = inet_addr (host)) != INADDR_NONE) {
#endif
ip = g_strdup (host);
GST_DEBUG_OBJECT (element, "resolved to IP %s", ip);
return ip;
}
/* perform a name lookup */
if (!(hostinfo = gethostbyname (host))) {
GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND, (NULL),
("Could not find IP address for host \"%s\".", host));
return NULL;
}
if (hostinfo->h_addrtype != AF_INET) {
GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND, (NULL),
("host \"%s\" is not an IP host", host));
return NULL;
}
addrs = hostinfo->h_addr_list;
/* There could be more than one IP address, but we just return the first */
ip = g_strdup (inet_ntoa (*(struct in_addr *) *addrs));
return ip;
}
/*
* Read a buffer from the given socket
*
* @param this - the element that has the socket that will be read
* @param socket - the socket fd that will be read
* @param buf - the buffer with the data read from the socket
* @return GST_FLOW_OK if the read operation was successful
* or GST_FLOW_ERROR indicating a connection close or an error.
* Handle it with EOS.
*/
GstFlowReturn
gst_dccp_read_buffer (GstElement * this, int socket, GstBuffer ** buf)
{
fd_set testfds;
int maxfdp1;
gssize bytes_read;
#ifndef G_OS_WIN32
int readsize;
struct msghdr mh;
struct iovec iov;
#else
unsigned long readsize;
#endif
*buf = NULL;
/* do a blocking select on the socket */
FD_ZERO (&testfds);
FD_SET (socket, &testfds);
maxfdp1 = socket + 1;
/* no action (0) is also an error in our case */
if (select (maxfdp1, &testfds, NULL, NULL, 0) <= 0) {
GST_ELEMENT_ERROR (this, RESOURCE, READ, (NULL),
("select failed: %s", g_strerror (errno)));
return GST_FLOW_ERROR;
}
/* ask how much is available for reading on the socket */
#ifndef G_OS_WIN32
if (ioctl (socket, FIONREAD, &readsize) < 0) {
GST_ELEMENT_ERROR (this, RESOURCE, READ, (NULL),
("read FIONREAD value failed: %s", g_strerror (errno)));
#else
if (ioctlsocket (socket, FIONREAD, &readsize) == SOCKET_ERROR) {
GST_ELEMENT_ERROR (this, RESOURCE, READ, (NULL),
("read FIONREAD value failed: %s", g_strerror (WSAGetLastError ())));
#endif
return GST_FLOW_ERROR;
}
if (readsize == 0) {
GST_DEBUG_OBJECT (this, "Got EOS on socket stream");
return GST_FLOW_UNEXPECTED;
}
*buf = gst_buffer_new_and_alloc ((int) readsize);
#ifndef G_OS_WIN32
memset (&mh, 0, sizeof (mh));
mh.msg_name = NULL;
mh.msg_namelen = 0;
iov.iov_base = (char *) GST_BUFFER_DATA (*buf);
iov.iov_len = readsize;
mh.msg_iov = &iov;
mh.msg_iovlen = 1;
bytes_read = recvmsg (socket, &mh, 0);
#else
bytes_read =
recvfrom (socket, (char *) GST_BUFFER_DATA (*buf), (int) readsize, 0,
NULL, 0);
#endif
if (bytes_read != readsize) {
GST_DEBUG_OBJECT (this, "Error while reading data");
return GST_FLOW_ERROR;
}
GST_LOG_OBJECT (this, "bytes read %" G_GSSIZE_FORMAT, bytes_read);
GST_LOG_OBJECT (this, "returning buffer of size %d", GST_BUFFER_SIZE (*buf));
return GST_FLOW_OK;
}
/* Create a new DCCP socket
*
* @param element - the element
* @return the socket file descriptor
*/
gint
gst_dccp_create_new_socket (GstElement * element)
{
int sock_fd;
if ((sock_fd = socket (AF_INET, SOCK_DCCP, IPPROTO_DCCP)) < 0) {
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_READ, (NULL), GST_ERROR_SYSTEM);
}
return sock_fd;
}
/* Connect to a server
* @param element - the element
* @param server_sin - a struct with a server address and port
* @param sock_fd - the socket to connect
* @return TRUE in case of successful connection, FALSE otherwise
*/
gboolean
gst_dccp_connect_to_server (GstElement * element, struct sockaddr_in server_sin,
int sock_fd)
{
#ifdef G_OS_WIN32
int errorCode;
#endif
GST_DEBUG_OBJECT (element, "connecting to server");
if (connect (sock_fd, (struct sockaddr *) &server_sin, sizeof (server_sin))) {
#ifdef G_OS_WIN32
errorCode = WSAGetLastError ();
switch (errorCode) {
case WSAECONNREFUSED:
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_WRITE,
("Connection to %s:%d refused.", inet_ntoa (server_sin.sin_addr),
ntohs (server_sin.sin_port)), (NULL));
return FALSE;
break;
default:
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_READ, (NULL),
("Connect to %s:%d failed: %s", inet_ntoa (server_sin.sin_addr),
ntohs (server_sin.sin_port), g_strerror (errorCode)));
return FALSE;
break;
}
#else
switch (errno) {
case ECONNREFUSED:
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_WRITE,
("Connection to %s:%d refused.", inet_ntoa (server_sin.sin_addr),
ntohs (server_sin.sin_port)), (NULL));
return FALSE;
break;
default:
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_READ, (NULL),
("Connect to %s:%d failed: %s", inet_ntoa (server_sin.sin_addr),
ntohs (server_sin.sin_port), g_strerror (errno)));
return FALSE;
break;
}
#endif
}
return TRUE;
}
/* FIXME support only one client */
/*
* Accept connection on the server socket.
*
* @param element - the element
* @param server_sock_fd - the server socket file descriptor
* @return the socket of the client connected to the server.
*/
gint
gst_dccp_server_wait_connections (GstElement * element, int server_sock_fd)
{
/* new client */
int client_sock_fd;
struct sockaddr_in client_address;
socklen_t client_address_len;
memset (&client_address, 0, sizeof (client_address));
client_address_len = 0;
if ((client_sock_fd =
accept (server_sock_fd, (struct sockaddr *) &client_address,
&client_address_len)) == -1) {
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_WRITE, (NULL),
("Could not accept client on server socket %d: %s (%d)",
server_sock_fd, g_strerror (errno), errno));
return -1;
}
GST_DEBUG_OBJECT (element, "Added new client ip %s with fd %d.",
inet_ntoa (client_address.sin_addr), client_sock_fd);
return client_sock_fd;
}
/*
* Bind a server address.
*
* @param element - the element
* @param server_sock_fd - the server socket fd
* @param server_sin - the address and the port to bind the server on
* @return true in success, false otherwise.
*/
gboolean
gst_dccp_bind_server_socket (GstElement * element, int server_sock_fd,
struct sockaddr_in server_sin)
{
int ret;
GST_DEBUG_OBJECT (element, "Binding server socket to address.");
ret = bind (server_sock_fd, (struct sockaddr *) &server_sin,
sizeof (server_sin));
if (ret) {
switch (errno) {
default:
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_READ, (NULL),
("Bind on port %d failed: %s", ntohs (server_sin.sin_port),
g_strerror (errno)));
return FALSE;
break;
}
}
return TRUE;
}
/*
* Listen on server socket.
*
* @param element - the element
* @param server_sock_fd - the server socket fd
* @return true in success, false otherwise.
*/
gboolean
gst_dccp_listen_server_socket (GstElement * element, int server_sock_fd)
{
GST_DEBUG_OBJECT (element, "Listening on server socket %d with queue of %d",
server_sock_fd, DCCP_BACKLOG);
if (listen (server_sock_fd, DCCP_BACKLOG) == -1) {
GST_ELEMENT_ERROR (element, RESOURCE, OPEN_READ, (NULL),
("Could not listen on server socket: %s", g_strerror (errno)));
return FALSE;
}
GST_DEBUG_OBJECT (element,
"Listened on server socket %d, returning from connection setup",
server_sock_fd);
return TRUE;
}
/* Write buffer to given socket incrementally.
*
* @param element - the element
* @param socket - the socket
* @param buf - the buffer that will be written
* @param size - the number of bytes of the buffer
* @param packet_size - the MTU
* @return the number of bytes written.
*/
static GstFlowReturn
gst_dccp_socket_write (GstElement * element, int socket, const void *buf,
size_t size, int packet_size)
{
size_t bytes_written = 0;
ssize_t wrote = 0;
#ifndef G_OS_WIN32
struct iovec iov;
struct msghdr mh;
memset (&mh, 0, sizeof (mh));
while (bytes_written < size) {
do {
mh.msg_name = NULL;
mh.msg_namelen = 0;
iov.iov_base = (char *) buf + bytes_written;
iov.iov_len = MIN (packet_size, size - bytes_written);
mh.msg_iov = &iov;
mh.msg_iovlen = 1;
wrote = sendmsg (socket, &mh, 0);
} while (wrote == -1 && errno == EAGAIN);
#else
int errorCode = 0;
while (bytes_written < size) {
do {
wrote = sendto (socket, (char *) buf + bytes_written,
MIN (packet_size, size - bytes_written), 0, NULL, 0);
errorCode = WSAGetLastError ();
} while (wrote == SOCKET_ERROR && errorCode == EAGAIN);
#endif
/* give up on error */
if (wrote >= 0)
bytes_written += wrote;
else
break;
}
if (wrote < 0)
GST_WARNING ("Error while writing.");
else
GST_LOG_OBJECT (element, "Wrote %" G_GSIZE_FORMAT " bytes succesfully.",
bytes_written);
if (bytes_written != size) {
GST_ELEMENT_ERROR (element, RESOURCE, WRITE,
("Error while sending data to socket %d.", socket),
("Only %" G_GSIZE_FORMAT " of %" G_GSIZE_FORMAT " bytes written: %s",
bytes_written, size, g_strerror (errno)));
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
/* Write buffer to given socket.
*
* @param this - the element
* @param buf - the buffer that will be written
* @param client_sock_fd - the client socket
* @param packet_size - the MTU
* @return GST_FLOW_OK if the send operation was successful, GST_FLOW_ERROR otherwise.
*/
GstFlowReturn
gst_dccp_send_buffer (GstElement * this, GstBuffer * buffer, int client_sock_fd,
int packet_size)
{
// size_t wrote;
gint size = 0;
guint8 *data;
size = GST_BUFFER_SIZE (buffer);
data = GST_BUFFER_DATA (buffer);
GST_LOG_OBJECT (this, "writing %d bytes", size);
if (packet_size < 0) {
return GST_FLOW_ERROR;
}
return gst_dccp_socket_write (this, client_sock_fd, data, size, packet_size);
}
/*
* Make address reusable.
* @param element - the element
* @param sock_fd - the socket
* @return TRUE if the operation was successful, FALSE otherwise.
*/
gboolean
gst_dccp_make_address_reusable (GstElement * element, int sock_fd)
{
int ret = 1;
/* make address reusable */
if (setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR,
(void *) &ret, sizeof (ret)) < 0) {
GST_ELEMENT_ERROR (element, RESOURCE, SETTINGS, (NULL),
("Could not setsockopt: %s", g_strerror (errno)));
return FALSE;
}
return TRUE;
}
/*
* Set DCCP congestion control.
* @param element - the element
* @param sock_fd - the socket
* @param ccid - the ccid number
* @return TRUE if the operation was successful, FALSE otherwise.
*/
gboolean
gst_dccp_set_ccid (GstElement * element, int sock_fd, uint8_t ccid)
{
uint8_t ccids[4]; /* for getting the available CCIDs, should be large enough */
socklen_t len = sizeof (ccids);
int i, ret;
gboolean ccid_supported = FALSE;
/*
* Determine which CCIDs are available on the host
*/
#ifndef G_OS_WIN32
ret = getsockopt (sock_fd, SOL_DCCP, DCCP_SOCKOPT_AVAILABLE_CCIDS, &ccids,
&len);
#else
ret =
getsockopt (sock_fd, SOL_DCCP, DCCP_SOCKOPT_AVAILABLE_CCIDS,
(char *) &ccids, &len);
#endif
if (ret < 0) {
GST_ERROR_OBJECT (element, "Can not determine available CCIDs");
return FALSE;
}
for (i = 0; i < sizeof (ccids); i++) {
if (ccid == ccids[i]) {
ccid_supported = TRUE;
}
}
if (!ccid_supported) {
GST_ERROR_OBJECT (element, "CCID specified is not supported");
return FALSE;
}
#ifndef G_OS_WIN32
if (setsockopt (sock_fd, SOL_DCCP, DCCP_SOCKOPT_CCID, &ccid,
#else
if (setsockopt (sock_fd, SOL_DCCP, DCCP_SOCKOPT_CCID, (char *) &ccid,
#endif
sizeof (ccid)) < 0) {
GST_ERROR_OBJECT (element, "Can not set CCID");
return FALSE;
}
return TRUE;
}
#if 0
/*
* Get the current ccid of TX or RX half-connection. tx_or_rx parameter must be
* DCCP_SOCKOPT_TX_CCID or DCCP_SOCKOPT_RX_CCID.
* @return ccid or -1 on error or tx_or_rx not the correct option
*/
static uint8_t
gst_dccp_get_ccid (GstElement * element, int sock_fd, int tx_or_rx)
{
uint8_t ccid;
socklen_t ccidlen;
int ret;
switch (tx_or_rx) {
case DCCP_SOCKOPT_TX_CCID:
case DCCP_SOCKOPT_RX_CCID:
break;
default:
return -1;
}
ccidlen = sizeof (ccid);
#ifndef G_OS_WIN32
ret = getsockopt (sock_fd, SOL_DCCP, tx_or_rx, &ccid, &ccidlen);
#else
ret = getsockopt (sock_fd, SOL_DCCP, tx_or_rx, (char *) &ccid, &ccidlen);
#endif
if (ret < 0) {
GST_ERROR_OBJECT (element, "Can not determine available CCIDs");
return -1;
}
return ccid;
}
#endif
/*
* Get the socket MTU.
* @param element - the element
* @param sock - the socket
* @return the MTU if the operation was successful, -1 otherwise.
*/
gint
gst_dccp_get_max_packet_size (GstElement * element, int sock)
{
int size;
socklen_t sizelen = sizeof (size);
#ifndef G_OS_WIN32
if (getsockopt (sock, SOL_DCCP, DCCP_SOCKOPT_GET_CUR_MPS,
&size, &sizelen) < 0) {
#else
if (getsockopt (sock, SOL_DCCP, DCCP_SOCKOPT_GET_CUR_MPS,
(char *) &size, &sizelen) < 0) {
#endif
GST_ELEMENT_ERROR (element, RESOURCE, SETTINGS, (NULL),
("Could not get current MTU %d: %s", errno, g_strerror (errno)));
return -1;
}
GST_DEBUG_OBJECT (element, "MTU: %d", size);
return size;
}
void
gst_dccp_socket_close (GstElement * element, int *socket)
{
if (*socket >= 0) {
GST_DEBUG_OBJECT (element, "closing socket");
close (*socket);
*socket = -1;
}
}