WvStreams
wvtcp.cc
1/*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4 *
5 * WvStream-based TCP connection class.
6 */
7#include "wvtcplistener.h"
8#include "wvtcp.h"
9#include "wvistreamlist.h"
10#include "wvmoniker.h"
11#include "wvlinkerhack.h"
12#include <fcntl.h>
13
14#ifdef _WIN32
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)
17#undef errno
18#define errno GetLastError()
19#define EWOULDBLOCK WSAEWOULDBLOCK
20#define EINPROGRESS WSAEINPROGRESS
21#define EISCONN WSAEISCONN
22#define EALREADY WSAEALREADY
23#undef EINVAL
24#define EINVAL WSAEINVAL
25#define SOL_TCP IPPROTO_TCP
26#define SOL_IP IPPROTO_IP
27#define FORCE_NONZERO 1
28#else
29# if HAVE_STDLIB_H
30# include <stdlib.h>
31# endif
32#endif
33#if HAVE_SYS_SOCKET_H
34# include <sys/socket.h>
35#endif
36#if HAVE_NETDB_H
37# include <netdb.h>
38#endif
39#if HAVE_NETINET_IN_H
40# include <netinet/in.h>
41#endif
42#if HAVE_NETINET_IP_H
43# if HAVE_NETINET_IN_SYSTM_H
44# include <netinet/in_systm.h>
45# endif
46# include <netinet/ip.h>
47#endif
48#if HAVE_NETINET_TCP_H
49# include <netinet/tcp.h>
50#endif
51
52#ifndef FORCE_NONZERO
53#define FORCE_NONZERO 0
54#endif
55
56#ifdef SOLARIS
57#define SOL_TCP 6
58#define SOL_IP 0
59#endif
60
61#ifdef MACOS
62#define SOL_TCP 6
63#define SOL_IP 0
64#endif
65
66WV_LINK(WvTCPConn);
67WV_LINK(WvTCPListener);
68
69
70static IWvStream *creator(WvStringParm s, IObject*)
71{
72 return new WvTCPConn(s);
73}
74
75static WvMoniker<IWvStream> reg("tcp", creator);
76
77
78static IWvListener *listener(WvStringParm s, IObject *)
79{
81 WvString hostport = wvtcl_getword(b);
82 WvString wrapper = b.getstr();
83 IWvListener *l = new WvTCPListener(hostport);
84 if (l && !!wrapper)
85 l->addwrap(wv::bind(&IWvStream::create, wrapper, _1));
86 return l;
87}
88
89static WvMoniker<IWvListener> lreg("tcp", listener);
90
91
93{
94 remaddr = (_remaddr.is_zero() && FORCE_NONZERO)
95 ? WvIPPortAddr("127.0.0.1", _remaddr.port) : _remaddr;
96 resolved = true;
97 connected = false;
98 incoming = false;
99
100 do_connect();
101}
102
103
104WvTCPConn::WvTCPConn(int _fd, const WvIPPortAddr &_remaddr)
105 : WvFDStream(_fd)
106{
107 remaddr = (_remaddr.is_zero() && FORCE_NONZERO)
108 ? WvIPPortAddr("127.0.0.1", _remaddr.port) : _remaddr;
109 resolved = true;
110 connected = true;
111 incoming = true;
112 nice_tcpopts();
113}
114
115
116WvTCPConn::WvTCPConn(WvStringParm _hostname, uint16_t _port)
117 : hostname(_hostname)
118{
119 struct servent* serv;
120 char *hnstr = hostname.edit(), *cptr;
121
122 cptr = strchr(hnstr, ':');
123 if (!cptr)
124 cptr = strchr(hnstr, '\t');
125 if (!cptr)
126 cptr = strchr(hnstr, ' ');
127 if (cptr)
128 {
129 *cptr++ = 0;
130 serv = getservbyname(cptr, NULL);
131 remaddr.port = serv ? ntohs(serv->s_port) : atoi(cptr);
132 }
133
134 if (_port)
135 remaddr.port = _port;
136
137 resolved = connected = false;
138 incoming = false;
139
141 if (x != WvIPAddr())
142 {
143 remaddr = WvIPPortAddr(x, remaddr.port);
144 resolved = true;
145 do_connect();
146 }
147 else
149}
150
151
153{
154 // nothing to do
155}
156
157
158// Set a few "nice" options on our socket... (read/write, non-blocking,
159// keepalive)
161{
162 set_close_on_exec(true);
163 set_nonblock(true);
164
165 int value = 1;
166 setsockopt(getfd(), SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value));
167 low_delay();
168}
169
170
172{
173 int value;
174
175 value = 1;
176 setsockopt(getfd(), SOL_TCP, TCP_NODELAY, &value, sizeof(value));
177
178#ifndef _WIN32
179 value = IPTOS_LOWDELAY;
180 setsockopt(getfd(), SOL_IP, IP_TOS, &value, sizeof(value));
181#endif
182}
183
184
186{
187 int value = 0;
188 setsockopt(getfd(), SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value));
189}
190
192{
193 if (getfd() < 0)
194 {
195 int rwfd = socket(PF_INET, SOCK_STREAM, 0);
196 if (rwfd < 0)
197 {
198 seterr(errno);
199 return;
200 }
201 setfd(rwfd);
202
203 nice_tcpopts();
204 }
205
206#ifndef _WIN32
207 WvIPPortAddr newaddr(remaddr);
208#else
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
211 WvIPAddr zero;
212 WvIPPortAddr newaddr(WvIPAddr(remaddr)==zero
213 ? WvIPAddr("127.0.0.1") : remaddr,
214 remaddr.port);
215#endif
216 sockaddr *sa = newaddr.sockaddr();
217 int ret = connect(getfd(), sa, newaddr.sockaddr_len()), err = errno;
218 assert(ret <= 0);
219
220 if (ret == 0 || (ret < 0 && err == EISCONN))
221 connected = true;
222 else if (ret < 0
223 && err != EINPROGRESS
224 && err != EWOULDBLOCK
225 && err != EAGAIN
226 && err != EALREADY
227 && err != EINVAL /* apparently winsock 1.1 might do this */)
228 {
229 connected = true; // "connection phase" is ended, anyway
230 seterr(err);
231 }
232 delete sa;
233}
234
235
237{
238 const WvIPAddr *ipr;
239 int dnsres = dns.findaddr(0, hostname, &ipr);
240
241 if (dnsres == 0)
242 {
243 // error resolving!
244 resolved = true;
245 seterr(WvString("Unknown host \"%s\"", hostname));
246 }
247 else if (dnsres > 0)
248 {
249 // fprintf(stderr, "%p: resolver succeeded!\n", this);
250 remaddr = WvIPPortAddr(*ipr, remaddr.port);
251 resolved = true;
252 do_connect();
253 }
254}
255
256#ifndef SO_ORIGINAL_DST
257# define SO_ORIGINAL_DST 80
258#endif
259
261{
262 sockaddr_in sin;
263 socklen_t sl = sizeof(sin);
264
265 if (!isok())
266 return WvIPPortAddr();
267
268 if (
269#ifndef _WIN32
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) &&
275#endif
276 getsockname(getfd(), (sockaddr *)&sin, &sl))
277 {
278 return WvIPPortAddr();
279 }
280
281 return WvIPPortAddr(&sin);
282}
283
284
286{
287 return &remaddr;
288}
289
290
292{
293 if (!resolved)
294 dns.pre_select(hostname, si);
295
296 if (resolved)
297 {
298 bool oldw = si.wants.writable;
299 if (!isconnected()) {
300 si.wants.writable = true;
301#ifdef _WIN32
302 // WINSOCK INSANITY ALERT!
303 //
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
309 // exception set!
310 si.wants.isexception = true;
311#endif
312 }
314 si.wants.writable = oldw;
315 return;
316 }
317}
318
319
321{
322 bool result = false;
323
324 if (!resolved)
325 {
326 if (dns.post_select(hostname, si))
327 {
329 if (!isok())
330 return true; // oops, failed to resolve the name!
331 }
332 }
333 else
334 {
335 result = WvFDStream::post_select(si);
336 if (result && !connected)
337 {
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.
348 int conn_res = -1;
349 socklen_t res_size = sizeof(conn_res);
350 if (getsockopt(getfd(), SOL_SOCKET, SO_ERROR,
351 &conn_res, &res_size))
352 {
353 // getsockopt failed
354 seterr(errno);
355 connected = true; // not in connecting phase anymore
356 }
357 else if (conn_res != 0)
358 {
359 // connect failed
360 seterr(conn_res);
361 connected = true; // not in connecting phase anymore
362 }
363 else
364 {
365 // connect succeeded! Double check by re-calling connect().
366 do_connect();
367 }
368 }
369 }
370
371 return result;
372}
373
374
375bool WvTCPConn::isok() const
376{
377 return !resolved || WvFDStream::isok();
378}
379
380
381size_t WvTCPConn::uwrite(const void *buf, size_t count)
382{
383 if (connected)
384 return WvFDStream::uwrite(buf, count);
385 else
386 return 0; // can't write yet; let them enqueue it instead
387}
388
389
390
391
393 : WvListener(new WvFdStream(socket(PF_INET, SOCK_STREAM, 0)))
394{
395 WvFdStream *fds = (WvFdStream *)cloned;
396 listenport = _listenport;
397 sockaddr *sa = listenport.sockaddr();
398
399 int x = 1;
400
401 fds->set_close_on_exec(true);
402 fds->set_nonblock(true);
403 if (getfd() < 0
404 || setsockopt(getfd(), SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x))
405 || bind(getfd(), sa, listenport.sockaddr_len())
406 || listen(getfd(), 5))
407 {
408 seterr(errno);
409 return;
410 }
411
412 if (listenport.port == 0) // auto-select a port number
413 {
414 socklen_t namelen = listenport.sockaddr_len();
415
416 if (getsockname(getfd(), sa, &namelen) != 0)
417 seterr(errno);
418 else
419 listenport = WvIPPortAddr((sockaddr_in *)sa);
420 }
421
422 delete sa;
423}
424
425
426WvTCPListener::~WvTCPListener()
427{
428 close();
429}
430
431
433{
434 struct sockaddr_in sin;
435 socklen_t len = sizeof(sin);
436
437 if (!isok()) return NULL;
438
439 int newfd = ::accept(getfd(), (struct sockaddr *)&sin, &len);
440 if (newfd >= 0)
441 return wrap(new WvTCPConn(newfd, WvIPPortAddr(&sin)));
442 else if (errno == EAGAIN || errno == EINTR)
443 return NULL; // this listener is doing weird stuff
444 else
445 {
446 seterr(errno);
447 return NULL;
448 }
449}
450
451
452void WvTCPListener::accept_callback(WvIStreamList *list,
453 wv::function<void(IWvStream*)> cb,
454 IWvStream *_conn)
455{
456 WvStreamClone *conn = new WvStreamClone(_conn);
457 conn->setcallback(wv::bind(cb, conn));
458 list->append(conn, true, "WvTCPConn");
459}
460
461
463{
464 return &listenport;
465}
466
The basic interface which is included by all other XPLC interfaces and objects.
virtual void addwrap(IWvListenerWrapper _wrapper)=0
Add a wrapper function for this stream: something that accept() will call to possibly wrap the stream...
A raw memory read-only buffer backed by a constant WvString.
virtual void seterr(int _errnum)
Set the errnum variable – we have an error.
Definition wverror.cc:144
A WvFastString acts exactly like a WvString, but can take (const char *) strings without needing to a...
Base class for streams built on Unix file descriptors.
void setfd(int fd)
Sets the file descriptor for both reading and writing.
int getfd() const
Returns the Unix file descriptor for reading and writing.
virtual bool isok() const
return true if the stream is actually usable right now
void set_nonblock(bool nonblock)
Make the fds on this stream blocking or non-blocking.
Definition wvfdstream.cc:97
void set_close_on_exec(bool close_on_exec)
Make the fds on this stream close-on-exec or not.
virtual bool post_select(SelectInfo &si)
post_select() is called after select(), and returns true if this object is now ready.
virtual void pre_select(SelectInfo &si)
pre_select() sets up for eventually calling select().
virtual size_t uwrite(const void *buf, size_t count)
unbuffered I/O functions; these ignore the buffer, which is handled by write().
An IP address is made up of a "dotted quad" – four decimal numbers in the form www....
An IP+Port address also includes a port number, with the resulting form www.xxx.yyy....
WvStreamList holds a list of WvStream objects – and its select() and callback() functions know how to...
virtual bool isok() const
By default, returns true if geterr() == 0.
A type-safe version of WvMonikerBase that lets you provide create functions for object types other th...
bool post_select(WvStringParm hostname, WvStream::SelectInfo &si)
determines whether the resolving process is complete.
int findaddr(int msec_timeout, WvStringParm name, WvIPAddr const **addr, WvIPAddrList *addrlist=NULL)
Return -1 on timeout, or the number of addresses found, which may be 0 if the address does not exist.
void pre_select(WvStringParm hostname, WvStream::SelectInfo &si)
add all of our waiting fds to an fd_set for use with select().
WvStreamClone simply forwards all requests to the "cloned" stream.
void setcallback(IWvStreamCallback _callfunc)
define the callback function for this stream, called whenever the callback() member is run,...
Definition wvstream.cc:1129
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition wvstream.cc:451
WvString is an implementation of a simple and efficient printable-string class.
char * edit()
make the string editable, and return a non-const (char*)
WvTCPConn tries to make all outgoing connections asynchronously (in the background).
virtual bool isok() const
Is this connection OK? Note: isok() will always be true if !resolved, even though fd==-1.
Definition wvtcp.cc:375
void debug_mode()
function to set up a TCP socket the way we don't like: turn the timeouts way down so that network err...
Definition wvtcp.cc:185
void low_delay()
function to set up a TCP socket the way we like In addition to the nice_tcpopts(),...
Definition wvtcp.cc:171
void nice_tcpopts()
function to set up a TCP socket the way we like (Read/Write, Non-Blocking, KeepAlive)
Definition wvtcp.cc:160
virtual bool post_select(SelectInfo &si)
override post_select() to set the 'connected' variable as soon as we are connected.
Definition wvtcp.cc:320
void check_resolver()
Resolve the remote address, if it was fed in non-IP form.
Definition wvtcp.cc:236
virtual ~WvTCPConn()
Destructor - rarely do you need to call this - close() is a much better way to tear down a TCP Stream...
Definition wvtcp.cc:152
virtual size_t uwrite(const void *buf, size_t count)
unbuffered I/O functions; these ignore the buffer, which is handled by write().
Definition wvtcp.cc:381
bool isconnected() const
has the connection been completed yet?
WvTCPConn(int _fd, const WvIPPortAddr &_remaddr)
Start a WvTCPConn on an already-open socket (used by WvTCPListener)
Definition wvtcp.cc:104
virtual void pre_select(SelectInfo &si)
override pre_select() to cause select() results when resolving names.
Definition wvtcp.cc:291
virtual const WvIPPortAddr * src() const
return the remote address (source of all incoming packets), which is a constant for any given TCP con...
Definition wvtcp.cc:285
void do_connect()
Connect to the remote end - note the "Protected" above ;)
Definition wvtcp.cc:191
WvIPPortAddr localaddr()
the local address of this socket (ie.
Definition wvtcp.cc:260
Class to easily create the Server side of a WvTCPConn.
virtual IWvStream * accept()
return a new WvTCPConn socket corresponding to a newly-accepted connection.
Definition wvtcp.cc:432
virtual const WvIPPortAddr * src() const
src() is a bit of a misnomer, but it returns the listener port.
Definition wvtcp.cc:462
WvTCPListener(const WvIPPortAddr &_listenport)
Create a WvStream that listens on _listenport of the current machine This is how you set up a TCP Ser...
Definition wvtcp.cc:392
WvString hostname()
Do gethostname() without a fixed-length buffer.
Definition strutils.cc:870
WvString wvtcl_getword(WvBuf &buf, const WvStringMask &splitchars=WVTCL_SPLITCHARS, bool do_unescape=true)
Get a single tcl word from an input buffer, and return the rest of the buffer untouched.
the data structure used by pre_select()/post_select() and internally by select().