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

Generated on Mon Feb 5 10:54:30 2007 for WvStreams by  doxygen 1.5.1