WvStreams
wvtcp.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * WvStream-based TCP connection class.
00006  */
00007 #include "wvtcplistener.h"
00008 #include "wvtcp.h"
00009 #include "wvistreamlist.h"
00010 #include "wvmoniker.h"
00011 #include "wvlinkerhack.h"
00012 #include <fcntl.h>
00013 
00014 #ifdef _WIN32
00015 #define setsockopt(a,b,c,d,e) setsockopt(a,b,c, (const char*) d,e)
00016 #define getsockopt(a,b,c,d,e) getsockopt(a,b,c,(char *)d, e) 
00017 #undef errno
00018 #define errno GetLastError()
00019 #define EWOULDBLOCK WSAEWOULDBLOCK
00020 #define EINPROGRESS WSAEINPROGRESS
00021 #define EISCONN WSAEISCONN
00022 #define EALREADY WSAEALREADY
00023 #undef EINVAL
00024 #define EINVAL WSAEINVAL
00025 #define SOL_TCP IPPROTO_TCP
00026 #define SOL_IP IPPROTO_IP
00027 #define FORCE_NONZERO 1
00028 #else
00029 # if HAVE_STDLIB_H
00030 #  include <stdlib.h>
00031 # endif
00032 #endif
00033 #if HAVE_SYS_SOCKET_H
00034 # include <sys/socket.h>
00035 #endif
00036 #if HAVE_NETDB_H
00037 # include <netdb.h>
00038 #endif
00039 #if HAVE_NETINET_IN_H
00040 # include <netinet/in.h>
00041 #endif
00042 #if HAVE_NETINET_IP_H
00043 # if HAVE_NETINET_IN_SYSTM_H
00044 #  include <netinet/in_systm.h>
00045 # endif
00046 # include <netinet/ip.h>
00047 #endif
00048 #if HAVE_NETINET_TCP_H
00049 # include <netinet/tcp.h>
00050 #endif
00051 
00052 #ifndef FORCE_NONZERO
00053 #define FORCE_NONZERO 0
00054 #endif
00055 
00056 WV_LINK(WvTCPConn);
00057 WV_LINK(WvTCPListener);
00058 
00059 
00060 static IWvStream *creator(WvStringParm s, IObject*)
00061 {
00062     return new WvTCPConn(s);
00063 }
00064 
00065 static WvMoniker<IWvStream> reg("tcp", creator);
00066 
00067 
00068 static IWvListener *listener(WvStringParm s, IObject *)
00069 {
00070     WvConstStringBuffer b(s);
00071     WvString hostport = wvtcl_getword(b);
00072     WvString wrapper = b.getstr();
00073     IWvListener *l = new WvTCPListener(hostport);
00074     if (l && !!wrapper)
00075         l->addwrap(wv::bind(&IWvStream::create, wrapper, _1));
00076     return l;
00077 }
00078 
00079 static WvMoniker<IWvListener> lreg("tcp", listener);
00080 
00081 
00082 WvTCPConn::WvTCPConn(const WvIPPortAddr &_remaddr)
00083 {
00084     remaddr = (_remaddr.is_zero() && FORCE_NONZERO)
00085         ? WvIPPortAddr("127.0.0.1", _remaddr.port) : _remaddr;
00086     resolved = true;
00087     connected = false;
00088     incoming = false;
00089     
00090     do_connect();
00091 }
00092 
00093 
00094 WvTCPConn::WvTCPConn(int _fd, const WvIPPortAddr &_remaddr)
00095     : WvFDStream(_fd)
00096 {
00097     remaddr = (_remaddr.is_zero() && FORCE_NONZERO)
00098         ? WvIPPortAddr("127.0.0.1", _remaddr.port) : _remaddr;
00099     resolved = true;
00100     connected = true;
00101     incoming = true;
00102     nice_tcpopts();
00103 }
00104 
00105 
00106 WvTCPConn::WvTCPConn(WvStringParm _hostname, uint16_t _port)
00107     : hostname(_hostname)
00108 {
00109     struct servent* serv;
00110     char *hnstr = hostname.edit(), *cptr;
00111     
00112     cptr = strchr(hnstr, ':');
00113     if (!cptr)
00114         cptr = strchr(hnstr, '\t');
00115     if (!cptr)
00116         cptr = strchr(hnstr, ' ');
00117     if (cptr)
00118     {
00119         *cptr++ = 0;
00120         serv = getservbyname(cptr, NULL);
00121         remaddr.port = serv ? ntohs(serv->s_port) : atoi(cptr);
00122     }
00123     
00124     if (_port)
00125         remaddr.port = _port;
00126     
00127     resolved = connected = false;
00128     incoming = false;
00129     
00130     WvIPAddr x(hostname);
00131     if (x != WvIPAddr())
00132     {
00133         remaddr = WvIPPortAddr(x, remaddr.port);
00134         resolved = true;
00135         do_connect();
00136     }
00137     else
00138         check_resolver();
00139 }
00140 
00141 
00142 WvTCPConn::~WvTCPConn()
00143 {
00144     // nothing to do
00145 }
00146 
00147 
00148 // Set a few "nice" options on our socket... (read/write, non-blocking, 
00149 // keepalive)
00150 void WvTCPConn::nice_tcpopts()
00151 {
00152     set_close_on_exec(true);
00153     set_nonblock(true);
00154     
00155     int value = 1;
00156     setsockopt(getfd(), SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value));
00157     low_delay();
00158 }
00159 
00160 
00161 void WvTCPConn::low_delay()
00162 {
00163     int value;
00164     
00165     value = 1;
00166     setsockopt(getfd(), SOL_TCP, TCP_NODELAY, &value, sizeof(value));
00167     
00168 #ifndef _WIN32
00169     value = IPTOS_LOWDELAY;
00170     setsockopt(getfd(), SOL_IP, IP_TOS, &value, sizeof(value));
00171 #endif
00172 }
00173 
00174 
00175 void WvTCPConn::debug_mode()
00176 {
00177     int value = 0;
00178     setsockopt(getfd(), SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value));
00179 }
00180 
00181 void WvTCPConn::do_connect()
00182 {
00183     if (getfd() < 0)
00184     {
00185         int rwfd = socket(PF_INET, SOCK_STREAM, 0);
00186         if (rwfd < 0)
00187         {
00188             seterr(errno);
00189             return;
00190         }
00191         setfd(rwfd);
00192         
00193         nice_tcpopts();
00194     }
00195     
00196 #ifndef _WIN32
00197     WvIPPortAddr newaddr(remaddr);
00198 #else
00199     // Win32 doesn't like to connect to 0.0.0.0:port; it means "any address
00200     // on the local machine", so let's just force localhost
00201     WvIPAddr zero;
00202     WvIPPortAddr newaddr(WvIPAddr(remaddr)==zero
00203                             ? WvIPAddr("127.0.0.1") : remaddr,
00204                          remaddr.port);
00205 #endif
00206     sockaddr *sa = newaddr.sockaddr();
00207     int ret = connect(getfd(), sa, newaddr.sockaddr_len()), err = errno;
00208     assert(ret <= 0);
00209     
00210     if (ret == 0 || (ret < 0 && err == EISCONN))
00211         connected = true;
00212     else if (ret < 0
00213              && err != EINPROGRESS
00214              && err != EWOULDBLOCK
00215              && err != EAGAIN
00216              && err != EALREADY
00217              && err != EINVAL /* apparently winsock 1.1 might do this */)
00218     {
00219         connected = true; // "connection phase" is ended, anyway
00220         seterr(err);
00221     }
00222     delete sa;
00223 }
00224 
00225 
00226 void WvTCPConn::check_resolver()
00227 {
00228     const WvIPAddr *ipr;
00229     int dnsres = dns.findaddr(0, hostname, &ipr);
00230     
00231     if (dnsres == 0)
00232     {
00233         // error resolving!
00234         resolved = true;
00235         seterr(WvString("Unknown host \"%s\"", hostname));
00236     }
00237     else if (dnsres > 0)
00238     {
00239         // fprintf(stderr, "%p: resolver succeeded!\n", this);
00240         remaddr = WvIPPortAddr(*ipr, remaddr.port);
00241         resolved = true;
00242         do_connect();
00243     }
00244 }
00245 
00246 #ifndef SO_ORIGINAL_DST
00247 # define SO_ORIGINAL_DST 80
00248 #endif
00249 
00250 WvIPPortAddr WvTCPConn::localaddr()
00251 {
00252     sockaddr_in sin;
00253     socklen_t sl = sizeof(sin);
00254     
00255     if (!isok())
00256         return WvIPPortAddr();
00257     
00258     if (
00259 #ifndef _WIN32
00260         // getsockopt() with SO_ORIGINAL_DST is for transproxy of incoming
00261         // connections.  For outgoing (and for windows) use just use good
00262         // old getsockname().
00263         (!incoming || getsockopt(getfd(), SOL_IP,
00264                                  SO_ORIGINAL_DST, (char*)&sin, &sl) < 0) &&
00265 #endif
00266         getsockname(getfd(), (sockaddr *)&sin, &sl))
00267     {
00268         return WvIPPortAddr();
00269     }
00270     
00271     return WvIPPortAddr(&sin);
00272 }
00273 
00274 
00275 const WvIPPortAddr *WvTCPConn::src() const
00276 {
00277     return &remaddr;
00278 }
00279 
00280 
00281 void WvTCPConn::pre_select(SelectInfo &si)
00282 {
00283     if (!resolved)
00284         dns.pre_select(hostname, si);
00285 
00286     if (resolved) 
00287     {
00288         bool oldw = si.wants.writable;
00289         if (!isconnected()) {
00290             si.wants.writable = true; 
00291 #ifdef _WIN32
00292             // WINSOCK INSANITY ALERT!
00293             // 
00294             // In Unix, you detect the success OR failure of a non-blocking
00295             // connect() by select()ing with the socket in the write set.
00296             // HOWEVER, in Windows, you detect the success of connect() by
00297             // select()ing with the socket in the write set, and the
00298             // failure of connect() by select()ing with the socket in the
00299             // exception set!
00300             si.wants.isexception = true;
00301 #endif
00302         }
00303         WvFDStream::pre_select(si);
00304         si.wants.writable = oldw;
00305         return;
00306     }
00307 }
00308                           
00309 
00310 bool WvTCPConn::post_select(SelectInfo &si)
00311 {
00312     bool result = false;
00313 
00314     if (!resolved)
00315     {
00316         if (dns.post_select(hostname, si))
00317         {
00318             check_resolver();
00319             if (!isok())
00320                 return true; // oops, failed to resolve the name!
00321         }
00322     }
00323     else
00324     {
00325         result = WvFDStream::post_select(si);
00326         if (result && !connected)
00327         {
00328             // the manual for connect() says just re-calling connect() later
00329             // will return either EISCONN or the error code from the previous
00330             // failed connection attempt.  However, in *some* OSes (like
00331             // Windows, at least) a failed connection attempt resets the 
00332             // socket back to "connectable" state, so every connect() call
00333             // will just restart the background connecting process and we'll
00334             // never get a result out.  Thus, we *first* check SO_ERROR.  If
00335             // that returns no error, then maybe the socket is connected, or
00336             // maybe they just didn't feel like giving us our error yet.
00337             // Only then, call connect() to look for EISCONN or another error.
00338             int conn_res = -1;
00339             socklen_t res_size = sizeof(conn_res);
00340             if (getsockopt(getfd(), SOL_SOCKET, SO_ERROR,
00341                            &conn_res, &res_size))
00342             {
00343                 // getsockopt failed
00344                 seterr(errno);
00345                 connected = true; // not in connecting phase anymore
00346             }
00347             else if (conn_res != 0)
00348             {
00349                 // connect failed
00350                 seterr(conn_res);
00351                 connected = true; // not in connecting phase anymore
00352             }
00353             else
00354             {
00355                 // connect succeeded!  Double check by re-calling connect().
00356                 do_connect();
00357             }
00358         }
00359     }
00360     
00361     return result;
00362 }
00363 
00364 
00365 bool WvTCPConn::isok() const
00366 {
00367     return !resolved || WvFDStream::isok();
00368 }
00369 
00370 
00371 size_t WvTCPConn::uwrite(const void *buf, size_t count)
00372 {
00373     if (connected)
00374         return WvFDStream::uwrite(buf, count);
00375     else
00376         return 0; // can't write yet; let them enqueue it instead
00377 }
00378 
00379 
00380 
00381 
00382 WvTCPListener::WvTCPListener(const WvIPPortAddr &_listenport)
00383         : WvListener(new WvFdStream(socket(PF_INET, SOCK_STREAM, 0)))
00384 {
00385     WvFdStream *fds = (WvFdStream *)cloned;
00386     listenport = _listenport;
00387     sockaddr *sa = listenport.sockaddr();
00388     
00389     int x = 1;
00390 
00391     fds->set_close_on_exec(true);
00392     fds->set_nonblock(true);
00393     if (getfd() < 0
00394         || setsockopt(getfd(), SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x))
00395         || bind(getfd(), sa, listenport.sockaddr_len())
00396         || listen(getfd(), 5))
00397     {
00398         seterr(errno);
00399         return;
00400     }
00401     
00402     if (listenport.port == 0) // auto-select a port number
00403     {
00404         socklen_t namelen = listenport.sockaddr_len();
00405         
00406         if (getsockname(getfd(), sa, &namelen) != 0)
00407             seterr(errno);
00408         else
00409             listenport = WvIPPortAddr((sockaddr_in *)sa);
00410     }
00411     
00412     delete sa;
00413 }
00414 
00415 
00416 WvTCPListener::~WvTCPListener()
00417 {
00418     close();
00419 }
00420 
00421 
00422 IWvStream *WvTCPListener::accept()
00423 {
00424     struct sockaddr_in sin;
00425     socklen_t len = sizeof(sin);
00426     
00427     if (!isok()) return NULL;
00428 
00429     int newfd = ::accept(getfd(), (struct sockaddr *)&sin, &len);
00430     if (newfd >= 0)
00431         return wrap(new WvTCPConn(newfd, WvIPPortAddr(&sin)));
00432     else if (errno == EAGAIN || errno == EINTR)
00433         return NULL; // this listener is doing weird stuff
00434     else
00435     {
00436         seterr(errno);
00437         return NULL;
00438     }
00439 }
00440 
00441 
00442 void WvTCPListener::auto_accept(WvIStreamList *list,
00443                                 wv::function<void(IWvStream*)> cb)
00444 {
00445     onaccept(wv::bind(&WvTCPListener::accept_callback, this, list,
00446                          cb, _1));
00447 }
00448 
00449 void WvTCPListener::auto_accept(wv::function<void(IWvStream*)> cb)
00450 {
00451     auto_accept(&WvIStreamList::globallist, cb);
00452 }
00453 
00454 
00455 void WvTCPListener::accept_callback(WvIStreamList *list,
00456                                     wv::function<void(IWvStream*)> cb,
00457                                     IWvStream *_conn)
00458 {
00459     WvStreamClone *conn = new WvStreamClone(_conn);
00460     conn->setcallback(wv::bind(cb, conn));
00461     list->append(conn, true, "WvTCPConn");
00462 }
00463 
00464 
00465 const WvIPPortAddr *WvTCPListener::src() const
00466 {
00467     return &listenport;
00468 }
00469