WvStreams
|
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