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