]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/inspsocket.cpp
Remove more unnecessary header traffic
[user/henk/code/inspircd.git] / src / inspsocket.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "socket.h"
15 #include "inspstring.h"
16 #include "socketengine.h"
17 #include "inspircd.h"
18
19 using irc::sockets::OpenTCPSocket;
20
21 bool InspSocket::Readable()
22 {
23         return ((this->state != I_CONNECTING) && (this->WaitingForWriteEvent == false));
24 }
25
26 InspSocket::InspSocket(InspIRCd* SI)
27 {
28         this->Timeout = NULL;
29         this->state = I_DISCONNECTED;
30         this->fd = -1;
31         this->WaitingForWriteEvent = false;
32         this->Instance = SI;
33         this->IsIOHooked = false;
34 }
35
36 InspSocket::InspSocket(InspIRCd* SI, int newfd, const char* ip)
37 {
38         this->Timeout = NULL;
39         this->fd = newfd;
40         this->state = I_CONNECTED;
41         strlcpy(this->IP,ip,MAXBUF);
42         this->WaitingForWriteEvent = false;
43         this->Instance = SI;
44         this->IsIOHooked = false;
45         if (this->fd > -1)
46                 this->Instance->SE->AddFd(this);
47 }
48
49 InspSocket::InspSocket(InspIRCd* SI, const std::string &ipaddr, int aport, bool listening, unsigned long maxtime, const std::string &connectbindip)
50 {
51         this->cbindip = connectbindip;
52         this->fd = -1;
53         this->Instance = SI;
54         strlcpy(host,ipaddr.c_str(),MAXBUF);
55         this->WaitingForWriteEvent = false;
56         this->IsIOHooked = false;
57         this->Timeout = NULL;
58         if (listening)
59         {
60                 if ((this->fd = OpenTCPSocket(host)) == ERROR)
61                 {
62                         this->fd = -1;
63                         this->state = I_ERROR;
64                         this->OnError(I_ERR_SOCKET);
65                         return;
66                 }
67                 else
68                 {
69                         if (!SI->BindSocket(this->fd,aport,(char*)ipaddr.c_str()))
70                         {
71                                 this->Close();
72                                 this->fd = -1;
73                                 this->state = I_ERROR;
74                                 this->OnError(I_ERR_BIND);
75                                 this->ClosePending = true;
76                                 return;
77                         }
78                         else
79                         {
80                                 this->state = I_LISTENING;
81                                 this->port = aport;
82                                 if (this->fd > -1)
83                                 {
84                                         if (!this->Instance->SE->AddFd(this))
85                                         {
86                                                 this->Close();
87                                                 this->state = I_ERROR;
88                                                 this->OnError(I_ERR_NOMOREFDS);
89                                         }
90                                 }
91                                 return;
92                         }
93                 }
94         }
95         else
96         {
97                 strlcpy(this->host,ipaddr.c_str(),MAXBUF);
98                 this->port = aport;
99
100                 bool ipvalid = true;
101 #ifdef IPV6
102                 if (strchr(host,':'))
103                 {
104                         in6_addr n;
105                         if (inet_pton(AF_INET6, host, &n) < 1)
106                                 ipvalid = false;
107                 }
108                 else
109 #endif
110                 {
111                         in_addr n;
112                         if (inet_aton(host,&n) < 1)
113                                 ipvalid = false;
114                 }
115                 if (!ipvalid)
116                 {
117                         this->Instance->Log(DEBUG,"BUG: Hostname passed to InspSocket, rather than an IP address!");
118                         this->OnError(I_ERR_CONNECT);
119                         this->Close();
120                         this->fd = -1;
121                         this->state = I_ERROR;
122                         return;
123                 }
124                 else
125                 {
126                         strlcpy(this->IP,host,MAXBUF);
127                         timeout_val = maxtime;
128                         if (!this->DoConnect())
129                         {
130                                 this->OnError(I_ERR_CONNECT);
131                                 this->Close();
132                                 this->fd = -1;
133                                 this->state = I_ERROR;
134                                 return;
135                         }
136                 }
137         }
138 }
139
140 void InspSocket::WantWrite()
141 {
142         this->Instance->SE->WantWrite(this);
143         this->WaitingForWriteEvent = true;
144 }
145
146 void InspSocket::SetQueues(int nfd)
147 {
148         // attempt to increase socket sendq and recvq as high as its possible
149         int sendbuf = 32768;
150         int recvbuf = 32768;
151         if(setsockopt(nfd,SOL_SOCKET,SO_SNDBUF,(const char *)&sendbuf,sizeof(sendbuf)) || setsockopt(nfd,SOL_SOCKET,SO_RCVBUF,(const char *)&recvbuf,sizeof(sendbuf)))
152                 this->Instance->Log(DEFAULT, "Could not increase SO_SNDBUF/SO_RCVBUF for socket %u", GetFd());
153 }
154
155 /* Most irc servers require you to specify the ip you want to bind to.
156  * If you dont specify an IP, they rather dumbly bind to the first IP
157  * of the box (e.g. INADDR_ANY). In InspIRCd, we scan thought the IP
158  * addresses we've bound server ports to, and we try and bind our outbound
159  * connections to the first usable non-loopback and non-any IP we find.
160  * This is easier to configure when you have a lot of links and a lot
161  * of servers to configure.
162  */
163 bool InspSocket::BindAddr(const std::string &ip)
164 {
165         ConfigReader Conf(this->Instance);
166         socklen_t size = sizeof(sockaddr_in);
167 #ifdef IPV6
168         bool v6 = false;
169         /* Are we looking for a binding to fit an ipv6 host? */
170         if ((ip.empty()) || (ip.find(':') != std::string::npos))
171                 v6 = true;
172 #endif
173         int j = 0;
174         while (j < Conf.Enumerate("bind") || (!ip.empty()))
175         {
176                 std::string IP = ip.empty() ? Conf.ReadValue("bind","address",j) : ip;
177                 if (!ip.empty() || Conf.ReadValue("bind","type",j) == "servers")
178                 {
179                         if (!ip.empty() || ((IP != "*") && (IP != "127.0.0.1") && (!IP.empty()) && (IP != "::1")))
180                         {
181                                 /* The [2] is required because we may write a sockaddr_in6 here, and sockaddr_in6 is larger than sockaddr, where sockaddr_in4 is not. */
182                                 sockaddr* s = new sockaddr[2];
183 #ifdef IPV6
184                                 if (v6)
185                                 {
186                                         in6_addr n;
187                                         if (inet_pton(AF_INET6, IP.c_str(), &n) > 0)
188                                         {
189                                                 memcpy(&((sockaddr_in6*)s)->sin6_addr, &n, sizeof(sockaddr_in6));
190                                                 ((sockaddr_in6*)s)->sin6_port = 0;
191                                                 ((sockaddr_in6*)s)->sin6_family = AF_INET6;
192                                                 size = sizeof(sockaddr_in6);
193                                         }
194                                         else
195                                         {
196                                                 delete[] s;
197                                                 j++;
198                                                 continue;
199                                         }
200                                 }
201                                 else
202 #endif
203                                 {
204                                         in_addr n;
205                                         if (inet_aton(IP.c_str(), &n) > 0)
206                                         {
207                                                 ((sockaddr_in*)s)->sin_addr = n;
208                                                 ((sockaddr_in*)s)->sin_port = 0;
209                                                 ((sockaddr_in*)s)->sin_family = AF_INET;
210                                         }
211                                         else
212                                         {
213                                                 delete[] s;
214                                                 j++;
215                                                 continue;
216                                         }
217                                 }
218
219                                 if (Instance->SE->Bind(this->fd, s, size) < 0)
220                                 {
221                                         this->state = I_ERROR;
222                                         this->OnError(I_ERR_BIND);
223                                         this->fd = -1;
224                                         delete[] s;
225                                         return false;
226                                 }
227
228                                 delete[] s;
229                                 return true;
230                         }
231                 }
232                 j++;
233         }
234         return true;
235 }
236
237 bool InspSocket::DoConnect()
238 {
239         /* The [2] is required because we may write a sockaddr_in6 here, and sockaddr_in6 is larger than sockaddr, where sockaddr_in4 is not. */
240         sockaddr* addr = new sockaddr[2];
241         socklen_t size = sizeof(sockaddr_in);
242 #ifdef IPV6
243         bool v6 = false;
244         if ((!*this->host) || strchr(this->host, ':'))
245                 v6 = true;
246
247         if (v6)
248         {
249                 this->fd = socket(AF_INET6, SOCK_STREAM, 0);
250                 if ((this->fd > -1) && ((strstr(this->IP,"::ffff:") != (char*)&this->IP) && (strstr(this->IP,"::FFFF:") != (char*)&this->IP)))
251                 {
252                         if (!this->BindAddr(this->cbindip))
253                         {
254                                 delete[] addr;
255                                 return false;
256                         }
257                 }
258         }
259         else
260 #endif
261         {
262                 this->fd = socket(AF_INET, SOCK_STREAM, 0);
263                 if (this->fd > -1)
264                 {
265                         if (!this->BindAddr(this->cbindip))
266                         {
267                                 delete[] addr;
268                                 return false;
269                         }
270                 }
271         }
272
273         if (this->fd == -1)
274         {
275                 this->state = I_ERROR;
276                 this->OnError(I_ERR_SOCKET);
277                 delete[] addr;
278                 return false;
279         }
280
281 #ifdef IPV6
282         if (v6)
283         {
284                 in6_addr addy;
285                 if (inet_pton(AF_INET6, this->host, &addy) > 0)
286                 {
287                         ((sockaddr_in6*)addr)->sin6_family = AF_INET6;
288                         memcpy(&((sockaddr_in6*)addr)->sin6_addr, &addy, sizeof(addy));
289                         ((sockaddr_in6*)addr)->sin6_port = htons(this->port);
290                         size = sizeof(sockaddr_in6);
291                 }
292         }
293         else
294 #endif
295         {
296                 in_addr addy;
297                 if (inet_aton(this->host, &addy) > 0)
298                 {
299                         ((sockaddr_in*)addr)->sin_family = AF_INET;
300                         ((sockaddr_in*)addr)->sin_addr = addy;
301                         ((sockaddr_in*)addr)->sin_port = htons(this->port);
302                 }
303         }
304
305         Instance->SE->NonBlocking(this->fd);
306
307 #ifdef WIN32
308         /* UGH for the LOVE OF ZOMBIE JESUS SOMEONE FIX THIS!!!!!!!!!!! */
309         Instance->SE->Blocking(this->fd);
310 #endif
311
312         if (Instance->SE->Connect(this, (sockaddr*)addr, size) == -1)
313         {
314                 if (errno != EINPROGRESS)
315                 {
316                         this->OnError(I_ERR_CONNECT);
317                         this->Close();
318                         this->state = I_ERROR;
319                         return false;
320                 }
321
322                 this->Timeout = new SocketTimeout(this->GetFd(), this->Instance, this, timeout_val, this->Instance->Time());
323                 this->Instance->Timers->AddTimer(this->Timeout);
324         }
325 #ifdef WIN32
326         /* CRAQ SMOKING STUFF TO BE FIXED */
327         Instance->SE->NonBlocking(this->fd);
328 #endif
329         this->state = I_CONNECTING;
330         if (this->fd > -1)
331         {
332                 if (!this->Instance->SE->AddFd(this))
333                 {
334                         this->OnError(I_ERR_NOMOREFDS);
335                         this->Close();
336                         this->state = I_ERROR;
337                         return false;
338                 }
339                 this->SetQueues(this->fd);
340         }
341         return true;
342 }
343
344
345 void InspSocket::Close()
346 {
347         /* Save this, so we dont lose it,
348          * otherise on failure, error messages
349          * might be inaccurate.
350          */
351         int save = errno;
352         if (this->fd > -1)
353         {
354                 if (this->IsIOHooked && Instance->Config->GetIOHook(this))
355                 {
356                         try
357                         {
358                                 Instance->Config->GetIOHook(this)->OnRawSocketClose(this->fd);
359                         }
360                         catch (CoreException& modexcept)
361                         {
362                                 Instance->Log(DEFAULT,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
363                         }
364                 }
365                 Instance->SE->Shutdown(this, 2);
366                 if (Instance->SE->Close(this) != -1)
367                         this->OnClose();
368
369                 if (Instance->SocketCull.find(this) == Instance->SocketCull.end())
370                         Instance->SocketCull[this] = this;
371         }
372         errno = save;
373 }
374
375 std::string InspSocket::GetIP()
376 {
377         return this->IP;
378 }
379
380 char* InspSocket::Read()
381 {
382         if (!Instance->SE->BoundsCheckFd(this))
383                 return NULL;
384
385         int n = 0;
386
387         if (this->IsIOHooked)
388         {
389                 int result2 = 0;
390                 int MOD_RESULT = 0;
391                 try
392                 {
393                         MOD_RESULT = Instance->Config->GetIOHook(this)->OnRawSocketRead(this->fd,this->ibuf,sizeof(this->ibuf),result2);
394                 }
395                 catch (CoreException& modexcept)
396                 {
397                         Instance->Log(DEFAULT,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
398                 }
399                 if (MOD_RESULT < 0)
400                 {
401                         n = -1;
402                         errno = EAGAIN;
403                 }
404                 else
405                 {
406                         n = result2;
407                 }
408         }
409         else
410         {
411                 n = recv(this->fd,this->ibuf,sizeof(this->ibuf),0);
412         }
413
414         if ((n > 0) && (n <= (int)sizeof(this->ibuf)))
415         {
416                 ibuf[n] = 0;
417                 return ibuf;
418         }
419         else
420         {
421                 int err = errno;
422                 if (err == EAGAIN)
423                         return "";
424                 else
425                         return NULL;
426         }
427 }
428
429 void InspSocket::MarkAsClosed()
430 {
431 }
432
433 // There are two possible outcomes to this function.
434 // It will either write all of the data, or an undefined amount.
435 // If an undefined amount is written the connection has failed
436 // and should be aborted.
437 int InspSocket::Write(const std::string &data)
438 {
439         /* Try and append the data to the back of the queue, and send it on its way
440          */
441         outbuffer.push_back(data);
442         this->Instance->SE->WantWrite(this);
443         return (!this->FlushWriteBuffer());
444 }
445
446 bool InspSocket::FlushWriteBuffer()
447 {
448         errno = 0;
449         if ((this->fd > -1) && (this->state == I_CONNECTED))
450         {
451                 if (this->IsIOHooked)
452                 {
453                         while (outbuffer.size() && (errno != EAGAIN))
454                         {
455                                 try
456                                 {
457                                         /* XXX: The lack of buffering here is NOT a bug, modules implementing this interface have to
458                                          * implement their own buffering mechanisms
459                                          */
460                                         Instance->Config->GetIOHook(this)->OnRawSocketWrite(this->fd, outbuffer[0].c_str(), outbuffer[0].length());
461                                         outbuffer.pop_front();
462                                 }
463                                 catch (CoreException& modexcept)
464                                 {
465                                         Instance->Log(DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
466                                         return true;
467                                 }
468                         }
469                 }
470                 else
471                 {
472                         /* If we have multiple lines, try to send them all,
473                          * not just the first one -- Brain
474                          */
475                         while (outbuffer.size() && (errno != EAGAIN))
476                         {
477                                 /* Send a line */
478                                 int result = Instance->SE->Send(this, outbuffer[0].c_str(), outbuffer[0].length(), 0);
479
480                                 if (result > 0)
481                                 {
482                                         if ((unsigned int)result >= outbuffer[0].length())
483                                         {
484                                                 /* The whole block was written (usually a line)
485                                                  * Pop the block off the front of the queue,
486                                                  * dont set errno, because we are clear of errors
487                                                  * and want to try and write the next block too.
488                                                  */
489                                                 outbuffer.pop_front();
490                                         }
491                                         else
492                                         {
493                                                 std::string temp = outbuffer[0].substr(result);
494                                                 outbuffer[0] = temp;
495                                                 /* We didnt get the whole line out. arses.
496                                                  * Try again next time, i guess. Set errno,
497                                                  * because we shouldnt be writing any more now,
498                                                  * until the socketengine says its safe to do so.
499                                                  */
500                                                 errno = EAGAIN;
501                                         }
502                                 }
503                                 else if ((result == -1) && (errno != EAGAIN))
504                                 {
505                                         this->OnError(I_ERR_WRITE);
506                                         this->state = I_ERROR;
507                                         this->Instance->SE->DelFd(this);
508                                         this->Close();
509                                         return true;
510                                 }
511                         }
512                 }
513         }
514
515         if ((errno == EAGAIN) && (fd > -1))
516         {
517                 this->Instance->SE->WantWrite(this);
518         }
519
520         return (fd < 0);
521 }
522
523 void SocketTimeout::Tick(time_t now)
524 {
525         if (ServerInstance->SE->GetRef(this->sfd) != this->sock)
526                 return;
527
528         if (this->sock->state == I_CONNECTING)
529         {
530                 // for non-listening sockets, the timeout can occur
531                 // which causes termination of the connection after
532                 // the given number of seconds without a successful
533                 // connection.
534                 this->sock->OnTimeout();
535                 this->sock->OnError(I_ERR_TIMEOUT);
536                 this->sock->timeout = true;
537
538                 /* NOTE: We must set this AFTER DelFd, as we added
539                  * this socket whilst writeable. This means that we
540                  * must DELETE the socket whilst writeable too!
541                  */
542                 this->sock->state = I_ERROR;
543
544                 if (ServerInstance->SocketCull.find(this->sock) == ServerInstance->SocketCull.end())
545                         ServerInstance->SocketCull[this->sock] = this->sock;
546         }
547
548         this->sock->Timeout = NULL;
549 }
550
551 bool InspSocket::Poll()
552 {
553         int incoming = -1;
554
555 #ifndef WINDOWS
556         if (!Instance->SE->BoundsCheckFd(this))
557                 return false;
558 #endif
559
560         if (Instance->SE->GetRef(this->fd) != this)
561                 return false;
562
563         switch (this->state)
564         {
565                 case I_CONNECTING:
566                         /* Our socket was in write-state, so delete it and re-add it
567                          * in read-state.
568                          */
569 #ifndef WINDOWS
570                         if (this->fd > -1)
571                         {
572                                 this->Instance->SE->DelFd(this);
573                                 if (!this->Instance->SE->AddFd(this))
574                                         return false;
575                         }
576 #endif
577                         this->SetState(I_CONNECTED);
578
579                         if (Instance->Config->GetIOHook(this))
580                         {
581                                 Instance->Log(DEBUG,"Hook for raw connect");
582                                 try
583                                 {
584                                         Instance->Config->GetIOHook(this)->OnRawSocketConnect(this->fd);
585                                 }
586                                 catch (CoreException& modexcept)
587                                 {
588                                         Instance->Log(DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
589                                 }
590                         }
591                         return this->OnConnected();
592                 break;
593                 case I_LISTENING:
594                 {
595                         /* The [2] is required because we may write a sockaddr_in6 here, and sockaddr_in6 is larger than sockaddr, where sockaddr_in4 is not. */
596                         sockaddr* client = new sockaddr[2];
597                         length = sizeof (sockaddr_in);
598                         std::string recvip;
599 #ifdef IPV6
600                         if ((!*this->host) || strchr(this->host, ':'))
601                                 length = sizeof(sockaddr_in6);
602 #endif
603                         incoming = Instance->SE->Accept(this, client, &length);
604 #ifdef IPV6
605                         if ((!*this->host) || strchr(this->host, ':'))
606                         {
607                                 char buf[1024];
608                                 recvip = inet_ntop(AF_INET6, &((sockaddr_in6*)client)->sin6_addr, buf, sizeof(buf));
609                         }
610                         else
611 #endif
612                         recvip = inet_ntoa(((sockaddr_in*)client)->sin_addr);
613                         this->OnIncomingConnection(incoming, (char*)recvip.c_str());
614
615                         if (this->IsIOHooked)
616                         {
617                                 try
618                                 {
619                                         Instance->Config->GetIOHook(this)->OnRawSocketAccept(incoming, recvip.c_str(), this->port);
620                                 }
621                                 catch (CoreException& modexcept)
622                                 {
623                                         Instance->Log(DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
624                                 }
625                         }
626
627                         this->SetQueues(incoming);
628
629                         delete[] client;
630                         return true;
631                 }
632                 break;
633                 case I_CONNECTED:
634                         /* Process the read event */
635                         return this->OnDataReady();
636                 break;
637                 default:
638                 break;
639         }
640         return true;
641 }
642
643 void InspSocket::SetState(InspSocketState s)
644 {
645         this->state = s;
646 }
647
648 InspSocketState InspSocket::GetState()
649 {
650         return this->state;
651 }
652
653 int InspSocket::GetFd()
654 {
655         return this->fd;
656 }
657
658 bool InspSocket::OnConnected() { return true; }
659 void InspSocket::OnError(InspSocketError e) { return; }
660 int InspSocket::OnDisconnect() { return 0; }
661 int InspSocket::OnIncomingConnection(int newfd, char* ip) { return 0; }
662 bool InspSocket::OnDataReady() { return true; }
663 bool InspSocket::OnWriteReady() { return true; }
664 void InspSocket::OnTimeout() { return; }
665 void InspSocket::OnClose() { return; }
666
667 InspSocket::~InspSocket()
668 {
669         this->Close();
670         if (Timeout)
671         {
672                 Instance->Timers->DelTimer(Timeout);
673                 Timeout = NULL;
674         }
675 }
676
677 void InspSocket::HandleEvent(EventType et, int errornum)
678 {
679         switch (et)
680         {
681                 case EVENT_ERROR:
682                         switch (errornum)
683                         {
684                                 case ETIMEDOUT:
685                                         this->OnError(I_ERR_TIMEOUT);
686                                 break;
687                                 case ECONNREFUSED:
688                                 case 0:
689                                         this->OnError(this->state == I_CONNECTING ? I_ERR_CONNECT : I_ERR_WRITE);
690                                 break;
691                                 case EADDRINUSE:
692                                         this->OnError(I_ERR_BIND);
693                                 break;
694                                 case EPIPE:
695                                 case EIO:
696                                         this->OnError(I_ERR_WRITE);
697                                 break;
698                         }
699                         if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
700                                 this->Instance->SocketCull[this] = this;
701                         return;
702                 break;
703                 case EVENT_READ:
704                         if (!this->Poll())
705                         {
706                                 if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
707                                         this->Instance->SocketCull[this] = this;
708                                 return;
709                         }
710                 break;
711                 case EVENT_WRITE:
712                         if (this->WaitingForWriteEvent)
713                         {
714                                 this->WaitingForWriteEvent = false;
715                                 if (!this->OnWriteReady())
716                                 {
717                                         if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
718                                                 this->Instance->SocketCull[this] = this;
719                                         return;
720                                 }
721                         }
722                         if (this->state == I_CONNECTING)
723                         {
724                                 /* This might look wrong as if we should be actually calling
725                                  * with EVENT_WRITE, but trust me it is correct. There are some
726                                  * writeability-state things in the read code, because of how
727                                  * InspSocket used to work regarding write buffering in previous
728                                  * versions of InspIRCd. - Brain
729                                  */
730                                 this->HandleEvent(EVENT_READ);
731                                 return;
732                         }
733                         else
734                         {
735                                 if (this->FlushWriteBuffer())
736                                 {
737                                         if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
738                                                 this->Instance->SocketCull[this] = this;
739                                         return;
740                                 }
741                         }
742                 break;
743         }
744 }
745