/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-1999 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: James da Silva, Systems Design and Analysis Group
* Computer Science Department
* University of Maryland at College Park
*/
/*
* $Id: dgram.c,v 1.32 2006/07/05 19:54:20 martinea Exp $
*
* library routines to marshall/send, recv/unmarshall UDP packets
*/
#include "amanda.h"
#include "dgram.h"
#include "amutil.h"
#include "conffile.h"
#include "sockaddr-util.h"
void
dgram_socket(
dgram_t * dgram,
int socket)
{
if(socket < 0 || socket >= (int)FD_SETSIZE) {
error(_("dgram_socket: socket %d out of range (0 .. %d)\n"),
socket, (int)FD_SETSIZE-1);
/*NOTREACHED*/
}
dgram->socket = socket;
}
int
dgram_bind(
dgram_t * dgram,
sa_family_t family,
in_port_t * portp,
int priv,
char **bind_msg)
{
int s, retries;
int new_s;
socklen_t_equiv len;
sockaddr_union name;
int save_errno;
int *portrange;
int sndbufsize = MAX_DGRAM;
portrange = getconf_intrange(CNF_RESERVED_UDP_PORT);
*portp = (in_port_t)0;
g_debug("dgram_bind: setting up a socket with family %d", family);
if((s = socket(family, SOCK_DGRAM, 0)) == -1) {
save_errno = errno;
dbprintf(_("dgram_bind: socket() failed: %s\n"),
strerror(save_errno));
errno = save_errno;
return -1;
}
if(s < 0 || s >= (int)FD_SETSIZE) {
dbprintf(_("dgram_bind: socket out of range: %d\n"),
s);
aclose(s);
errno = EMFILE; /* out of range */
return -1;
}
/* try setting the buffer size (= maximum allowable UDP packet size) */
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
(void *) &sndbufsize, sizeof(sndbufsize)) < 0) {
dbprintf("dgram_bind: could not set udp send buffer to %d: %s (ignored)\n",
sndbufsize, strerror(errno));
}
SU_INIT(&name, family);
SU_SET_INADDR_ANY(&name);
/*
* If a port range was specified, we try to get a port in that
* range first. Next, we try to get a reserved port. If that
* fails, we just go for any port.
*
* In all cases, not to use port that's assigned to other services.
*
* It is up to the caller to make sure we have the proper permissions
* to get the desired port, and to make sure we return a port that
* is within the range it requires.
*/
for (retries = 0; ; retries++) {
if ((new_s = bind_portrange(s, &name, portrange[0], portrange[1], "udp", priv, bind_msg)) >= 0)
goto out;
dbprintf(_("dgram_bind: Could not bind to port in range: %d - %d.\n"),
portrange[0], portrange[1]);
if (new_s == -1) {
break;
}
if (retries >= BIND_CYCLE_RETRIES) {
dbprintf(_("dgram_bind: Giving up...\n"));
break;
}
dbprintf(_("dgram_bind: Retrying entire range after 15 second delay.\n"));
sleep(15);
}
save_errno = errno;
dbprintf(_("dgram_bind: bind(in6addr_any) failed: %s\n"),
strerror(save_errno));
aclose(s);
errno = save_errno;
return -1;
out:
/* find out what name was actually used */
aclose(s);
len = (socklen_t_equiv)sizeof(name);
if(getsockname(new_s, (struct sockaddr *)&name, &len) == -1) {
save_errno = errno;
dbprintf(_("dgram_bind: getsockname() failed: %s\n"), strerror(save_errno));
errno = save_errno;
aclose(s);
return -1;
}
*portp = SU_GET_PORT(&name);
dgram->socket = new_s;
dbprintf(_("dgram_bind: socket %d bound to %s\n"),
dgram->socket, str_sockaddr(&name));
return 0;
}
int
dgram_send_addr(
sockaddr_union *addr,
dgram_t * dgram)
{
int s, rc;
int socket_opened;
int save_errno;
int max_wait;
int wait_count;
#if defined(USE_REUSEADDR)
const int on = 1;
int r;
#endif
dbprintf(_("dgram_send_addr(addr=%p, dgram=%p)\n"),
addr, dgram);
dump_sockaddr(addr);
dbprintf(_("dgram_send_addr: %p->socket = %d\n"),
dgram, dgram->socket);
if(dgram->socket != -1) {
s = dgram->socket;
socket_opened = 0;
} else {
int sndbufsize = MAX_DGRAM;
g_debug("dgram_send_addr: setting up a socket with family %d", SU_GET_FAMILY(addr));
if((s = socket(SU_GET_FAMILY(addr), SOCK_DGRAM, 0)) == -1) {
save_errno = errno;
dbprintf(_("dgram_send_addr: socket() failed: %s\n"),
strerror(save_errno));
errno = save_errno;
return -1;
}
socket_opened = 1;
#ifdef USE_REUSEADDR
r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(void *)&on, (socklen_t_equiv)sizeof(on));
if (r < 0) {
dbprintf(_("dgram_send_addr: setsockopt(SO_REUSEADDR) failed: %s\n"),
strerror(errno));
}
#endif
/* try setting the buffer size (= maximum allowable UDP packet size) */
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
(void *) &sndbufsize, sizeof(sndbufsize)) < 0) {
dbprintf("dgram_send_addr: could not set udp send buffer to %d: %s (ignored)\n",
sndbufsize, strerror(errno));
}
}
if(s < 0 || s >= (int)FD_SETSIZE) {
dbprintf(_("dgram_send_addr: socket out of range: %d\n"), s);
errno = EMFILE; /* out of range */
rc = -1;
} else {
max_wait = 300 / 5; /* five minutes */
wait_count = 0;
rc = 0;
while(sendto(s,
dgram->data,
dgram->len,
0,
(struct sockaddr *)addr,
SS_LEN(addr)) == -1) {
#ifdef ECONNREFUSED
if(errno == ECONNREFUSED && wait_count++ < max_wait) {
dbprintf(_("dgram_send_addr: sendto(%s): retry %d after ECONNREFUSED\n"),
str_sockaddr(addr),
wait_count);
sleep(5);
continue;
}
#endif
#ifdef EAGAIN
if(errno == EAGAIN && wait_count++ < max_wait) {
dbprintf(_("dgram_send_addr: sendto(%s): retry %d after EAGAIN\n"),
str_sockaddr(addr),
wait_count);
sleep(5);
continue;
}
#endif
save_errno = errno;
dbprintf(_("dgram_send_addr: sendto(%s) failed: %s \n"),
str_sockaddr(addr),
strerror(save_errno));
errno = save_errno;
rc = -1;
break;
}
}
if(socket_opened) {
save_errno = errno;
if(close(s) == -1) {
dbprintf(_("dgram_send_addr: close(%s): failed: %s\n"),
str_sockaddr(addr),
strerror(errno));
/*
* Calling function should not care that the close failed.
* It does care about the send results though.
*/
}
errno = save_errno;
}
return rc;
}
ssize_t
dgram_recv(
dgram_t * dgram,
int timeout,
sockaddr_union *fromaddr)
{
SELECT_ARG_TYPE ready;
struct timeval to;
ssize_t size;
int sock;
socklen_t_equiv addrlen;
ssize_t nfound;
int save_errno;
sock = dgram->socket;
FD_ZERO(&ready);
FD_SET(sock, &ready);
to.tv_sec = timeout;
to.tv_usec = 0;
dbprintf(_("dgram_recv(dgram=%p, timeout=%u, fromaddr=%p socket=%d)\n"),
dgram, timeout, fromaddr, sock);
nfound = (ssize_t)select(sock+1, &ready, NULL, NULL, &to);
if(nfound <= 0 || !FD_ISSET(sock, &ready)) {
save_errno = errno;
if(nfound < 0) {
dbprintf(_("dgram_recv: select() failed: %s\n"), strerror(save_errno));
} else if(nfound == 0) {
dbprintf(plural(_("dgram_recv: timeout after %d second\n"),
_("dgram_recv: timeout after %d seconds\n"),
timeout),
timeout);
nfound = 0;
} else if (!FD_ISSET(sock, &ready)) {
int i;
for(i = 0; i < sock + 1; i++) {
if(FD_ISSET(i, &ready)) {
dbprintf(_("dgram_recv: got fd %d instead of %d\n"), i, sock);
}
}
save_errno = EBADF;
nfound = -1;
}
errno = save_errno;
return nfound;
}
addrlen = (socklen_t_equiv)sizeof(sockaddr_union);
size = recvfrom(sock, dgram->data, (size_t)MAX_DGRAM, 0,
(struct sockaddr *)fromaddr, &addrlen);
if(size == -1) {
save_errno = errno;
dbprintf(_("dgram_recv: recvfrom() failed: %s\n"), strerror(save_errno));
errno = save_errno;
return -1;
}
dump_sockaddr(fromaddr);
dgram->len = (size_t)size;
dgram->data[size] = '\0';
dgram->cur = dgram->data;
return size;
}
void
dgram_zero(
dgram_t * dgram)
{
dgram->cur = dgram->data;
dgram->len = 0;
*(dgram->cur) = '\0';
}
int dgram_cat(dgram_t *dgram, const char *fmt, ...)
{
ssize_t bufsize;
va_list argp;
int len;
assert(dgram != NULL);
assert(fmt != NULL);
assert(dgram->len == (size_t)(dgram->cur - dgram->data));
assert(dgram->len < sizeof(dgram->data));
bufsize = (ssize_t)(sizeof(dgram->data) - dgram->len);
if (bufsize <= 0)
return -1;
arglist_start(argp, fmt);
len = g_vsnprintf(dgram->cur, (size_t)bufsize, fmt, argp);
arglist_end(argp);
if(len < 0) {
return -1;
} else if((ssize_t)len > bufsize) {
dgram->len = sizeof(dgram->data);
dgram->cur = dgram->data + dgram->len;
return -1;
}
else {
dgram->len += len;
dgram->cur = dgram->data + dgram->len;
}
return 0;
}
void
dgram_eatline(
dgram_t * dgram)
{
char *p = dgram->cur;
char *end = dgram->data + dgram->len;
while(p < end && *p && *p != '\n')
p++;
if(*p == '\n')
p++;
dgram->cur = p;
}