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