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 
66 WV_LINK(WvTCPConn);
67 WV_LINK(WvTCPListener);
68 
69 
70 static IWvStream *creator(WvStringParm s, IObject*)
71 {
72  return new WvTCPConn(s);
73 }
74 
75 static WvMoniker<IWvStream> reg("tcp", creator);
76 
77 
78 static 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 
89 static 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 
104 WvTCPConn::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 
116 WvTCPConn::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 
140  WvIPAddr x(hostname);
141  if (x != WvIPAddr())
142  {
143  remaddr = WvIPPortAddr(x, remaddr.port);
144  resolved = true;
145  do_connect();
146  }
147  else
148  check_resolver();
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  {
328  check_resolver();
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 
375 bool WvTCPConn::isok() const
376 {
377  return !resolved || WvFDStream::isok();
378 }
379 
380 
381 size_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 
426 WvTCPListener::~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 
452 void 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 
WvString::edit
char * edit()
make the string editable, and return a non-const (char*)
Definition: wvstring.h:397
wvtcl_getword
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.
Definition: wvtclstring.cc:359
WvTCPConn::src
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
WvResolver::pre_select
void pre_select(WvStringParm hostname, WvStream::SelectInfo &si)
add all of our waiting fds to an fd_set for use with select().
Definition: wvresolver.cc:316
WvFdStream::isok
virtual bool isok() const
return true if the stream is actually usable right now
Definition: wvfdstream.cc:134
IWvListener::addwrap
virtual void addwrap(IWvListenerWrapper _wrapper)=0
Add a wrapper function for this stream: something that accept() will call to possibly wrap the stream...
WvTCPConn::low_delay
void low_delay()
function to set up a TCP socket the way we like In addition to the nice_tcpopts(),...
Definition: wvtcp.cc:171
WvListener
Definition: wvlistener.h:15
WvTCPConn::WvTCPConn
WvTCPConn(int _fd, const WvIPPortAddr &_remaddr)
Start a WvTCPConn on an already-open socket (used by WvTCPListener)
Definition: wvtcp.cc:104
WvTCPConn::isok
virtual bool isok() const
Is this connection OK? Note: isok() will always be true if !resolved, even though fd==-1.
Definition: wvtcp.cc:375
WvFdStream::uwrite
virtual size_t uwrite(const void *buf, size_t count)
unbuffered I/O functions; these ignore the buffer, which is handled by write().
Definition: wvfdstream.cc:162
WvTCPConn::do_connect
void do_connect()
Connect to the remote end - note the "Protected" above ;)
Definition: wvtcp.cc:191
IWvStream
Definition: iwvstream.h:24
WvTCPConn::~WvTCPConn
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
WvErrorBase::seterr
virtual void seterr(int _errnum)
Set the errnum variable – we have an error.
Definition: wverror.cc:144
IWvListener
Definition: iwvlistener.h:16
WvResolver::post_select
bool post_select(WvStringParm hostname, WvStream::SelectInfo &si)
determines whether the resolving process is complete.
Definition: wvresolver.cc:331
WvTCPListener
Class to easily create the Server side of a WvTCPConn.
Definition: wvtcplistener.h:16
WvConstStringBuffer
A raw memory read-only buffer backed by a constant WvString.
Definition: wvbuf.h:241
WvStream::seterr
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition: wvstream.cc:451
WvFdStream::post_select
virtual bool post_select(SelectInfo &si)
post_select() is called after ::select(), and returns true if this object is now ready.
Definition: wvfdstream.cc:254
WvTCPConn
WvTCPConn tries to make all outgoing connections asynchronously (in the background).
Definition: wvtcp.h:39
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvTCPConn::nice_tcpopts
void nice_tcpopts()
function to set up a TCP socket the way we like (Read/Write, Non-Blocking, KeepAlive)
Definition: wvtcp.cc:160
WvTCPListener::accept
virtual IWvStream * accept()
return a new WvTCPConn socket corresponding to a newly-accepted connection.
Definition: wvtcp.cc:432
WvFdStream::setfd
void setfd(int fd)
Sets the file descriptor for both reading and writing.
Definition: wvfdstream.h:36
IWvStream::SelectInfo
the data structure used by pre_select()/post_select() and internally by select().
Definition: iwvstream.h:50
WvIPPortAddr
An IP+Port address also includes a port number, with the resulting form www.xxx.yyy....
Definition: wvaddr.h:393
WvMoniker
A type-safe version of WvMonikerBase that lets you provide create functions for object types other th...
Definition: wvmoniker.h:61
WvStreamClone
WvStreamClone simply forwards all requests to the "cloned" stream.
Definition: wvstreamclone.h:23
WvFdStream::getfd
int getfd() const
Returns the Unix file descriptor for reading and writing.
Definition: wvfdstream.h:81
WvFdStream::set_close_on_exec
void set_close_on_exec(bool close_on_exec)
Make the fds on this stream close-on-exec or not.
Definition: wvfdstream.cc:107
IObject
Definition: IObject.h:65
WvTCPConn::check_resolver
void check_resolver()
Resolve the remote address, if it was fed in non-IP form.
Definition: wvtcp.cc:236
WvTCPConn::pre_select
virtual void pre_select(SelectInfo &si)
override pre_select() to cause select() results when resolving names.
Definition: wvtcp.cc:291
WvTCPConn::debug_mode
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
WvTCPListener::WvTCPListener
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
WvResolver::findaddr
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.
Definition: wvresolver.cc:149
WvTCPConn::uwrite
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
WvStream::setcallback
void setcallback(IWvStreamCallback _callfunc)
define the callback function for this stream, called whenever the callback() member is run,...
Definition: wvstream.cc:1130
WvTCPConn::isconnected
bool isconnected() const
has the connection been completed yet?
Definition: wvtcp.h:107
WvTCPConn::localaddr
WvIPPortAddr localaddr()
the local address of this socket (ie.
Definition: wvtcp.cc:260
WvIPAddr
An IP address is made up of a "dotted quad" – four decimal numbers in the form www....
Definition: wvaddr.h:249
WvFdStream
Base class for streams built on Unix file descriptors.
Definition: wvfdstream.h:20
hostname
WvString hostname()
Do gethostname() without a fixed-length buffer.
Definition: strutils.cc:870
WvTCPConn::post_select
virtual bool post_select(SelectInfo &si)
override post_select() to set the 'connected' variable as soon as we are connected.
Definition: wvtcp.cc:320
WvFdStream::pre_select
virtual void pre_select(SelectInfo &si)
pre_select() sets up for eventually calling ::select().
Definition: wvfdstream.cc:214
WvIStreamList
WvStreamList holds a list of WvStream objects – and its select() and callback() functions know how to...
Definition: wvistreamlist.h:20
WvFdStream::set_nonblock
void set_nonblock(bool nonblock)
Make the fds on this stream blocking or non-blocking.
Definition: wvfdstream.cc:97
WvTCPListener::src
virtual const WvIPPortAddr * src() const
src() is a bit of a misnomer, but it returns the listener port.
Definition: wvtcp.cc:462
WvListener::isok
virtual bool isok() const
By default, returns true if geterr() == 0.
Definition: wvlistener.h:38