]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/inspsocket.cpp
Fix sockaddr length argument, BSD will complain if it doesn't exactly match the expec...
[user/henk/code/inspircd.git] / src / inspsocket.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 /* $Core */
15
16 #include "socket.h"
17 #include "inspstring.h"
18 #include "socketengine.h"
19 #include "inspircd.h"
20
21 bool BufferedSocket::Readable()
22 {
23         return (this->state != I_CONNECTING);
24 }
25
26 BufferedSocket::BufferedSocket(InspIRCd* SI)
27 {
28         this->Timeout = NULL;
29         this->state = I_DISCONNECTED;
30         this->fd = -1;
31         this->ServerInstance = SI;
32 }
33
34 BufferedSocket::BufferedSocket(InspIRCd* SI, int newfd, const char* ip)
35 {
36         this->Timeout = NULL;
37         this->fd = newfd;
38         this->state = I_CONNECTED;
39         strlcpy(this->IP,ip,MAXBUF);
40         this->ServerInstance = SI;
41         if (this->fd > -1)
42                 this->ServerInstance->SE->AddFd(this);
43 }
44
45 BufferedSocket::BufferedSocket(InspIRCd* SI, const std::string &ipaddr, int aport, unsigned long maxtime, const std::string &connectbindip)
46 {
47         this->cbindip = connectbindip;
48         this->fd = -1;
49         this->ServerInstance = SI;
50         strlcpy(host,ipaddr.c_str(),MAXBUF);
51         this->Timeout = NULL;
52
53         strlcpy(this->host,ipaddr.c_str(),MAXBUF);
54         this->port = aport;
55
56         bool ipvalid = true;
57 #ifdef IPV6
58         if (strchr(host,':'))
59         {
60                 in6_addr n;
61                 if (inet_pton(AF_INET6, host, &n) < 1)
62                         ipvalid = false;
63         }
64         else
65 #endif
66         {
67                 in_addr n;
68                 if (inet_aton(host,&n) < 1)
69                         ipvalid = false;
70         }
71         if (!ipvalid)
72         {
73                 this->ServerInstance->Logs->Log("SOCKET", DEBUG,"BUG: Hostname passed to BufferedSocket, rather than an IP address!");
74                 this->OnError(I_ERR_CONNECT);
75                 this->Close();
76                 this->fd = -1;
77                 this->state = I_ERROR;
78                 return;
79         }
80         else
81         {
82                 strlcpy(this->IP,host,MAXBUF);
83                 if (!this->DoConnect(maxtime))
84                 {
85                         this->OnError(I_ERR_CONNECT);
86                         this->Close();
87                         this->fd = -1;
88                         this->state = I_ERROR;
89                         return;
90                 }
91         }
92 }
93
94 void BufferedSocket::SetQueues()
95 {
96         // attempt to increase socket sendq and recvq as high as its possible
97         int sendbuf = 32768;
98         int recvbuf = 32768;
99         if(setsockopt(this->fd,SOL_SOCKET,SO_SNDBUF,(const char *)&sendbuf,sizeof(sendbuf)) || setsockopt(this->fd,SOL_SOCKET,SO_RCVBUF,(const char *)&recvbuf,sizeof(sendbuf)))
100         {
101                 //this->ServerInstance->Log(DEFAULT, "Could not increase SO_SNDBUF/SO_RCVBUF for socket %u", GetFd());
102                 ; // do nothing. I'm a little sick of people trying to interpret this message as a result of why their incorrect setups don't work.
103         }
104 }
105
106 bool BufferedSocket::DoBindMagic(const std::string &current_ip, bool v6)
107 {
108         /* The [2] is required because we may write a sockaddr_in6 here, and sockaddr_in6 is larger than sockaddr, where sockaddr_in4 is not. */
109         socklen_t size = sizeof(sockaddr_in);
110         sockaddr* s = new sockaddr[2];
111 #ifdef IPV6
112         if (v6)
113         {
114                 in6_addr n;
115                 if (inet_pton(AF_INET6, current_ip.c_str(), &n) > 0)
116                 {
117                         memcpy(&((sockaddr_in6*)s)->sin6_addr, &n, sizeof(sockaddr_in6));
118                         ((sockaddr_in6*)s)->sin6_port = 0;
119                         ((sockaddr_in6*)s)->sin6_family = AF_INET6;
120                         size = sizeof(sockaddr_in6);
121                 }
122                 else
123                 {
124                         // Well, this is as good as it's gonna get.
125                         errno = EADDRNOTAVAIL;
126                         delete[] s;
127                         return false;
128                 }
129         }
130         else
131 #endif
132         {
133                 in_addr n;
134                 if (inet_aton(current_ip.c_str(), &n) > 0)
135                 {
136                         ((sockaddr_in*)s)->sin_addr = n;
137                         ((sockaddr_in*)s)->sin_port = 0;
138                         ((sockaddr_in*)s)->sin_family = AF_INET;
139                 }
140                 else
141                 {
142                         // Well, this is as good as it's gonna get.
143                         errno = EADDRNOTAVAIL;
144                         delete[] s;
145                         return false;
146                 }
147         }
148
149         if (ServerInstance->SE->Bind(this->fd, s, size) < 0)
150         {
151                 this->state = I_ERROR;
152                 this->OnError(I_ERR_BIND);
153                 delete[] s;
154                 return false;
155         }
156
157         delete[] s;
158         return true;
159 }
160
161 /* Most irc servers require you to specify the ip you want to bind to.
162  * If you dont specify an IP, they rather dumbly bind to the first IP
163  * of the box (e.g. INADDR_ANY). In InspIRCd, we scan thought the IP
164  * addresses we've bound server ports to, and we try and bind our outbound
165  * connections to the first usable non-loopback and non-any IP we find.
166  * This is easier to configure when you have a lot of links and a lot
167  * of servers to configure.
168  */
169 bool BufferedSocket::BindAddr(const std::string &ip_to_bind)
170 {
171         ConfigReader Conf(this->ServerInstance);
172         bool v6 = false;
173
174         // Case one: If they provided an IP, try bind it
175         if (!ip_to_bind.empty())
176         {
177 #ifdef IPV6
178                 // Check whether or not they are binding to an IPv6 IP..
179                 if (ip_to_bind.find(':') != std::string::npos)
180                         v6 = true;
181 #endif
182                 // And if it fails, don't do anything.
183                 return this->DoBindMagic(ip_to_bind, v6);
184         }
185
186         for (int j = 0; j < Conf.Enumerate("bind"); j++)
187         {
188                 // We only want to try bind to a server ip.
189                 if (Conf.ReadValue("bind","type",j) != "servers")
190                         continue;
191
192                 // set current IP to the <bind> tag
193                 std::string current_ip = Conf.ReadValue("bind","address",j);
194
195 #ifdef IPV6
196                 // Check whether this <bind> is for an ipv6 address
197                 if (current_ip.find(':') != std::string::npos)
198                         v6 = true;
199                 else
200                         v6 = false;
201 #endif
202
203                 // Make sure IP is nothing local
204                 if (current_ip == "*" || current_ip == "127.0.0.1" || current_ip.empty() || current_ip == "::1")
205                         continue;
206
207                 // Try bind, don't fail if it doesn't bind though.
208                 if (this->DoBindMagic(current_ip, v6))
209                         return true;
210         }
211
212         // NOTE: You may wonder WTF we are returning *true* here, but that is because there were no custom binds setup, and so we have nothing to do
213         // (remember, outgoing connections without binding are perfectly ok).
214         ServerInstance->Logs->Log("SOCKET", DEBUG,"nothing in the config to bind()!");
215         return true;
216 }
217
218 bool BufferedSocket::DoConnect(unsigned long maxtime)
219 {
220         irc::sockets::sockaddrs addr;
221         irc::sockets::aptosa(this->host, this->port, &addr);
222
223         this->fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
224
225         if (this->fd == -1)
226         {
227                 this->state = I_ERROR;
228                 this->OnError(I_ERR_SOCKET);
229                 return false;
230         }
231
232         if (!this->BindAddr(this->cbindip))
233         {
234                 this->Close();
235                 this->fd = -1;
236                 return false;
237         }
238
239         ServerInstance->SE->NonBlocking(this->fd);
240
241         if (ServerInstance->SE->Connect(this, &addr.sa, sa_size(addr)) == -1)
242         {
243                 if (errno != EINPROGRESS)
244                 {
245                         this->OnError(I_ERR_CONNECT);
246                         this->Close();
247                         this->state = I_ERROR;
248                         return false;
249                 }
250
251                 this->Timeout = new SocketTimeout(this->GetFd(), this->ServerInstance, this, maxtime, this->ServerInstance->Time());
252                 this->ServerInstance->Timers->AddTimer(this->Timeout);
253         }
254
255         this->state = I_CONNECTING;
256         if (this->fd > -1)
257         {
258                 if (!this->ServerInstance->SE->AddFd(this))
259                 {
260                         this->OnError(I_ERR_NOMOREFDS);
261                         this->Close();
262                         this->state = I_ERROR;
263                         return false;
264                 }
265                 this->SetQueues();
266         }
267
268         ServerInstance->Logs->Log("SOCKET", DEBUG,"BufferedSocket::DoConnect success");
269         return true;
270 }
271
272
273 void BufferedSocket::Close()
274 {
275         /* Save this, so we dont lose it,
276          * otherise on failure, error messages
277          * might be inaccurate.
278          */
279         int save = errno;
280         if (this->fd > -1)
281         {
282                 if (this->GetIOHook())
283                 {
284                         try
285                         {
286                                 this->GetIOHook()->OnRawSocketClose(this->fd);
287                         }
288                         catch (CoreException& modexcept)
289                         {
290                                 ServerInstance->Logs->Log("SOCKET", DEFAULT,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
291                         }
292                 }
293                 ServerInstance->SE->Shutdown(this, 2);
294                 if (ServerInstance->SE->Close(this) != -1)
295                         this->OnClose();
296
297                 if (ServerInstance->SocketCull.find(this) == ServerInstance->SocketCull.end())
298                         ServerInstance->SocketCull[this] = this;
299         }
300         errno = save;
301 }
302
303 std::string BufferedSocket::GetIP()
304 {
305         return this->IP;
306 }
307
308 const char* BufferedSocket::Read()
309 {
310         if (!ServerInstance->SE->BoundsCheckFd(this))
311                 return NULL;
312
313         int n = 0;
314         char* ReadBuffer = ServerInstance->GetReadBuffer();
315
316         if (this->GetIOHook())
317         {
318                 int result2 = 0;
319                 int MOD_RESULT = 0;
320                 try
321                 {
322                         MOD_RESULT = this->GetIOHook()->OnRawSocketRead(this->fd, ReadBuffer, ServerInstance->Config->NetBufferSize, result2);
323                 }
324                 catch (CoreException& modexcept)
325                 {
326                         ServerInstance->Logs->Log("SOCKET", DEFAULT,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
327                 }
328                 if (MOD_RESULT < 0)
329                 {
330                         n = -1;
331                         errno = EAGAIN;
332                 }
333                 else
334                 {
335                         n = result2;
336                 }
337         }
338         else
339         {
340                 n = recv(this->fd, ReadBuffer, ServerInstance->Config->NetBufferSize, 0);
341         }
342
343         /*
344          * This used to do some silly bounds checking instead of just passing bufsize - 1 to recv.
345          * Not only does that make absolutely no sense, but it could potentially result in a read buffer's worth
346          * of data being thrown into the bit bucket for no good reason, which is just *stupid*.. do things correctly now.
347          * --w00t (july 2, 2008)
348          */
349         if (n > 0)
350         {
351                 ReadBuffer[n] = 0;
352                 return ReadBuffer;
353         }
354         else
355         {
356                 int err = errno;
357                 if (err == EAGAIN)
358                         return "";
359                 else
360                         return NULL;
361         }
362 }
363
364 /*
365  * This function formerly tried to flush write buffer each call.
366  * While admirable in attempting to get the data out to wherever
367  * it is going, on a full socket, it's just going to syscall write() and
368  * EAGAIN constantly, instead of waiting in the SE to know if it can write
369  * which will chew a bit of CPU.
370  *
371  * So, now this function returns void (take note) and just adds to the sendq.
372  *
373  * It'll get written at a determinate point when the socketengine tells us it can write.
374  *              -- w00t (april 1, 2008)
375  */
376 void BufferedSocket::Write(const std::string &data)
377 {
378         /* Append the data to the back of the queue ready for writing */
379         outbuffer.push_back(data);
380
381         /* Mark ourselves as wanting write */
382         this->ServerInstance->SE->WantWrite(this);
383 }
384
385 bool BufferedSocket::FlushWriteBuffer()
386 {
387         errno = 0;
388         if ((this->fd > -1) && (this->state == I_CONNECTED))
389         {
390                 if (this->GetIOHook())
391                 {
392                         while (outbuffer.size() && (errno != EAGAIN))
393                         {
394                                 try
395                                 {
396                                         /* XXX: The lack of buffering here is NOT a bug, modules implementing this interface have to
397                                          * implement their own buffering mechanisms
398                                          */
399                                         this->GetIOHook()->OnRawSocketWrite(this->fd, outbuffer[0].c_str(), outbuffer[0].length());
400                                         outbuffer.pop_front();
401                                 }
402                                 catch (CoreException& modexcept)
403                                 {
404                                         ServerInstance->Logs->Log("SOCKET", DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
405                                         return true;
406                                 }
407                         }
408                 }
409                 else
410                 {
411                         /* If we have multiple lines, try to send them all,
412                          * not just the first one -- Brain
413                          */
414                         while (outbuffer.size() && (errno != EAGAIN))
415                         {
416                                 /* Send a line */
417                                 int result = ServerInstance->SE->Send(this, outbuffer[0].c_str(), outbuffer[0].length(), 0);
418
419                                 if (result > 0)
420                                 {
421                                         if ((unsigned int)result >= outbuffer[0].length())
422                                         {
423                                                 /* The whole block was written (usually a line)
424                                                  * Pop the block off the front of the queue,
425                                                  * dont set errno, because we are clear of errors
426                                                  * and want to try and write the next block too.
427                                                  */
428                                                 outbuffer.pop_front();
429                                         }
430                                         else
431                                         {
432                                                 std::string temp = outbuffer[0].substr(result);
433                                                 outbuffer[0] = temp;
434                                                 /* We didnt get the whole line out. arses.
435                                                  * Try again next time, i guess. Set errno,
436                                                  * because we shouldnt be writing any more now,
437                                                  * until the socketengine says its safe to do so.
438                                                  */
439                                                 errno = EAGAIN;
440                                         }
441                                 }
442                                 else if (result == 0)
443                                 {
444                                         this->ServerInstance->SE->DelFd(this);
445                                         this->Close();
446                                         return true;
447                                 }
448                                 else if ((result == -1) && (errno != EAGAIN))
449                                 {
450                                         this->OnError(I_ERR_WRITE);
451                                         this->state = I_ERROR;
452                                         this->ServerInstance->SE->DelFd(this);
453                                         this->Close();
454                                         return true;
455                                 }
456                         }
457                 }
458         }
459
460         if ((errno == EAGAIN) && (fd > -1))
461         {
462                 this->ServerInstance->SE->WantWrite(this);
463         }
464
465         return (fd < 0);
466 }
467
468 void SocketTimeout::Tick(time_t)
469 {
470         ServerInstance->Logs->Log("SOCKET", DEBUG,"SocketTimeout::Tick");
471
472         if (ServerInstance->SE->GetRef(this->sfd) != this->sock)
473                 return;
474
475         if (this->sock->state == I_CONNECTING)
476         {
477                 // for connecting sockets, the timeout can occur
478                 // which causes termination of the connection after
479                 // the given number of seconds without a successful
480                 // connection.
481                 this->sock->OnTimeout();
482                 this->sock->OnError(I_ERR_TIMEOUT);
483
484                 /* NOTE: We must set this AFTER DelFd, as we added
485                  * this socket whilst writeable. This means that we
486                  * must DELETE the socket whilst writeable too!
487                  */
488                 this->sock->state = I_ERROR;
489
490                 if (ServerInstance->SocketCull.find(this->sock) == ServerInstance->SocketCull.end())
491                         ServerInstance->SocketCull[this->sock] = this->sock;
492         }
493
494         this->sock->Timeout = NULL;
495 }
496
497 bool BufferedSocket::InternalMarkConnected()
498 {
499         /* Our socket was in write-state, so delete it and re-add it
500          * in read-state.
501          */
502         this->SetState(I_CONNECTED);
503
504         if (this->GetIOHook())
505         {
506                 ServerInstance->Logs->Log("SOCKET",DEBUG,"Hook for raw connect");
507                 try
508                 {
509                         this->GetIOHook()->OnRawSocketConnect(this->fd);
510                 }
511                 catch (CoreException& modexcept)
512                 {
513                         ServerInstance->Logs->Log("SOCKET",DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
514                         return false;
515                 }
516         }
517         return this->OnConnected();
518 }
519
520 void BufferedSocket::SetState(BufferedSocketState s)
521 {
522         this->state = s;
523 }
524
525 BufferedSocketState BufferedSocket::GetState()
526 {
527         return this->state;
528 }
529
530 bool BufferedSocket::OnConnected() { return true; }
531 void BufferedSocket::OnError(BufferedSocketError) { return; }
532 int BufferedSocket::OnDisconnect() { return 0; }
533 bool BufferedSocket::OnDataReady() { return true; }
534 bool BufferedSocket::OnWriteReady()
535 {
536         // Default behaviour: just try write some.
537         return !this->FlushWriteBuffer();
538 }
539 void BufferedSocket::OnTimeout() { return; }
540 void BufferedSocket::OnClose() { return; }
541
542 BufferedSocket::~BufferedSocket()
543 {
544         this->Close();
545         if (Timeout)
546         {
547                 ServerInstance->Timers->DelTimer(Timeout);
548                 Timeout = NULL;
549         }
550 }
551
552 void BufferedSocket::HandleEvent(EventType et, int errornum)
553 {
554         switch (et)
555         {
556                 case EVENT_ERROR:
557                 {
558                         switch (errornum)
559                         {
560                                 case ETIMEDOUT:
561                                         this->OnError(I_ERR_TIMEOUT);
562                                         break;
563                                 case ECONNREFUSED:
564                                 case 0:
565                                         this->OnError(this->state == I_CONNECTING ? I_ERR_CONNECT : I_ERR_WRITE);
566                                         break;
567                                 case EADDRINUSE:
568                                         this->OnError(I_ERR_BIND);
569                                         break;
570                                 case EPIPE:
571                                 case EIO:
572                                         this->OnError(I_ERR_WRITE);
573                                         break;
574                         }
575
576                         if (this->ServerInstance->SocketCull.find(this) == this->ServerInstance->SocketCull.end())
577                                 this->ServerInstance->SocketCull[this] = this;
578                         return;
579                         break;
580                 }
581                 case EVENT_READ:
582                 {
583                         if (!this->OnDataReady())
584                         {
585                                 if (this->ServerInstance->SocketCull.find(this) == this->ServerInstance->SocketCull.end())
586                                         this->ServerInstance->SocketCull[this] = this;
587                                 return;
588                         }
589                         break;
590                 }
591                 case EVENT_WRITE:
592                 {
593                         if (this->state == I_CONNECTING)
594                         {
595                                 if (!this->InternalMarkConnected())
596                                 {
597                                         if (this->ServerInstance->SocketCull.find(this) == this->ServerInstance->SocketCull.end())
598                                                 this->ServerInstance->SocketCull[this] = this;
599                                         return;
600                                 }
601                                 return;
602                         }
603                         else
604                         {
605                                 if (!this->OnWriteReady())
606                                 {
607                                         if (this->ServerInstance->SocketCull.find(this) == this->ServerInstance->SocketCull.end())
608                                                 this->ServerInstance->SocketCull[this] = this;
609                                         return;
610                                 }
611                         }
612                         break;
613                 }
614         }
615 }
616