/* GNet - Networking library
* Copyright (C) 2000-2003 David Helder
* Copyright (C) 2000-2003 Andrew Lanoix
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "gnet-private.h"
#include "udp.h"
/**
* gnet_udp_socket_new
*
* Creates a #GUdpSocket bound to all interfaces and an arbitrary
* port.
*
* Returns: a new #GUdpSocket; NULL on error.
*
**/
GUdpSocket*
gnet_udp_socket_new (void)
{
return gnet_udp_socket_new_full (NULL, 0);
}
/**
* gnet_udp_socket_new_with_port:
* @port: port to bind to (0 for an arbitrary port)
*
* Creates a #GUdpSocket bound to all interfaces and port @port. If
* @port is 0, an arbitrary port will be used.
*
* Returns: a new #GUdpSocket; NULL on error.
*
**/
GUdpSocket*
gnet_udp_socket_new_with_port (gint port)
{
return gnet_udp_socket_new_full (NULL, port);
}
/**
* gnet_udp_socket_new_full
* @iface: interface to bind to (NULL for all interfaces)
* @port: port to bind to (0 for an arbitrary port)
*
* Creates a #GUdpSocket bound to interface @iface and port @port.
* If @iface is NULL, all interfaces will be used. If @port is 0, an
* arbitrary port will be used.
*
* Returns: a new #GUdpSocket; NULL on error.
*
**/
GUdpSocket*
gnet_udp_socket_new_full (const GInetAddr* iface, gint port)
{
SOCKET sockfd;
struct sockaddr_storage sa;
GUdpSocket* s;
const int on = 1;
/* Create sockfd and address */
sockfd = gnet_private_create_listen_socket (SOCK_DGRAM, iface, port, &sa);
if (!GNET_IS_SOCKET_VALID(sockfd))
{
g_warning ("socket() failed");
return NULL;
}
/* Set broadcast option. This allows the user to broadcast packets.
It has no effect otherwise. */
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,
(void*) &on, sizeof(on)) != 0)
{
g_warning ("setsockopt() failed");
GNET_CLOSE_SOCKET(sockfd);
return NULL;
}
/* Bind to the socket to some local address and port */
if (bind(sockfd, &GNET_SOCKADDR_SA(sa), GNET_SOCKADDR_LEN(sa)) != 0)
{
GNET_CLOSE_SOCKET(sockfd);
return NULL;
}
/* Create UDP socket */
s = g_new0 (GUdpSocket, 1);
s->sockfd = sockfd;
s->sa = sa;
s->ref_count = 1;
return s;
}
/**
* gnet_udp_socket_delete:
* @socket: a #GUdpSocket
*
* Deletes a #GUdpSocket.
*
**/
void
gnet_udp_socket_delete (GUdpSocket* socket)
{
if (socket != NULL)
gnet_udp_socket_unref (socket);
}
/**
* gnet_udp_socket_ref
* @socket: #GUdpSocket to reference
*
* Adds a reference to a #GUdpSocket.
*
**/
void
gnet_udp_socket_ref (GUdpSocket* socket)
{
g_return_if_fail (socket != NULL);
socket->ref_count++;
}
/**
* gnet_udp_socket_unref
* @socket: a #GUdpSocket
*
* Removes a reference from a #GUdpScoket. A #GUdpSocket is deleted
* when the reference count reaches 0.
*
**/
void
gnet_udp_socket_unref (GUdpSocket* s)
{
g_return_if_fail(s != NULL);
s->ref_count--;
if (s->ref_count == 0)
{
GNET_CLOSE_SOCKET(s->sockfd); /* Don't care if this fails... */
if (s->iochannel)
g_io_channel_unref(s->iochannel);
g_free(s);
}
}
/**
* gnet_udp_socket_get_io_channel
* @socket: a #GUdpSocket
*
* Gets the #GIOChannel of a #GUdpSocket.
*
* Use the channel with g_io_add_watch() to check if the socket is
* readable or writable. If the channel is readable, call
* gnet_udp_socket_receive() to receive a packet. If the channel is
* writable, call gnet_udp_socket_send() to send a packet. This is
* not a normal giochannel - do not read from or write to it.
*
* Every #GUdpSocket has one and only one #GIOChannel. If you ref
* the channel, then you must unref it eventually. Do not close the
* channel. The channel is closed by GNet when the socket is
* deleted.
*
* Returns: a #GIOChannel.
*
**/
GIOChannel*
gnet_udp_socket_get_io_channel (GUdpSocket* socket)
{
g_return_val_if_fail (socket != NULL, NULL);
if (socket->iochannel == NULL)
socket->iochannel = gnet_private_io_channel_new(socket->sockfd);
return socket->iochannel;
}
/**
* gnet_udp_socket_get_local_inetaddr
* @socket: a #GUdpSocket
*
* Gets the local host's address from a #GUdpSocket.
*
* Returns: a #GInetAddr.
*
**/
GInetAddr*
gnet_udp_socket_get_local_inetaddr (const GUdpSocket* socket)
{
socklen_t socklen;
struct sockaddr_storage sa;
GInetAddr* ia;
g_return_val_if_fail (socket, NULL);
socklen = sizeof(sa);
if (getsockname(socket->sockfd, &GNET_SOCKADDR_SA(sa), &socklen) != 0)
return NULL;
ia = g_new0(GInetAddr, 1);
ia->ref_count = 1;
ia->sa = sa;
return ia;
}
/**
* gnet_udp_socket_send
* @socket: a #GUdpSocket
* @buffer: buffer to send
* @length: length of @buffer
* @dst: destination address
*
* Sends data to a host using a #GUdpSocket.
*
* Returns: 0 if successful; something else on error.
*
**/
gint
gnet_udp_socket_send (GUdpSocket* socket,
const gchar* buffer, gint length,
const GInetAddr* dst)
{
gint bytes_sent;
struct sockaddr_storage sa;
g_return_val_if_fail (socket, -1);
g_return_val_if_fail (dst, -1);
/* Address of dst must be address of socket */
#ifdef HAVE_IPV6
if (GNET_INETADDR_FAMILY(dst) != GNET_SOCKADDR_FAMILY(socket->sa))
{
/* If dst is IPv4, map to IPv6 */
if (GNET_INETADDR_FAMILY(dst) == AF_INET &&
GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6)
{
sa.ss_family = AF_INET6;
GNET_SOCKADDR_SET_SS_LEN(sa);
GNET_SOCKADDR_PORT_SET(sa, GNET_INETADDR_PORT(dst));
GNET_SOCKADDR_ADDR32_SET(sa, 0, 0);
GNET_SOCKADDR_ADDR32_SET(sa, 1, 0);
GNET_SOCKADDR_ADDR32_SET(sa, 2, g_htonl(0xffff));
GNET_SOCKADDR_ADDR32_SET(sa, 3, GNET_INETADDR_ADDR32(dst, 0));
}
/* If dst is IPv6, map to IPv4 if possible */
else if (GNET_INETADDR_FAMILY(dst) == AF_INET6 &&
GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET &&
IN6_IS_ADDR_V4MAPPED(&GNET_INETADDR_SA6(dst).sin6_addr))
{
sa.ss_family = AF_INET;
GNET_SOCKADDR_SET_SS_LEN(sa);
GNET_SOCKADDR_PORT_SET(sa, GNET_INETADDR_PORT(dst));
GNET_SOCKADDR_ADDR32_SET(sa, 0, GNET_INETADDR_ADDR32(dst, 3));
}
else
return -1;
}
/* Addresses match - just copy the address */
else
#endif
{
sa = dst->sa;
}
bytes_sent = sendto (socket->sockfd,
(void*) buffer, length, 0,
(struct sockaddr*) &sa, GNET_SOCKADDR_LEN(sa));
if (bytes_sent != length)
return -1;
return 0;
}
/**
* gnet_udp_socket_receive
* @socket: a #GUdpSocket
* @buffer: buffer to write to
* @length: length of @buffer
* @src: pointer to source address (optional)
*
* Receives data using a #GUdpSocket. If @src is set, the source
* address is stored in the location @src points to. The address is
* caller owned.
*
* Returns: the number of bytes received, -1 on error.
*
**/
gint
gnet_udp_socket_receive (GUdpSocket* socket,
gchar* buffer, gint length,
GInetAddr** src)
{
gint bytes_received;
struct sockaddr_storage sa;
gint sa_len = sizeof(struct sockaddr_storage);
g_return_val_if_fail (socket, -1);
g_return_val_if_fail (buffer, -1);
bytes_received = recvfrom (socket->sockfd,
(void*) buffer, length,
0, (struct sockaddr*) &sa, &sa_len);
if (bytes_received == -1)
return -1;
if (src)
{
(*src) = g_new0 (GInetAddr, 1);
(*src)->sa = sa;
(*src)->ref_count = 1;
}
return bytes_received;
}
#ifndef GNET_WIN32 /*********** Unix code ***********/
/**
* gnet_udp_socket_has_packet
* @socket: a #GUdpSocket
*
* Tests if a #GUdpSocket has a packet waiting to be received.
*
* Returns: TRUE if there is packet waiting, FALSE otherwise.
*
**/
gboolean
gnet_udp_socket_has_packet (const GUdpSocket* socket)
{
fd_set readfds;
struct timeval timeout = {0, 0};
FD_ZERO (&readfds);
FD_SET (socket->sockfd, &readfds);
if ((select(socket->sockfd + 1, &readfds, NULL, NULL, &timeout)) == 1)
{
return TRUE;
}
return FALSE;
}
#else /*********** Windows code ***********/
gboolean
gnet_udp_socket_has_packet (const GUdpSocket* socket)
{
gint bytes_received;
gchar data[1];
guint packetlength;
u_long arg;
gint error;
arg = 1;
ioctlsocket(socket->sockfd, FIONBIO, &arg); /* set to non-blocking mode */
packetlength = 1;
bytes_received = recvfrom(socket->sockfd, (void*) data, packetlength,
MSG_PEEK, NULL, NULL);
error = WSAGetLastError();
arg = 0;
ioctlsocket(socket->sockfd, FIONBIO, &arg); /* set blocking mode */
if (bytes_received == SOCKET_ERROR)
{
if (WSAEMSGSIZE != error)
{
return FALSE;
}
/* else, the buffer was not big enough, which is fine since we
just want to see if a packet is there..*/
}
if (bytes_received)
return TRUE;
return FALSE;
}
#endif /*********** End Windows code ***********/
/**
* gnet_udp_socket_get_ttl
* @socket: a #GUdpSocket
*
* Gets the time-to-live (TTL) default of a #GUdpSocket. All UDP
* packets have a TTL field. This field is decremented by a router
* before it forwards the packet. If the TTL reaches zero, the
* packet is discarded. The default value is sufficient for most
* applications.
*
* Returns: the TTL (an integer between 0 and 255), -1 if the kernel
* default is being used, or an integer less than -1 on error.
*
**/
gint
gnet_udp_socket_get_ttl (const GUdpSocket* socket)
{
int ttl;
socklen_t ttl_size;
int rv = -2;
ttl_size = sizeof(ttl);
/* Get the IPv4 TTL if it's bound to an IPv4 address */
if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET)
{
rv = getsockopt(socket->sockfd, IPPROTO_IP, IP_TTL,
(void*) &ttl, &ttl_size);
}
/* Or, get the IPv6 TTL if it's bound to an IPv6 address */
#ifdef HAVE_IPV6
else if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6)
{
rv = getsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
(void*) &ttl, &ttl_size);
}
#endif
else
g_assert_not_reached();
if (rv == -1)
return -2;
return ttl;
}
/**
* gnet_udp_socket_set_ttl
* @socket: a #GUdpSocket
* @ttl: value to set TTL to
*
* Sets the time-to-live (TTL) default of a #GUdpSocket. Set the TTL
* to -1 to use the kernel default. The default value is sufficient
* for most applications.
*
* Returns: 0 if successful.
*
**/
gint
gnet_udp_socket_set_ttl (GUdpSocket* socket, gint ttl)
{
int rv1, rv2;
#ifdef HAVE_IPV6
GIPv6Policy policy;
#endif
rv1 = -1;
rv2 = -1;
/* If the bind address is anything IPv4 *or* the bind address is
0::0 IPv6 and IPv6 policy allows IPv4, set IP_TTL. In the latter case,
if we bind to 0::0 and the host is dual-stacked, then all IPv4
interfaces will be bound to also. */
if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET
#ifdef HAVE_IPV6
|| (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6 &&
IN6_IS_ADDR_UNSPECIFIED(&GNET_SOCKADDR_SA6(socket->sa).sin6_addr) &&
((policy = gnet_ipv6_get_policy()) == GIPV6_POLICY_IPV4_THEN_IPV6 ||
policy == GIPV6_POLICY_IPV6_THEN_IPV4))
#endif
)
{
rv1 = setsockopt(socket->sockfd, IPPROTO_IP, IP_TTL,
(void*) &ttl, sizeof(ttl));
}
/* If the bind address is IPv6, set IPV6_UNICAST_HOPS */
#ifdef HAVE_IPV6
if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6)
{
rv2 = setsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
(void*) &ttl, sizeof(ttl));
}
#endif
if (rv1 == -1 && rv2 == -1)
return -1;
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1