prozilla/libprozilla/src/connect.c

304 lines
7.8 KiB
C

/******************************************************************************
libprozilla - a download accelerator library
Copyright (C) 2001 Kalum Somaratna
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
******************************************************************************/
/* Connection routines. */
/* $Id$ */
#include "common.h"
#include "misc.h"
#include "debug.h"
#include "connect.h"
/******************************************************************************
Connect to the specified server.
******************************************************************************/
uerr_t connect_to_server(int *sock, const char *name, int port,
struct timeval *tout)
{
int status, noblock, flags;
char szPort[10];
extern int h_errno;
int opt;
struct timeval timeout;
struct addrinfo hints, *res = NULL;
int error;
assert(name != NULL);
memcpy(&timeout, tout, sizeof(timeout));
memset(&hints, 0, sizeof(hints));
memset(szPort, 0, sizeof(szPort));
snprintf(szPort, sizeof(szPort), "%d", port);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(name, szPort, &hints, &res);
if (error)
return HOSTERR;
/* Create a socket. */
if ((*sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TCP)) < 1)
{
freeaddrinfo(res);
return CONSOCKERR;
}
/* Experimental. */
flags = fcntl(*sock, F_GETFL, 0);
if (flags != -1)
noblock = fcntl(*sock, F_SETFL, flags | O_NONBLOCK);
else
noblock = -1;
status = connect(*sock, res->ai_addr, res->ai_addrlen);
if ((status == -1) && (noblock != -1) && (errno == EINPROGRESS))
{
fd_set writefd;
FD_ZERO(&writefd);
FD_SET(*sock, &writefd);
status = select((*sock + 1), NULL, &writefd, NULL, &timeout);
/* Do we need to retry if the err is EINTR? */
if (status > 0)
{
socklen_t arglen = sizeof(int);
if (getsockopt(*sock, SOL_SOCKET, SO_ERROR, &status, &arglen) < 0)
status = errno;
if (status != 0)
errno = status, status = -1;
if (errno == EINPROGRESS)
errno = ETIMEDOUT;
}
else if (status == 0)
errno = ETIMEDOUT, status = -1;
}
if (status < 0)
{
close(*sock);
if (errno == ECONNREFUSED)
{
freeaddrinfo(res);
return CONREFUSED;
}
else
{
freeaddrinfo(res);
return CONERROR;
}
}
else
{
flags = fcntl(*sock, F_GETFL, 0);
if (flags != -1)
fcntl(*sock, F_SETFL, flags & ~O_NONBLOCK);
}
/* Enable KEEPALIVE, so dead connections could be closed
* earlier. Useful in conjuction with TCP kernel tuning
* in /proc/sys/net/ipv4/tcp_* files. */
opt = 1;
setsockopt(*sock, SOL_SOCKET, SO_KEEPALIVE,
(char *)&opt, (int)sizeof(opt));
freeaddrinfo(res);
return NOCONERROR;
}
/******************************************************************************
...
******************************************************************************/
uerr_t bind_socket(int *sockfd)
{
struct sockaddr_in serv_addr;
/* Open a TCP socket (an Internet stream socket). */
if ((*sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
return CONSOCKERR;
/* Fill in the structure fields for binding. */
memset((void *)&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(0); /* Let the system choose. */
/* Bind the address to the socket. */
if (bind(*sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
perror("bind");
close(*sockfd);
return BINDERR;
}
/* Allow only one server. */
if (listen(*sockfd, 1) < 0)
{
perror("listen");
close(*sockfd);
return LISTENERR;
}
return BINDOK;
}
/******************************************************************************
...
******************************************************************************/
int select_fd(int fd, struct timeval *timeout, int writep)
{
fd_set fds, exceptfds;
struct timeval to;
FD_ZERO(&fds);
FD_SET(fd, &fds);
FD_ZERO(&exceptfds);
FD_SET(fd, &exceptfds);
memcpy(&to, timeout, sizeof(struct timeval));
return(select(fd + 1, writep ? NULL : &fds, writep ? &fds : NULL,
&exceptfds, &to));
}
/******************************************************************************
Receive size bytes from sock with a time delay.
******************************************************************************/
int krecv(int sock, char *buffer, int size, int flags,
struct timeval *timeout)
{
int ret, arglen;
arglen = sizeof(int);
assert(size >= 0);
do
{
if (timeout)
{
do
{
ret = select_fd(sock, timeout, 0);
} while ((ret == -1) && (errno == EINTR));
if (ret <= 0)
{
/* proz_debug("Error after select res=%d errno=%d.", ret, errno); */
/* Set errno to ETIMEDOUT on timeout. */
if (ret == 0)
errno = ETIMEDOUT;
return -1;
}
}
ret = recv(sock, buffer, size, flags);
} while ((ret == -1) && (errno == EINTR));
return ret;
}
/******************************************************************************
Send size bytes to sock with a time delay.
******************************************************************************/
int ksend(int sock, char *buffer, int size, int flags,
struct timeval *timeout)
{
int ret = 0;
/* write() may write less than size bytes, thus the outward loop
keeps trying it until all was written, or an error occurred. The
inner loop is reserved for the usual EINTR f*kage, and the
innermost loop deals with the same during select(). */
while (size != 0)
{
do
{
if (timeout)
{
do
{
ret = select_fd(sock, timeout, 1);
} while ((ret == -1) && (errno == EINTR));
if (ret <= 0)
{
/* Set errno to ETIMEDOUT on timeout. */
if (ret == 0)
errno = ETIMEDOUT;
return -1;
}
}
ret = send(sock, buffer, size, flags);
} while ((ret == -1) && (errno == EINTR));
if (ret <= 0)
break;
buffer += ret;
size -= ret;
}
return ret;
}
/******************************************************************************
Accept a connection.
******************************************************************************/
uerr_t accept_connection(int listen_sock, int *data_sock)
{
struct sockaddr_in cli_addr;
socklen_t clilen = sizeof(cli_addr);
int sockfd;
sockfd = accept(listen_sock, (struct sockaddr *)&cli_addr, &clilen);
if (sockfd < 0)
{
perror("accept");
return ACCEPTERR;
}
*data_sock = sockfd;
/* Now we can free the listen socket since it is not needed...
accept() returned the new socket... */
close(listen_sock);
return ACCEPTOK;
}