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.
Definition IObject.h:65
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.
Definition wvbuf.h:242
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...
Definition wvstring.h:94
Base class for streams built on Unix file descriptors.
Definition wvfdstream.h:21
void setfd(int fd)
Sets the file descriptor for both reading and writing.
Definition wvfdstream.h:36
int getfd() const
Returns the Unix file descriptor for reading and writing.
Definition wvfdstream.h:81
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....
Definition wvaddr.h:250
An IP+Port address also includes a port number, with the resulting form www.xxx.yyy....
Definition wvaddr.h:394
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.
Definition wvlistener.h:38
A type-safe version of WvMonikerBase that lets you provide create functions for object types other th...
Definition wvmoniker.h:62
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.
Definition wvstring.h:330
char * edit()
make the string editable, and return a non-const (char*)
Definition wvstring.h:397
WvTCPConn tries to make all outgoing connections asynchronously (in the background).
Definition wvtcp.h:40
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?
Definition wvtcp.h:107
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
the data structure used by pre_select()/post_select() and internally by select().
Definition iwvstream.h:50
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.