/*
* socklib.c
*
* Handy functions for IPv4 socket connections.
*
* Author: Dario Rodriguez dario@softhome.net
* This file is licensed under the terms of the GNU LGPL v2+
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include "socklib.h"
typedef struct _sselect {
fd_set readset;
fd_set writeset;
int maxfd;
int numfd;
fd_set readresult;
fd_set writeresult;
int sizeresult;
int readresultreturned;
int writeresultreturned;
int sizeuserptr;
void **userptr;
} _sselect;
#ifndef FILLTV
#define FILLTV(tv,sec,usec) (tv).tv_sec=(sec),(tv).tv_usec=(usec)
#endif
#ifndef FILLIPV4ADDR
#define FILLIPV4ADDR(a,family,addr,port) \
memset(&(a),0,sizeof(struct sockaddr_in)),\
s.sin_family=family,\
s.sin_addr.s_addr=addr,\
s.sin_port=htons(port)
#endif
#define USERPTRBLOCKSIZE 1024
static int sselect_adduserptr(_sselect *ssel, int fd, void *userptr);
static int sselect_clearuserptr(_sselect *ssel, int fd);
char *
ipv4_genip(char *hostname, long *resulthostsize)
{
struct addrinfo hints,*ai;
struct sockaddr_in *in;
char *host;
memset(&hints,0,sizeof(hints));
hints.ai_family=AF_INET;
if(getaddrinfo(hostname,NULL,&hints,&ai)!=0)
return(NULL);
in=(struct sockaddr_in *)ai->ai_addr;
if(in->sin_family!=AF_INET) {
freeaddrinfo(ai),ai=NULL;
return(NULL);
}
*resulthostsize=4;
if(in==NULL || (host=malloc(*resulthostsize))==NULL) {
freeaddrinfo(ai),ai=NULL;
return(NULL);
}
memcpy(host,&(in->sin_addr),*resulthostsize);
freeaddrinfo(ai),ai=NULL;
return(host);
}
int
ipv4_genport(char *portname, int fallback)
{
struct addrinfo hints,*ai;
struct sockaddr_in *in;
int port;
if(*portname>='0' && *portname<='9')
return(atoi(portname));
memset(&hints,0,sizeof(hints));
hints.ai_family=AF_INET;
if(getaddrinfo(NULL,portname,&hints,&ai)!=0)
return(fallback);
if((in=((struct sockaddr_in *)ai->ai_addr))==NULL) {
freeaddrinfo(ai),ai=NULL;
return(fallback);
}
port=htons(in->sin_port);
freeaddrinfo(ai),ai=NULL;
return(port);
}
int
ipv4_preconnect(char *host, long hostsize, int port) /* setup socket, set non-blocking, connect(2) call */
{
struct sockaddr_in c,s;
int fd;
int res,err;
FILLIPV4ADDR(c,AF_INET,htonl(INADDR_ANY),0);
FILLIPV4ADDR(c,AF_INET,htonl(INADDR_ANY),port);
memcpy(&(s.sin_addr.s_addr),host,hostsize);
if((fd=socket(AF_INET,SOCK_STREAM,0 /* any protocol */))==-1)
return(-1);
if(bind(fd,(struct sockaddr *)&c,sizeof(c))!=0) {
close(fd),fd=-1;
return(-1);
}
sock_setblocking(fd,0);
res=connect(fd,(struct sockaddr *)&s,sizeof(s));
err=errno;
if(res==-1 && err!=EINPROGRESS) {
close(fd),fd=-1;
return(-1);
}
return(fd);
}
int
ipv4_connect(int fd, long timeoutmsec) /* tests writeability */
{
struct timeval tv;
fd_set wset;
FILLTV(tv,timeoutmsec/1000L,(timeoutmsec%(1000L))*1000L);
FD_ZERO(&wset);
FD_SET(fd,&wset);
if(select(fd+1,NULL,&wset,NULL,&tv)>0)
return(0);
return(-1);
}
int
ipv4_postconnect(int fd) /* hopefully connect(2) suceeded, set blocking */
{
sock_setblocking(fd,1);
return(0);
}
int
ipv4_server(int port)
{
return(ipv4_serverbinded(NULL,0,port));
}
int
ipv4_serverbinded(char *host, long hostsize, int port)
{
struct sockaddr_in s;
int fd;
if((fd=socket(AF_INET,SOCK_STREAM,0 /* any protocol */))==-1)
return(-1);
FILLIPV4ADDR(s,AF_INET,htonl(INADDR_ANY),port);
if(host!=NULL)
memcpy(&(s.sin_addr.s_addr),host,hostsize);
if(bind(fd,(struct sockaddr *)&s,sizeof(s))!=0) {
close(fd),fd=-1;
return(-1);
}
if(listen(fd,4)==-1) {
close(fd),fd=-1;
return(-1);
}
return(fd);
}
int
sock_accept(int fd)
{
int newfd;
struct sockaddr_in s;
socklen_t slen;
slen=sizeof(s);
if(fd==-1 || (newfd=accept(fd,(struct sockaddr *)&s,&slen))==-1)
return(-1);
return(newfd);
}
int
sock_getinfo(int fd, int *iplen, char *ip, int *port) /* ip must be at least 16 bytes to have room for an ipv6 address */
{
struct sockaddr c;
struct sockaddr_in *c4;
struct sockaddr_in6 *c6;
socklen_t clen;
if(fd==-1)
return(-1);
clen=sizeof(c);
if(getsockname(fd,(struct sockaddr *)&c,&clen)==-1)
return(-1);
if(c.sa_family!=AF_INET && c.sa_family!=AF_INET6)
return(-1);
if(c.sa_family==AF_INET) {
c4=(struct sockaddr_in *) &c;
*iplen=sizeof(c4->sin_addr.s_addr);
memcpy(ip,&(c4->sin_addr.s_addr),*iplen);
*port=ntohs(c4->sin_port);
} else {
c6=(struct sockaddr_in6 *) &c;
*iplen=sizeof(c6->sin6_addr);
memcpy(ip,&(c6->sin6_addr),*iplen);
*port=ntohs(c6->sin6_port);
}
return(0);
}
int
sock_queued(int fd)
{
int n;
if(ioctl(fd,FIONREAD,&n)!=0)
return(-1);
return(n);
}
int
sock_setblocking(int fd, int block)
{
int fl;
if((fl=fcntl(fd,F_GETFL,0))==-1)
return(-1);
fl=(block)?(fl&(~O_NONBLOCK)):(fl|O_NONBLOCK);
return(fcntl(fd,F_SETFL,fl));
}
int
sock_readable(int fd) /* tests readability */
{
struct timeval tv;
fd_set rset;
FILLTV(tv,0,0);
FD_ZERO(&rset);
FD_SET(fd,&rset);
if(select(fd+1,&rset,NULL,NULL,&tv)>0)
return(0);
return(-1);
}
sselect *
sselect_init(void)
{
_sselect *ssel;
if((ssel=malloc(sizeof(_sselect)))==NULL)
return(NULL);
memset(ssel,0,sizeof(_sselect));
sselect_reset((sselect *)ssel);
return((sselect *)ssel);
}
void
sselect_free(sselect *paramssel)
{
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL)
return;
if(ssel->userptr!=NULL) {
ssel->sizeuserptr=0;
free(ssel->userptr),ssel->userptr=NULL;
}
free(ssel),ssel=NULL;
}
int
sselect_reset(sselect *paramssel)
{
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL)
return(-1);
FD_ZERO(&(ssel->readset));
FD_ZERO(&(ssel->writeset));
ssel->maxfd=0;
ssel->numfd=0;
FD_ZERO(&(ssel->readresult));
FD_ZERO(&(ssel->writeresult));
memset(ssel->userptr,0,sizeof(void *)*ssel->sizeuserptr);
return(0);
}
int
sselect_addread(sselect *paramssel, int fd, void *userptr)
{
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fd<0 || FD_ISSET(fd,&(ssel->readset)))
return(-1);
if(userptr!=NULL) {
if(sselect_adduserptr(ssel, fd, userptr)!=0)
return(-1);
} else
sselect_clearuserptr(ssel,fd);
FD_SET(fd,&(ssel->readset));
if(fd>ssel->maxfd)
ssel->maxfd=fd;
ssel->numfd++;
return(0);
}
int
sselect_addwrite(sselect *paramssel, int fd, void *userptr)
{
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fd<0 || FD_ISSET(fd,&(ssel->writeset)))
return(-1);
if(userptr!=NULL) {
if(sselect_adduserptr(ssel, fd, userptr)!=0)
return(-1);
} else
sselect_clearuserptr(ssel,fd);
FD_SET(fd,&(ssel->writeset));
if(fd>ssel->maxfd)
ssel->maxfd=fd;
ssel->numfd++;
return(0);
}
int
sselect_delread(sselect *paramssel, int fd)
{
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fd<0 || !FD_ISSET(fd,&(ssel->readset)))
return(-1);
FD_CLR(fd,&(ssel->readset));
if(fd>0 && fd==ssel->maxfd && !FD_ISSET(fd,&(ssel->writeset)))
ssel->maxfd=fd-1;
ssel->numfd--;
return(0);
}
int
sselect_delwrite(sselect *paramssel, int fd)
{
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fd<0 || !FD_ISSET(fd,&(ssel->writeset)))
return(-1);
FD_CLR(fd,&(ssel->writeset));
if(fd>0 && fd==ssel->maxfd && !FD_ISSET(fd,&(ssel->readset)))
ssel->maxfd=fd-1;
ssel->numfd--;
return(0);
}
int
sselect_wait(sselect *paramssel, int ms)
{
struct timeval tv;
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || ms<0)
return(-1);
memcpy(&(ssel->readresult),&(ssel->readset),sizeof(fd_set));
memcpy(&(ssel->writeresult),&(ssel->writeset),sizeof(fd_set));
ssel->readresultreturned=0;
ssel->writeresultreturned=0;
FILLTV(tv,(ms/1000),(ms%1000)*1000);
if((ssel->sizeresult=select(
ssel->maxfd+1,&(ssel->readresult),
&(ssel->writeresult),NULL,&tv))<0)
return(-1);
return(ssel->sizeresult);
}
int
sselect_getread(sselect *paramssel, int *fds, int sizefds)
{
int i,n;
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fds==NULL || sizefds<1)
return(-1);
for(n=0,i=ssel->readresultreturned;i<=ssel->maxfd && n<sizefds;i++) {
if(FD_ISSET(i,&(ssel->readresult))) {
fds[n++]=i;
FD_CLR(i,&(ssel->readresult));
}
}
ssel->readresultreturned=i;
return(n);
}
int
sselect_getwrite(sselect *paramssel, int *fds, int sizefds)
{
int i,n;
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fds==NULL || sizefds<1)
return(-1);
for(n=0,i=ssel->writeresultreturned;i<=ssel->maxfd && n<sizefds;i++) {
if(FD_ISSET(i,&(ssel->writeresult))) {
fds[n++]=i;
FD_CLR(i,&(ssel->writeresult));
}
}
ssel->writeresultreturned=i;
return(n);
}
/* advanced sselect functions */
int
sselect_getreadfiltered(sselect *paramssel, fd_set *filter, int *fds, int sizefds)
{
int i,n;
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fds==NULL || sizefds<1)
return(-1);
for(n=0,i=0;i<=ssel->maxfd && n<sizefds;i++) {
if(FD_ISSET(i,&(ssel->readresult)) && FD_ISSET(i,filter)) {
fds[n++]=i;
FD_CLR(i,&(ssel->readresult));
}
}
return(n);
}
int
sselect_getwritefiltered(sselect *paramssel, fd_set *filter, int *fds, int sizefds)
{
int i,n;
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fds==NULL || sizefds<1)
return(-1);
for(n=0,i=0;i<=ssel->maxfd && n<sizefds;i++) {
if(FD_ISSET(i,&(ssel->writeresult)) && FD_ISSET(i,filter)) {
fds[n++]=i;
FD_CLR(i,&(ssel->writeresult));
}
}
return(n);
}
void *
sselect_getuserptr(sselect *paramssel, int fd)
{
_sselect *ssel=(_sselect *)paramssel;
if(ssel==NULL || fd<0 || fd>=ssel->sizeuserptr)
return(NULL);
return(ssel->userptr[fd]);
}
/* aux functions */
void
sock_setfast(int fd)
{
int val;
val=1;
setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(void *)&val,sizeof(val));
}
void
sock_setunsafe(int fd)
{
int val;
struct linger ltime;
val=1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(void *)&val,sizeof(val));
ltime.l_onoff=1;
ltime.l_linger=0;
setsockopt(fd,SOL_SOCKET,SO_LINGER,(void *)<ime,sizeof(ltime));
}
void
sock_setsafe(int fd)
{
int val;
struct linger ltime;
val=0;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(void *)&val,sizeof(val));
ltime.l_onoff=0;
ltime.l_linger=1;
setsockopt(fd,SOL_SOCKET,SO_LINGER,(void *)<ime,sizeof(ltime));
}
/* local functions */
static int
sselect_adduserptr(_sselect *ssel, int fd, void *userptr)
{
if(ssel==NULL || fd<0)
return(-1);
if(ssel->sizeuserptr<(fd+1)) {
void **newptr;
int newsize;
newsize=(fd+USERPTRBLOCKSIZE)/USERPTRBLOCKSIZE;
newsize*=USERPTRBLOCKSIZE;
if((newptr=realloc(ssel->userptr,sizeof(void *)*newsize))==NULL)
return(-1);
ssel->userptr=newptr;
memset(ssel->userptr+ssel->sizeuserptr,0,sizeof(void *)*(newsize-ssel->sizeuserptr));
ssel->sizeuserptr=newsize;
}
ssel->userptr[fd]=userptr;
return(0);
}
static int
sselect_clearuserptr(_sselect *ssel, int fd)
{
if(ssel==NULL || fd<0)
return(-1);
if(ssel->sizeuserptr<(fd+1))
return(0);
ssel->userptr[fd]=NULL;
return(0);
}