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