• Main Page
  • Modules
  • Classes
  • Files
  • File List
  • File Members

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

Generated on Thu Aug 12 2010 11:33:11 for WvStreams by  doxygen 1.7.1