2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
5 * WvStream-based TCP connection class.
7 #include "wvtcplistener.h"
9 #include "wvistreamlist.h"
10 #include "wvmoniker.h"
11 #include "wvlinkerhack.h"
15 #define setsockopt(a,b,c,d,e) setsockopt(a,b,c, (const char*) d,e)
16 #define getsockopt(a,b,c,d,e) getsockopt(a,b,c,(char *)d, e)
18 #define errno GetLastError()
19 #define EWOULDBLOCK WSAEWOULDBLOCK
20 #define EINPROGRESS WSAEINPROGRESS
21 #define EISCONN WSAEISCONN
22 #define EALREADY WSAEALREADY
24 #define EINVAL WSAEINVAL
25 #define SOL_TCP IPPROTO_TCP
26 #define SOL_IP IPPROTO_IP
27 #define FORCE_NONZERO 1
34 # include <sys/socket.h>
40 # include <netinet/in.h>
43 # if HAVE_NETINET_IN_SYSTM_H
44 # include <netinet/in_systm.h>
46 # include <netinet/ip.h>
48 #if HAVE_NETINET_TCP_H
49 # include <netinet/tcp.h>
53 #define FORCE_NONZERO 0
67 WV_LINK(WvTCPListener
);
70 static IWvStream
*creator(WvStringParm s
, IObject
*)
72 return new WvTCPConn(s
);
75 static WvMoniker
<IWvStream
> reg("tcp", creator
);
78 static IWvListener
*listener(WvStringParm s
, IObject
*)
80 WvConstStringBuffer
b(s
);
81 WvString hostport
= wvtcl_getword(b
);
82 WvString wrapper
= b
.getstr();
83 IWvListener
*l
= new WvTCPListener(hostport
);
85 l
->addwrap(wv::bind(&IWvStream::create
, wrapper
, _1
));
89 static WvMoniker
<IWvListener
> lreg("tcp", listener
);
92 WvTCPConn::WvTCPConn(const WvIPPortAddr
&_remaddr
)
94 remaddr
= (_remaddr
.is_zero() && FORCE_NONZERO
)
95 ? WvIPPortAddr("127.0.0.1", _remaddr
.port
) : _remaddr
;
104 WvTCPConn::WvTCPConn(int _fd
, const WvIPPortAddr
&_remaddr
)
107 remaddr
= (_remaddr
.is_zero() && FORCE_NONZERO
)
108 ? WvIPPortAddr("127.0.0.1", _remaddr
.port
) : _remaddr
;
116 WvTCPConn::WvTCPConn(WvStringParm _hostname
, uint16_t _port
)
117 : hostname(_hostname
)
119 struct servent
* serv
;
120 char *hnstr
= hostname
.edit(), *cptr
;
122 cptr
= strchr(hnstr
, ':');
124 cptr
= strchr(hnstr
, '\t');
126 cptr
= strchr(hnstr
, ' ');
130 serv
= getservbyname(cptr
, NULL
);
131 remaddr
.port
= serv
? ntohs(serv
->s_port
) : atoi(cptr
);
135 remaddr
.port
= _port
;
137 resolved
= connected
= false;
140 WvIPAddr
x(hostname
);
143 remaddr
= WvIPPortAddr(x
, remaddr
.port
);
152 WvTCPConn::~WvTCPConn()
158 // Set a few "nice" options on our socket... (read/write, non-blocking,
160 void WvTCPConn::nice_tcpopts()
162 set_close_on_exec(true);
166 setsockopt(getfd(), SOL_SOCKET
, SO_KEEPALIVE
, &value
, sizeof(value
));
171 void WvTCPConn::low_delay()
176 setsockopt(getfd(), SOL_TCP
, TCP_NODELAY
, &value
, sizeof(value
));
179 value
= IPTOS_LOWDELAY
;
180 setsockopt(getfd(), SOL_IP
, IP_TOS
, &value
, sizeof(value
));
185 void WvTCPConn::debug_mode()
188 setsockopt(getfd(), SOL_SOCKET
, SO_KEEPALIVE
, &value
, sizeof(value
));
191 void WvTCPConn::do_connect()
195 int rwfd
= socket(PF_INET
, SOCK_STREAM
, 0);
207 WvIPPortAddr
newaddr(remaddr
);
209 // Win32 doesn't like to connect to 0.0.0.0:port; it means "any address
210 // on the local machine", so let's just force localhost
212 WvIPPortAddr
newaddr(WvIPAddr(remaddr
)==zero
213 ? WvIPAddr("127.0.0.1") : remaddr
,
216 sockaddr
*sa
= newaddr
.sockaddr();
217 int ret
= connect(getfd(), sa
, newaddr
.sockaddr_len()), err
= errno
;
220 if (ret
== 0 || (ret
< 0 && err
== EISCONN
))
223 && err
!= EINPROGRESS
224 && err
!= EWOULDBLOCK
227 && err
!= EINVAL
/* apparently winsock 1.1 might do this */)
229 connected
= true; // "connection phase" is ended, anyway
236 void WvTCPConn::check_resolver()
239 int dnsres
= dns
.findaddr(0, hostname
, &ipr
);
245 seterr(WvString("Unknown host \"%s\"", hostname
));
249 // fprintf(stderr, "%p: resolver succeeded!\n", this);
250 remaddr
= WvIPPortAddr(*ipr
, remaddr
.port
);
256 #ifndef SO_ORIGINAL_DST
257 # define SO_ORIGINAL_DST 80
260 WvIPPortAddr
WvTCPConn::localaddr()
263 socklen_t sl
= sizeof(sin
);
266 return WvIPPortAddr();
270 // getsockopt() with SO_ORIGINAL_DST is for transproxy of incoming
271 // connections. For outgoing (and for windows) use just use good
272 // old getsockname().
273 (!incoming
|| getsockopt(getfd(), SOL_IP
,
274 SO_ORIGINAL_DST
, (char*)&sin
, &sl
) < 0) &&
276 getsockname(getfd(), (sockaddr
*)&sin
, &sl
))
278 return WvIPPortAddr();
281 return WvIPPortAddr(&sin
);
285 const WvIPPortAddr
*WvTCPConn::src() const
291 void WvTCPConn::pre_select(SelectInfo
&si
)
294 dns
.pre_select(hostname
, si
);
298 bool oldw
= si
.wants
.writable
;
299 if (!isconnected()) {
300 si
.wants
.writable
= true;
302 // WINSOCK INSANITY ALERT!
304 // In Unix, you detect the success OR failure of a non-blocking
305 // connect() by select()ing with the socket in the write set.
306 // HOWEVER, in Windows, you detect the success of connect() by
307 // select()ing with the socket in the write set, and the
308 // failure of connect() by select()ing with the socket in the
310 si
.wants
.isexception
= true;
313 WvFDStream::pre_select(si
);
314 si
.wants
.writable
= oldw
;
320 bool WvTCPConn::post_select(SelectInfo
&si
)
326 if (dns
.post_select(hostname
, si
))
330 return true; // oops, failed to resolve the name!
335 result
= WvFDStream::post_select(si
);
336 if (result
&& !connected
)
338 // the manual for connect() says just re-calling connect() later
339 // will return either EISCONN or the error code from the previous
340 // failed connection attempt. However, in *some* OSes (like
341 // Windows, at least) a failed connection attempt resets the
342 // socket back to "connectable" state, so every connect() call
343 // will just restart the background connecting process and we'll
344 // never get a result out. Thus, we *first* check SO_ERROR. If
345 // that returns no error, then maybe the socket is connected, or
346 // maybe they just didn't feel like giving us our error yet.
347 // Only then, call connect() to look for EISCONN or another error.
349 socklen_t res_size
= sizeof(conn_res
);
350 if (getsockopt(getfd(), SOL_SOCKET
, SO_ERROR
,
351 &conn_res
, &res_size
))
355 connected
= true; // not in connecting phase anymore
357 else if (conn_res
!= 0)
361 connected
= true; // not in connecting phase anymore
365 // connect succeeded! Double check by re-calling connect().
375 bool WvTCPConn::isok() const
377 return !resolved
|| WvFDStream::isok();
381 size_t WvTCPConn::uwrite(const void *buf
, size_t count
)
384 return WvFDStream::uwrite(buf
, count
);
386 return 0; // can't write yet; let them enqueue it instead
392 WvTCPListener::WvTCPListener(const WvIPPortAddr
&_listenport
)
393 : WvListener(new WvFdStream(socket(PF_INET
, SOCK_STREAM
, 0)))
395 WvFdStream
*fds
= (WvFdStream
*)cloned
;
396 listenport
= _listenport
;
397 sockaddr
*sa
= listenport
.sockaddr();
401 fds
->set_close_on_exec(true);
402 fds
->set_nonblock(true);
404 || setsockopt(getfd(), SOL_SOCKET
, SO_REUSEADDR
, &x
, sizeof(x
))
405 || bind(getfd(), sa
, listenport
.sockaddr_len())
406 || listen(getfd(), 5))
412 if (listenport
.port
== 0) // auto-select a port number
414 socklen_t namelen
= listenport
.sockaddr_len();
416 if (getsockname(getfd(), sa
, &namelen
) != 0)
419 listenport
= WvIPPortAddr((sockaddr_in
*)sa
);
426 WvTCPListener::~WvTCPListener()
432 IWvStream
*WvTCPListener::accept()
434 struct sockaddr_in sin
;
435 socklen_t len
= sizeof(sin
);
437 if (!isok()) return NULL
;
439 int newfd
= ::accept(getfd(), (struct sockaddr
*)&sin
, &len
);
441 return wrap(new WvTCPConn(newfd
, WvIPPortAddr(&sin
)));
442 else if (errno
== EAGAIN
|| errno
== EINTR
)
443 return NULL
; // this listener is doing weird stuff
452 void WvTCPListener::accept_callback(WvIStreamList
*list
,
453 wv::function
<void(IWvStream
*)> cb
,
456 WvStreamClone
*conn
= new WvStreamClone(_conn
);
457 conn
->setcallback(wv::bind(cb
, conn
));
458 list
->append(conn
, true, "WvTCPConn");
462 const WvIPPortAddr
*WvTCPListener::src() const