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