/* * 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); }