1c4e952a232cd1540075f180f1d207d2e95ebb54
[silc.git] / lib / silcutil / silcsocketstream.c
1 /*
2
3   silcsocketstream.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 - 2006 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silc.h"
21
22 /************************** Types and definitions ***************************/
23
24 #define SILC_IS_SOCKET_STREAM(s) (s->ops == &silc_socket_stream_ops)
25 #define SILC_IS_SOCKET_STREAM_UDP(s) (s->ops == &silc_socket_udp_stream_ops)
26
27 const SilcStreamOps silc_socket_stream_ops;
28 const SilcStreamOps silc_socket_udp_stream_ops;
29
30 /* Platform specific functions */
31 int silc_socket_stream_read(SilcStream stream, unsigned char *buf,
32                             SilcUInt32 buf_len);
33 int silc_socket_stream_write(SilcStream stream, const unsigned char *data,
34                              SilcUInt32 data_len);
35 SilcBool silc_socket_stream_close(SilcStream stream);
36 void silc_socket_stream_destroy(SilcStream stream);
37 int silc_socket_udp_stream_read(SilcStream stream, unsigned char *buf,
38                                 SilcUInt32 buf_len);
39 int silc_socket_udp_stream_write(SilcStream stream, const unsigned char *data,
40                                  SilcUInt32 data_len);
41
42 /* Internal async host lookup context. */
43 typedef struct {
44   SilcSocketStream stream;
45   SilcSocketStreamStatus status;
46   SilcSocketStreamCallback callback;
47   SilcAsyncOperation op;
48   void *context;
49   unsigned int require_fqdn : 1;
50   unsigned int aborted      : 1;
51 } *SilcSocketHostLookup;
52
53
54 /************************ Static utility functions **************************/
55
56 /* The IO process callback that calls the notifier callback to upper
57    layer. */
58
59 SILC_TASK_CALLBACK(silc_socket_stream_io)
60 {
61   SilcSocketStream stream = context;
62
63   if (silc_unlikely(!stream->notifier))
64     return;
65
66   switch (type) {
67   case SILC_TASK_READ:
68     stream->notifier(stream, SILC_STREAM_CAN_READ, stream->notifier_context);
69     break;
70
71   case SILC_TASK_WRITE:
72     stream->notifier(stream, SILC_STREAM_CAN_WRITE, stream->notifier_context);
73     break;
74
75   default:
76     break;
77   }
78 }
79
80 /* Finishing timeout callback that will actually call the user specified
81    host lookup callback.  This is executed back in the calling thread and
82    not in the lookup thread. */
83
84 SILC_TASK_CALLBACK(silc_socket_host_lookup_finish)
85 {
86   SilcSocketHostLookup lookup = context;
87   SilcSocketStream stream = lookup->stream;
88
89   if (lookup->aborted) {
90     SILC_LOG_DEBUG(("Socket stream creation was aborted"));
91     silc_net_close_connection(stream->sock);
92     silc_free(stream->ip);
93     silc_free(stream->hostname);
94     silc_free(stream);
95     silc_free(lookup);
96     return;
97   }
98
99   if (lookup->status != SILC_SOCKET_OK) {
100     SILC_LOG_DEBUG(("Socket stream failed"));
101     silc_net_close_connection(stream->sock);
102     silc_free(stream->ip);
103     silc_free(stream->hostname);
104     silc_free(stream);
105     stream = lookup->stream = NULL;
106   }
107
108   /* Return the created socket stream to the caller */
109   if (lookup->callback)
110     lookup->callback(lookup->status, stream, lookup->context);
111
112   if (lookup->op)
113     silc_async_free(lookup->op);
114   silc_free(lookup);
115 }
116
117 /* The thread function that performs the actual lookup. */
118
119 static void *silc_socket_host_lookup_start(void *context)
120 {
121   SilcSocketHostLookup lookup = (SilcSocketHostLookup)context;
122   SilcSocketStream stream = lookup->stream;
123   SilcSchedule schedule = stream->schedule;
124
125   stream->port = silc_net_get_remote_port(stream->sock);
126
127   silc_net_check_host_by_sock(stream->sock, &stream->hostname, &stream->ip);
128   if (!stream->ip) {
129     lookup->status = SILC_SOCKET_UNKNOWN_IP;
130     goto out;
131   }
132
133   if (!stream->hostname && lookup->require_fqdn) {
134     lookup->status = SILC_SOCKET_UNKNOWN_HOST;
135     goto out;
136   }
137
138   if (!stream->hostname) {
139     stream->hostname = strdup(stream->ip);
140     if (!stream->hostname) {
141       lookup->status = SILC_SOCKET_NO_MEMORY;
142       goto out;
143     }
144   }
145
146   lookup->status = SILC_SOCKET_OK;
147
148  out:
149   silc_schedule_task_add_timeout(schedule, silc_socket_host_lookup_finish,
150                                  lookup, 0, 0);
151   silc_schedule_wakeup(schedule);
152   return NULL;
153 }
154
155 /* Abort callback for stream creation. */
156
157 static void silc_socket_host_lookup_abort(SilcAsyncOperation op,
158                                           void *context)
159 {
160   SilcSocketHostLookup lookup = context;
161
162   /* The host lookup is done in thread.  We'll let it finish in its own
163      good time and handle the abortion after it finishes. */
164   lookup->aborted = TRUE;
165 }
166
167
168 /******************************* Public API *********************************/
169
170 /* Creates TCP socket stream */
171
172 SilcAsyncOperation
173 silc_socket_tcp_stream_create(int sock, SilcBool lookup,
174                               SilcBool require_fqdn,
175                               SilcSchedule schedule,
176                               SilcSocketStreamCallback callback,
177                               void *context)
178 {
179   SilcSocketStream stream;
180   SilcSocketHostLookup l;
181
182   stream = silc_calloc(1, sizeof(*stream));
183   if (!stream) {
184     if (callback)
185       callback(SILC_SOCKET_NO_MEMORY, NULL, context);
186     return NULL;
187   }
188
189   SILC_LOG_DEBUG(("Creating TCP socket stream %p", stream));
190
191   stream->ops = &silc_socket_stream_ops;
192   stream->sock = sock;
193   stream->schedule = schedule;
194
195   l = silc_calloc(1, sizeof(*l));
196   if (!l) {
197     silc_free(stream);
198     if (callback)
199       callback(SILC_SOCKET_NO_MEMORY, NULL, context);
200     return NULL;
201   }
202
203   l->stream = stream;
204   l->callback = callback;
205   l->context = context;
206   l->require_fqdn = require_fqdn;
207
208   if (lookup) {
209     /* Start asynchronous IP, hostname and port lookup process */
210     l->op = silc_async_alloc(silc_socket_host_lookup_abort, NULL, l);
211     if (!l->op) {
212       silc_free(stream);
213       silc_free(l);
214       if (callback)
215         callback(SILC_SOCKET_ERROR, NULL, context);
216       return NULL;
217     }
218
219     /* Lookup in thread */
220     SILC_LOG_DEBUG(("Starting async host lookup"));
221     silc_thread_create(silc_socket_host_lookup_start, l, FALSE);
222     return l->op;
223   } else {
224     /* No lookup */
225     l->status = SILC_SOCKET_OK;
226     silc_socket_host_lookup_finish(schedule,
227                                    silc_schedule_get_context(schedule),
228                                    0, 0, l);
229     return NULL;
230   }
231 }
232
233 /* Creates UDP socket stream */
234
235 SilcStream silc_socket_udp_stream_create(int sock, SilcBool ipv6,
236                                          SilcBool connected,
237                                          SilcSchedule schedule)
238 {
239   SilcSocketStream stream;
240
241   stream = silc_calloc(1, sizeof(*stream));
242   if (!stream)
243     return NULL;
244
245   SILC_LOG_DEBUG(("Creating UDP socket stream %p", stream));
246
247   stream->ops = &silc_socket_udp_stream_ops;
248   stream->sock = sock;
249   stream->schedule = schedule;
250   stream->ipv6 = ipv6;
251   stream->connected = connected;
252
253   return (SilcStream)stream;
254 }
255
256 /* Returns TRUE if the stream is UDP stream */
257
258 SilcBool silc_socket_stream_is_udp(SilcStream stream, SilcBool *connected)
259 {
260   SilcSocketStream socket_stream = stream;
261
262   if (!SILC_IS_SOCKET_STREAM_UDP(socket_stream))
263     return FALSE;
264
265   if (connected)
266     *connected = socket_stream->connected;
267
268   return TRUE;
269 }
270
271 /* Returns socket stream information */
272
273 SilcBool silc_socket_stream_get_info(SilcStream stream,
274                                      int *sock, const char **hostname,
275                                      const char **ip, SilcUInt16 *port)
276 {
277   SilcSocketStream socket_stream = stream;
278
279   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
280       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
281     return FALSE;
282
283   if (sock)
284     *sock = socket_stream->sock;
285   if (hostname) {
286     if (!socket_stream->hostname)
287       return FALSE;
288     *hostname = socket_stream->hostname;
289   }
290   if (ip) {
291     if (!socket_stream->ip)
292       return FALSE;
293     *ip = socket_stream->ip;
294   }
295   if (port) {
296     if (!socket_stream->port)
297       return FALSE;
298     *port = socket_stream->port;
299   }
300
301   return TRUE;
302 }
303
304 /* Set socket information */
305
306 SilcBool silc_socket_stream_set_info(SilcStream stream,
307                                      const char *hostname,
308                                      const char *ip, SilcUInt16 port)
309 {
310   SilcSocketStream socket_stream = stream;
311
312   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
313       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
314     return FALSE;
315
316   if (hostname) {
317     silc_free(socket_stream->hostname);
318     socket_stream->hostname = strdup(hostname);
319     if (!socket_stream->hostname)
320       return FALSE;
321   }
322   if (ip) {
323     silc_free(socket_stream->ip);
324     socket_stream->ip = strdup(ip);
325     if (!socket_stream->ip)
326       return FALSE;
327     if (!socket_stream->hostname) {
328       socket_stream->hostname = strdup(ip);
329       if (!socket_stream->hostname)
330         return FALSE;
331     }
332   }
333   if (port)
334     socket_stream->port = port;
335
336   return TRUE;
337 }
338
339 /* Return socket errno */
340
341 int silc_socket_stream_get_error(SilcStream stream)
342 {
343   SilcSocketStream socket_stream = stream;
344
345   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
346       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
347     return 0;
348
349   return socket_stream->sock_error;
350 }
351
352 /* Set QoS for socket stream */
353
354 SilcBool silc_socket_stream_set_qos(SilcStream stream,
355                                     SilcUInt32 read_rate,
356                                     SilcUInt32 read_limit_bytes,
357                                     SilcUInt32 limit_sec,
358                                     SilcUInt32 limit_usec)
359 {
360   SilcSocketStream socket_stream = stream;
361
362   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
363       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
364     return FALSE;
365
366   SILC_LOG_DEBUG(("Setting QoS for socket stream"));
367
368   if (socket_stream->qos && !read_rate && !read_limit_bytes &&
369       !limit_sec && !limit_usec) {
370     silc_schedule_task_del_by_context(socket_stream->schedule,
371                                       socket_stream->qos);
372     silc_free(socket_stream->qos);
373     socket_stream->qos = NULL;
374     return TRUE;
375   }
376
377   if (!socket_stream->qos) {
378     socket_stream->qos = silc_calloc(1, sizeof(*socket_stream->qos));
379     if (!socket_stream->qos)
380       return FALSE;
381   }
382
383   socket_stream->qos->read_rate = read_rate;
384   socket_stream->qos->read_limit_bytes = read_limit_bytes;
385   socket_stream->qos->limit_sec = limit_sec;
386   socket_stream->qos->limit_usec = limit_usec;
387   memset(&socket_stream->qos->next_limit, 0,
388          sizeof(socket_stream->qos->next_limit));
389   socket_stream->qos->cur_rate = 0;
390   socket_stream->qos->sock = socket_stream;
391
392   socket_stream->qos->buffer = silc_malloc(read_limit_bytes);
393   if (!socket_stream->qos->buffer)
394     return FALSE;
395
396   return TRUE;
397 }
398
399 /* Closes socket */
400
401 SilcBool silc_socket_stream_close(SilcStream stream)
402 {
403   SilcSocketStream socket_stream = stream;
404
405   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
406       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
407     return FALSE;
408
409   silc_schedule_unset_listen_fd(socket_stream->schedule, socket_stream->sock);
410   silc_net_close_connection(socket_stream->sock);
411
412   return TRUE;
413 }
414
415 /* Destroys the stream */
416
417 void silc_socket_stream_destroy(SilcStream stream)
418 {
419   SilcSocketStream socket_stream = stream;
420
421   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
422       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
423     return;
424
425   silc_socket_stream_close(socket_stream);
426   silc_free(socket_stream->ip);
427   silc_free(socket_stream->hostname);
428   silc_schedule_task_del_by_fd(socket_stream->schedule, socket_stream->sock);
429
430   if (socket_stream->qos) {
431     silc_schedule_task_del_by_context(socket_stream->schedule,
432                                       socket_stream->qos);
433     if (socket_stream->qos->buffer) {
434       memset(socket_stream->qos->buffer, 0,
435              socket_stream->qos->read_limit_bytes);
436       silc_free(socket_stream->qos->buffer);
437     }
438     silc_free(socket_stream->qos);
439   }
440
441   silc_schedule_wakeup(socket_stream->schedule);
442
443   silc_free(socket_stream);
444 }
445
446 /* Sets stream notification callback for the stream */
447
448 void silc_socket_stream_notifier(SilcStream stream,
449                                  SilcSchedule schedule,
450                                  SilcStreamNotifier callback,
451                                  void *context)
452 {
453   SilcSocketStream socket_stream = stream;
454
455   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
456       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
457     return;
458
459   SILC_LOG_DEBUG(("Setting stream notifier callback"));
460
461   socket_stream->notifier = callback;
462   socket_stream->notifier_context = context;
463   socket_stream->schedule = schedule;
464
465   if (socket_stream->notifier) {
466     /* Add the socket to scheduler.  Safe to call if already added. */
467     silc_schedule_task_add_fd(socket_stream->schedule, socket_stream->sock,
468                               silc_socket_stream_io, socket_stream);
469
470     /* Initially set socket for reading */
471     silc_schedule_set_listen_fd(socket_stream->schedule, socket_stream->sock,
472                                 SILC_TASK_READ, FALSE);
473     silc_schedule_wakeup(socket_stream->schedule);
474   } else {
475     /* Unschedule the socket */
476     silc_schedule_unset_listen_fd(socket_stream->schedule,
477                                   socket_stream->sock);
478     silc_schedule_task_del_by_fd(socket_stream->schedule,
479                                  socket_stream->sock);
480     silc_schedule_wakeup(socket_stream->schedule);
481   }
482 }
483
484 /* Return associated scheduler */
485
486 SilcSchedule silc_socket_stream_get_schedule(SilcStream stream)
487 {
488   SilcSocketStream socket_stream = stream;
489
490   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
491       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
492     return NULL;
493
494   return socket_stream->schedule;
495 }
496
497 /* SILC Socket Stream ops.  Functions are implemented under the
498    platform specific subdirectories. */
499 const SilcStreamOps silc_socket_stream_ops =
500 {
501   silc_socket_stream_read,
502   silc_socket_stream_write,
503   silc_socket_stream_close,
504   silc_socket_stream_destroy,
505   silc_socket_stream_notifier,
506   silc_socket_stream_get_schedule,
507 };
508 const SilcStreamOps silc_socket_udp_stream_ops =
509 {
510   silc_socket_udp_stream_read,
511   silc_socket_udp_stream_write,
512   silc_socket_stream_close,
513   silc_socket_stream_destroy,
514   silc_socket_stream_notifier,
515   silc_socket_stream_get_schedule,
516 };