re/src/udp/udp.c
2011-11-11 09:26:16 +00:00

799 lines
14 KiB
C

/**
* @file udp.c User Datagram Protocol
*
* Copyright (C) 2010 Creytiv.com
*/
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_IO_H
#include <io.h>
#endif
#if !defined(WIN32) && !defined (CYGWIN)
#define __USE_POSIX 1 /**< Use POSIX flag */
#include <netdb.h>
#endif
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef __APPLE__
#include "TargetConditionals.h"
#endif
#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_main.h>
#include <re_sa.h>
#include <re_net.h>
#include <re_udp.h>
#define DEBUG_MODULE "udp"
#define DEBUG_LEVEL 5
#include <re_dbg.h>
/** Platform independent buffer type cast */
#ifdef WIN32
#define BUF_CAST (char *)
#define SOK_CAST (int)
#define SIZ_CAST (int)
#define close closesocket
#elif defined (__SYMBIAN32__)
#define BUF_CAST (void *)
#define SOK_CAST
#define SIZ_CAST
#else
#define BUF_CAST
#define SOK_CAST
#define SIZ_CAST
#endif
enum {
UDP_RXSZ_DEFAULT = 8192
};
/** Defines a UDP socket */
struct udp_sock {
struct list helpers; /**< List of UDP Helpers */
udp_recv_h *rh; /**< Receive handler */
void *arg; /**< Handler argument */
int fd; /**< Socket file descriptor */
int fd6; /**< IPv6 socket file descriptor */
bool conn; /**< Connected socket flag */
size_t rxsz; /**< Maximum receive chunk size */
size_t rx_presz; /**< Preallocated rx buffer size */
int err; /**< Cached error code */
};
/** Defines a UDP helper */
struct udp_helper {
struct le le;
int layer;
udp_helper_send_h *sendh;
udp_helper_recv_h *recvh;
void *arg;
};
static void dummy_udp_recv_handler(const struct sa *src,
struct mbuf *mb, void *arg)
{
(void)src;
(void)mb;
(void)arg;
}
static bool helper_send_handler(int *err, struct sa *dst,
struct mbuf *mb, void *arg)
{
(void)err;
(void)dst;
(void)mb;
(void)arg;
return false;
}
static bool helper_recv_handler(struct sa *src,
struct mbuf *mb, void *arg)
{
(void)src;
(void)mb;
(void)arg;
return false;
}
static void udp_destructor(void *data)
{
struct udp_sock *us = data;
list_flush(&us->helpers);
if (-1 != us->fd) {
fd_close(us->fd);
(void)close(us->fd);
}
if (-1 != us->fd6) {
fd_close(us->fd6);
(void)close(us->fd6);
}
}
static void udp_read(struct udp_sock *us, int fd)
{
struct mbuf *mb = mbuf_alloc(us->rxsz);
struct sa src;
struct le *le;
int err = 0;
ssize_t n;
if (!mb)
return;
src.len = sizeof(src.u);
n = recvfrom(fd, BUF_CAST mb->buf + us->rx_presz,
mb->size - us->rx_presz, 0,
&src.u.sa, &src.len);
if (n < 0) {
err = errno;
if (EAGAIN == err)
goto out;
#ifdef EWOULDBLOCK
if (EWOULDBLOCK == err)
goto out;
#endif
#if TARGET_OS_IPHONE
if (ENOTCONN == err) {
struct udp_sock *us_new;
struct sa laddr;
err = udp_local_get(us, &laddr);
if (err)
goto out;
if (-1 != us->fd) {
fd_close(us->fd);
(void)close(us->fd);
us->fd = -1;
}
if (-1 != us->fd6) {
fd_close(us->fd6);
(void)close(us->fd6);
us->fd6 = -1;
}
err = udp_listen(&us_new, &laddr, NULL, NULL);
if (err)
goto out;
us->fd = us_new->fd;
us->fd6 = us_new->fd6;
us_new->fd = -1;
us_new->fd6 = -1;
mem_deref(us_new);
udp_thread_attach(us);
goto out;
}
#endif
/* cache error code */
us->err = err;
goto out;
}
mb->pos = us->rx_presz;
mb->end = n + us->rx_presz;
(void)mbuf_resize(mb, mb->end);
/* call helpers */
le = us->helpers.head;
while (le) {
struct udp_helper *uh = le->data;
bool hdld;
le = le->next;
hdld = uh->recvh(&src, mb, uh->arg);
if (hdld)
goto out;
}
us->rh(&src, mb, us->arg);
out:
mem_deref(mb);
}
static void udp_read_handler(int flags, void *arg)
{
struct udp_sock *us = arg;
(void)flags;
udp_read(us, us->fd);
}
static void udp_read_handler6(int flags, void *arg)
{
struct udp_sock *us = arg;
(void)flags;
udp_read(us, us->fd6);
}
/**
* Create and listen on a UDP Socket
*
* @param usp Pointer to returned UDP Socket
* @param local Local network address
* @param rh Receive handler
* @param arg Handler argument
*
* @return 0 if success, otherwise errorcode
*/
int udp_listen(struct udp_sock **usp, const struct sa *local,
udp_recv_h *rh, void *arg)
{
struct addrinfo hints, *res = NULL, *r;
struct udp_sock *us = NULL;
char addr[NET_ADDRSTRLEN];
char serv[6] = "0";
int af, error, err = 0;
if (!usp)
return EINVAL;
us = mem_zalloc(sizeof(*us), udp_destructor);
if (!us)
return ENOMEM;
list_init(&us->helpers);
us->fd = -1;
us->fd6 = -1;
if (local) {
af = sa_af(local);
err = sa_ntop(local, addr, sizeof(addr));
(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
if (err)
goto out;
}
else {
#ifdef HAVE_INET6
af = AF_UNSPEC;
#else
af = AF_INET;
#endif
}
memset(&hints, 0, sizeof(hints));
/* set-up hints structure */
hints.ai_family = af;
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
error = getaddrinfo(local ? addr : NULL, serv, &hints, &res);
if (error) {
#ifdef WIN32
DEBUG_WARNING("listen: getaddrinfo: wsaerr=%d\n",
WSAGetLastError());
#endif
DEBUG_WARNING("listen: getaddrinfo: %s:%s (%s)\n",
addr, serv, gai_strerror(error));
err = EADDRNOTAVAIL;
goto out;
}
for (r = res; r; r = r->ai_next) {
int fd = -1;
if (us->fd > 0)
continue;
DEBUG_INFO("listen: for: af=%d addr=%j\n",
r->ai_family, r->ai_addr);
fd = SOK_CAST socket(r->ai_family, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
err = errno;
continue;
}
err = net_sockopt_blocking_set(fd, false);
if (err) {
DEBUG_WARNING("udp listen: nonblock set: %s\n",
strerror(err));
(void)close(fd);
continue;
}
if (bind(fd, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
err = errno;
DEBUG_INFO("listen: bind(): %s (%J)\n",
strerror(err), local);
(void)close(fd);
continue;
}
/* Can we do both IPv4 and IPv6 on same socket? */
if (AF_INET6 == r->ai_family) {
struct sa sa;
int on = 1; /* assume v6only */
#if defined (IPPROTO_IPV6) && defined (IPV6_V6ONLY)
socklen_t on_len = sizeof(on);
if (0 != getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
(char *)&on, &on_len)) {
on = 1;
}
#endif
/* Extra check for unspec addr - MAC OS X/Solaris */
if (0==sa_set_sa(&sa, r->ai_addr) && sa_is_any(&sa)) {
on = 1;
}
DEBUG_INFO("socket %d: IPV6_V6ONLY is %d\n", fd, on);
if (on) {
us->fd6 = fd;
continue;
}
}
/* OK */
us->fd = fd;
break;
}
freeaddrinfo(res);
/* We must have at least one socket */
if (-1 == us->fd && -1 == us->fd6) {
if (0 == err)
err = EADDRNOTAVAIL;
goto out;
}
err = udp_thread_attach(us);
if (err)
goto out;
us->rh = rh ? rh : dummy_udp_recv_handler;
us->arg = arg;
us->rxsz = UDP_RXSZ_DEFAULT;
out:
if (err)
mem_deref(us);
else
*usp = us;
return err;
}
/**
* Connect a UDP Socket to its peer, so we can receive ICMP messages.
* When connected, this UDP Socket will only receive data from that peer.
*
* @param us UDP Socket
* @param conn Connected flag
*/
void udp_connect(struct udp_sock *us, bool conn)
{
if (!us)
return;
us->conn = conn;
}
static int udp_send_internal(struct udp_sock *us, const struct sa *dst,
struct mbuf *mb, struct le *le)
{
struct sa hdst;
int err = 0, fd;
/* check for error in e.g. connected state */
if (us->err) {
err = us->err;
us->err = 0; /* clear error */
return err;
}
/* choose a socket */
if (AF_INET6 == sa_af(dst) && -1 != us->fd6)
fd = us->fd6;
else
fd = us->fd;
/* call helpers in reverse order */
while (le) {
struct udp_helper *uh = le->data;
le = le->prev;
if (dst != &hdst) {
sa_cpy(&hdst, dst);
dst = &hdst;
}
if (uh->sendh(&err, &hdst, mb, uh->arg) || err)
return err;
}
/* Connected socket? */
if (us->conn) {
if (0 != connect(fd, &dst->u.sa, dst->len)) {
DEBUG_WARNING("send: connect: %s\n", strerror(errno));
us->conn = false;
}
if (send(fd, BUF_CAST mb->buf + mb->pos, mb->end - mb->pos,
0) < 0)
return errno;
}
else {
if (sendto(fd, BUF_CAST mb->buf + mb->pos, mb->end - mb->pos,
0, &dst->u.sa, dst->len) < 0)
return errno;
}
return 0;
}
/**
* Send a UDP Datagram to a peer
*
* @param us UDP Socket
* @param dst Destination network address
* @param mb Buffer to send
*
* @return 0 if success, otherwise errorcode
*/
int udp_send(struct udp_sock *us, const struct sa *dst, struct mbuf *mb)
{
if (!us || !dst || !mb)
return EINVAL;
return udp_send_internal(us, dst, mb, us->helpers.tail);
}
/**
* Send an anonymous UDP Datagram to a peer
*
* @param dst Destination network address
* @param mb Buffer to send
*
* @return 0 if success, otherwise errorcode
*/
int udp_send_anon(const struct sa *dst, struct mbuf *mb)
{
struct udp_sock *us;
int err;
if (!dst || !mb)
return EINVAL;
err = udp_listen(&us, NULL, NULL, NULL);
if (err)
return err;
err = udp_send_internal(us, dst, mb, NULL);
mem_deref(us);
return err;
}
/**
* Get the local network address on the UDP Socket
*
* @param us UDP Socket
* @param local The returned local network address
*
* @return 0 if success, otherwise errorcode
*
* @todo bug no way to specify AF
*/
int udp_local_get(const struct udp_sock *us, struct sa *local)
{
if (!us || !local)
return EINVAL;
local->len = sizeof(local->u);
if (0 == getsockname(us->fd, &local->u.sa, &local->len))
return 0;
if (0 == getsockname(us->fd6, &local->u.sa, &local->len))
return 0;
return errno;
}
/**
* Set socket options on the UDP Socket
*
* @param us UDP Socket
* @param level Socket level
* @param optname Option name
* @param optval Option value
* @param optlen Option length
*
* @return 0 if success, otherwise errorcode
*/
int udp_setsockopt(struct udp_sock *us, int level, int optname,
const void *optval, uint32_t optlen)
{
int err = 0;
if (!us)
return EINVAL;
if (-1 != us->fd) {
if (0 != setsockopt(us->fd, level, optname,
BUF_CAST optval, optlen))
err |= errno;
}
if (-1 != us->fd6) {
if (0 != setsockopt(us->fd6, level, optname,
BUF_CAST optval, optlen))
err |= errno;
}
return err;
}
/**
* Set the send/receive buffer size on a UDP Socket
*
* @param us UDP Socket
* @param size Buffer size in bytes
*
* @return 0 if success, otherwise errorcode
*/
int udp_sockbuf_set(struct udp_sock *us, int size)
{
int err = 0;
if (!us)
return EINVAL;
err |= udp_setsockopt(us, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
err |= udp_setsockopt(us, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
return err;
}
/**
* Set the maximum receive chunk size on a UDP Socket
*
* @param us UDP Socket
* @param rxsz Maximum receive chunk size
*/
void udp_rxsz_set(struct udp_sock *us, size_t rxsz)
{
if (!us)
return;
us->rxsz = rxsz;
}
/**
* Set preallocated space on receive buffer.
*
* @param us UDP Socket
* @param rx_presz Size of preallocate space.
*/
void udp_rxbuf_presz_set(struct udp_sock *us, size_t rx_presz)
{
if (!us)
return;
us->rx_presz = rx_presz;
}
/**
* Set receive handler on a UDP Socket
*
* @param us UDP Socket
* @param rh Receive handler
* @param arg Handler argument
*/
void udp_handler_set(struct udp_sock *us, udp_recv_h *rh, void *arg)
{
if (!us)
return;
us->rh = rh ? rh : dummy_udp_recv_handler;
us->arg = arg;
}
/**
* Get the File Descriptor from a UDP Socket
*
* @param us UDP Socket
* @param af Address Family
*
* @return File Descriptor, or -1 for errors
*/
int udp_sock_fd(const struct udp_sock *us, int af)
{
if (!us)
return -1;
switch (af) {
default:
case AF_INET: return us->fd;
case AF_INET6: return (us->fd6 != -1) ? us->fd6 : us->fd;
}
}
/**
* Attach the current thread to the UDP Socket
*
* @param us UDP Socket
*
* @return 0 if success, otherwise errorcode
*/
int udp_thread_attach(struct udp_sock *us)
{
int err = 0;
if (!us)
return EINVAL;
if (-1 != us->fd) {
err = fd_listen(us->fd, FD_READ, udp_read_handler, us);
if (err)
goto out;
}
if (-1 != us->fd6) {
err = fd_listen(us->fd6, FD_READ, udp_read_handler6, us);
if (err)
goto out;
}
out:
if (err)
udp_thread_detach(us);
return err;
}
/**
* Detach the current thread from the UDP Socket
*
* @param us UDP Socket
*/
void udp_thread_detach(struct udp_sock *us)
{
if (!us)
return;
if (-1 != us->fd)
fd_close(us->fd);
if (-1 != us->fd6)
fd_close(us->fd6);
}
static void helper_destructor(void *data)
{
struct udp_helper *uh = data;
list_unlink(&uh->le);
}
static bool sort_handler(struct le *le1, struct le *le2, void *arg)
{
struct udp_helper *uh1 = le1->data, *uh2 = le2->data;
(void)arg;
return uh1->layer <= uh2->layer;
}
/**
* Register a UDP protocol stack helper
*
* @param uhp Pointer to allocated UDP helper object
* @param us UDP socket
* @param layer Layer number; higher number means higher up in stack
* @param sh Send handler
* @param rh Receive handler
* @param arg Handler argument
*
* @return 0 if success, otherwise errorcode
*/
int udp_register_helper(struct udp_helper **uhp, struct udp_sock *us,
int layer,
udp_helper_send_h *sh, udp_helper_recv_h *rh,
void *arg)
{
struct udp_helper *uh;
if (!us)
return EINVAL;
uh = mem_zalloc(sizeof(*uh), helper_destructor);
if (!uh)
return ENOMEM;
list_append(&us->helpers, &uh->le, uh);
uh->layer = layer;
uh->sendh = sh ? sh : helper_send_handler;
uh->recvh = rh ? rh : helper_recv_handler;
uh->arg = arg;
list_sort(&us->helpers, sort_handler, NULL);
if (uhp)
*uhp = uh;
return 0;
}
/**
* Send a UDP Datagram to a remote peer bypassing this helper and
* the helpers above it.
*
* @param us UDP Socket
* @param dst Destination network address
* @param mb Buffer to send
* @param uh UDP Helper
*
* @return 0 if success, otherwise errorcode
*/
int udp_send_helper(struct udp_sock *us, const struct sa *dst,
struct mbuf *mb, struct udp_helper *uh)
{
if (!us || !dst || !mb || !uh)
return EINVAL;
return udp_send_internal(us, dst, mb, uh->le.prev);
}