/* sockstuff.* - socket handling code Copyright (C) 1999-2004 Matthew Mueller 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "sockstuff.h" #include #include #include #include #include #include #include extern "C" { #include "compat/fake-getaddrinfo.h" #include "compat/fake-getnameinfo.h" } #include "log.h" #include "strreps.h" #include "file.h" #include "myregex.h" //comment the next line out if by chance you actually don't have select. #define HAVE_SELECT int sock_timeout=3*60; #ifdef HAVE_WINSOCK_H static void sockstuff_cleanup(void){ WSACleanup(); } void sockstuff_init(void){ WORD wVersionRequested; WSADATA wsaData; int r; wVersionRequested = MAKEWORD(2, 0); if ((r=WSAStartup( wVersionRequested, &wsaData))) { throw FileEx(Ex_INIT,"WSAStartup error: %i",r); } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 ) { /* We couldn't find a usable WinSock DLL. */ WSACleanup( ); throw FileEx(Ex_INIT,"bad winsock version %i.%i", LOBYTE( wsaData.wVersion ) , HIBYTE( wsaData.wVersion )); } atexit(sockstuff_cleanup); } //why the !*#&$ doesn't winsock have a strerror function??? const char* sock_strerror(int e) { switch (e) { case WSAEINTR: return "WSAEINTR"; case WSAEBADF: return "WSAEBADF"; case WSAEACCES: return "WSAEACCES"; case WSAEFAULT: return "WSAEFAULT"; case WSAEINVAL: return "WSAEINVAL"; case WSAEMFILE: return "WSAEMFILE"; case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; case WSAEINPROGRESS: return "WSAEINPROGRESS"; case WSAEALREADY: return "WSAEALREADY"; case WSAENOTSOCK: return "WSAENOTSOCK"; case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; case WSAEMSGSIZE: return "WSAEMSGSIZE"; case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; case WSAEADDRINUSE: return "WSAEADDRINUSE"; case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; case WSAENETDOWN: return "WSAENETDOWN"; case WSAENETUNREACH: return "WSAENETUNREACH"; case WSAENETRESET: return "WSAENETRESET"; case WSAECONNABORTED: return "WSAECONNABORTED"; case WSAECONNRESET: return "WSAECONNRESET"; case WSAENOBUFS: return "WSAENOBUFS"; case WSAEISCONN: return "WSAEISCONN"; case WSAENOTCONN: return "WSAENOTCONN"; case WSAESHUTDOWN: return "WSAESHUTDOWN"; case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; case WSAETIMEDOUT: return "WSAETIMEDOUT"; case WSAECONNREFUSED: return "WSAECONNREFUSED"; case WSAELOOP: return "WSAELOOP"; case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; case WSAEHOSTUNREACH: return "WSAEHOSTUNREACH"; case WSAENOTEMPTY: return "WSAENOTEMPTY"; case WSAEPROCLIM: return "WSAEPROCLIM"; case WSAEUSERS: return "WSAEUSERS"; case WSAEDQUOT: return "WSAEDQUOT"; case WSAESTALE: return "WSAESTALE"; case WSAEREMOTE: return "WSAEREMOTE"; case WSAEDISCON: return "WSAEDISCON"; case WSASYSNOTREADY: return "WSASYSNOTREADY"; case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; case WSANOTINITIALISED: return "WSANOTINITIALISED"; case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; case WSATRY_AGAIN: return "WSATRY_AGAIN"; case WSANO_RECOVERY: return "WSANO_RECOVERY"; case WSANO_DATA: return "WSANO_DATA"; default: return "unknown winsock error"; } } #endif static c_regex_r rfc2732host("\\[(.*)\\](:(.*))?"), rawipv6host(".*:.*:.*"), normalhost("([^:]*)(:(.*))?"); void parse_host(string &saddr, string &sservice, const string &host, const string &defservice="") { sservice=defservice; c_regex_subs subs; if (!rfc2732host.match(host,&subs)) { //actually, rfc2732 indicates that inside the []'s is an ipv6 address only, not ipv4 or a hostname. But that would be harder to implement and not as flexible anyway ;) saddr=subs.subs(1); if (subs.sublen(2)>0) sservice=subs.subs(3); } else if (!rawipv6host.match(host,&subs)) { saddr=host; } else if (!normalhost.match(host,&subs)) { saddr=subs.subs(1); if (subs.sublen(2)>0) sservice=subs.subs(3); } else throw FileEx(Ex_INIT,"parse_host error"); //shouldn't happen. } /* This is a generic function to make a stream connection to a given server/port. service is the default port name/number (can be overridden in netaddress), netaddress is the host name to connect to. The function returns the socket, ready for action.*/ sock_t make_connection(const char *netaddress,const char *service){ PERROR("make_connection(%s,%s)", netaddress, service); sock_t sock; int connected; const char *lasterrorfunc="",*lasterror=""; //save the previous error message so we can include something nice in the exception if all connections fail. Maintains compatibility in the case of hosts that resolve to a single address, and in the case of resolving to multiple, at least it's something. heh. string saddr,sservice; parse_host(saddr, sservice, netaddress, service); struct addrinfo hints, *res, *res0; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; int error; /* resolve address/port into sockaddr */ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(saddr.c_str(), sservice.c_str(), &hints, &res0); if (error) throw FileEx(Ex_INIT, "getaddrinfo: %s", gai_strerror(error)); /* try all the sockaddrs until connection goes successful */ for (res = res0; res; res = res->ai_next) { error = getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); if (error) { PMSG_nnl("Connecting to ...", gai_strerror(error)); } else PMSG_nnl("Connecting to [%s]:%s ... ",hbuf,sbuf); sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (!sock_isvalid(sock)) { lasterrorfunc="socket";lasterror=sock_strerror(sock_errno); PMSG("%s: %s",lasterrorfunc, lasterror); continue; } #if defined(HAVE_SELECT) && defined(HAVE_FCNTL) fcntl(sock,F_SETFL,O_NONBLOCK);//we can set the sock to nonblocking, and use select to enforce a timeout. #endif connected = connect(sock, res->ai_addr, res->ai_addrlen); #if defined(HAVE_SELECT) && defined(HAVE_FCNTL) if (connected<0 && sock_errno==EINPROGRESS){ fd_set w; struct timeval tv; int i; tv.tv_sec=sock_timeout; tv.tv_usec=0; FD_ZERO(&w); FD_SET(sock,&w); if ((i=select(sock+1,NULL,&w,NULL,&tv))>0){ int erp; socklen_t erl=sizeof(erp); if (getsockopt(sock,SOL_SOCKET,SO_ERROR,&erp,&erl)){ lasterrorfunc="getsockopt";lasterror=sock_strerror(sock_errno); PMSG("%s: %s",lasterrorfunc, lasterror); sock_close(sock); continue; } if (erp) { lasterrorfunc="connect";lasterror=sock_strerror(erp); PMSG("%s: %s",lasterrorfunc, lasterror); sock_close(sock); continue; } }else{ lasterrorfunc="make_connection";lasterror="timeout reached"; PMSG("%s: %s",lasterrorfunc, lasterror); sock_close(sock); continue; } fcntl(sock,F_SETFL,0);//set to blocking again (I hope) connected=0; } #endif if (connected < 0) { lasterrorfunc="connect";lasterror=sock_strerror(sock_errno); PMSG("%s: %s",lasterrorfunc, lasterror); sock_close(sock); continue; } freeaddrinfo(res0); PMSG("connected."); return sock; } freeaddrinfo(res0); throw FileEx(Ex_INIT, "%s: %s",lasterrorfunc,lasterror); } /* This is just like the write() system call, accept that it will make sure that all data is transmitted. */ int sock_write_ensured(sock_t sockfd, const char *buf, size_t count) { size_t bytes_sent = 0; int this_write; while (bytes_sent < count) { // do this_write = sock_write(sockfd, buf, count - bytes_sent); // while ( (this_write < 0) && (sock_errno == EINTR) ); if (this_write <= 0) return this_write; bytes_sent += this_write; buf += this_write; } return count; } //like read(), but uses select() to enforce a timeout. read() blocks forever if the connection is lost unexpectadly (modem disconnect, etc) int sock_read(sock_t sockfd, void *buf, size_t count){ #ifdef HAVE_SELECT fd_set r; struct timeval tv; int i; tv.tv_sec=sock_timeout; tv.tv_usec=0; FD_ZERO(&r); FD_SET(sockfd,&r); if ((i=select(sockfd+1,&r,NULL,NULL,&tv))>0){ #endif //return read(sockfd,buf,count); return recv(sockfd, (RECV_ARG2)buf, count, MSG_NOSIGNAL); #ifdef HAVE_SELECT } if (i==0) throw FileEx(Ex_INIT,"sock_read timeout reached (%is)", sock_timeout); return i; #endif } bool sock_datawaiting(sock_t sockfd){ #ifdef HAVE_SELECT fd_set r; struct timeval tv; int i; tv.tv_sec=0; tv.tv_usec=0; FD_ZERO(&r); FD_SET(sockfd,&r); if ((i=select(sockfd+1,&r,NULL,NULL,&tv))>0){ return true; } #endif return false; }