Merge branch 'topic/mm-fixes' of git://208.110.73.182/silc into silc.1.1.branch
[silc.git] / lib / silcutil / symbian / silcsymbiannet.cpp
1 /*
2
3   silcsymbiannet.cpp
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2006 - 2007 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 #include "silcsymbiansocketstream.h"
22
23 /************************ Static utility functions **************************/
24
25 static SilcBool silc_net_set_sockaddr(TInetAddr *addr, const char *ip_addr,
26                                       int port)
27 {
28   /* Check for IPv4 and IPv6 addresses */
29   if (ip_addr) {
30     if (!silc_net_is_ip(ip_addr)) {
31       SILC_LOG_ERROR(("%s is not IP address", ip_addr));
32       return FALSE;
33     }
34
35     if (silc_net_is_ip4(ip_addr)) {
36       /* IPv4 address */
37       unsigned char buf[4];
38       TUint32 a;
39
40       if (!silc_net_addr2bin(ip_addr, buf, sizeof(buf)))
41         return FALSE;
42
43       SILC_GET32_MSB(a, buf);
44       addr->SetAddress(a);
45       addr->SetPort(port);
46     } else {
47 #ifdef HAVE_IPV6
48       SILC_LOG_ERROR(("IPv6 not supported"));
49       return FALSE;
50 #else
51       SILC_LOG_ERROR(("Operating System does not support IPv6"));
52       return FALSE;
53 #endif
54     }
55   } else {
56     addr->SetAddress(0);
57     addr->SetPort(port);
58   }
59
60   return TRUE;
61 }
62
63 /****************************** TCP Listener ********************************/
64
65 class SilcSymbianTCPListener;
66
67 extern "C" {
68
69 /* Deliver new stream to upper layer */
70
71 static void silc_net_accept_stream(SilcSocketStreamStatus status,
72                                    SilcStream stream, void *context)
73 {
74   SilcNetListener listener = (SilcNetListener)context;
75
76   /* In case of error, the socket has been destroyed already via
77      silc_stream_destroy. */
78   if (status != SILC_SOCKET_OK)
79     return;
80
81   listener->callback(SILC_NET_OK, stream, listener->context);
82 }
83
84 } /* extern "C" */
85
86 /* TCP Listener class */
87
88 class SilcSymbianTCPListener : public CActive {
89 public:
90   /* Constructor */
91   SilcSymbianTCPListener() : CActive(CActive::EPriorityStandard)
92   {
93     CActiveScheduler::Add(this);
94   }
95
96   /* Destructor */
97   ~SilcSymbianTCPListener()
98   {
99     Cancel();
100   }
101
102   /* Listen for connection */
103   void Listen()
104   {
105     SILC_LOG_DEBUG(("Listen()"));
106
107     new_conn = new RSocket;
108     if (!new_conn)
109       return;
110     if (new_conn->Open(ss) != KErrNone) {
111       Listen();
112       return;
113     }
114
115     /* Start listenning */
116     sock.Accept(*new_conn, iStatus);
117     SetActive();
118   }
119
120   /* Listener callback */
121   virtual void RunL()
122   {
123     SILC_LOG_DEBUG(("RunL(), iStatus=%d", iStatus));
124
125     if (iStatus != KErrNone) {
126       if (new_conn)
127         delete new_conn;
128       new_conn = NULL;
129       Listen();
130       return;
131     }
132
133     SILC_LOG_DEBUG(("Accept new connection"));
134
135     /* Set socket options */
136     new_conn->SetOpt(KSoReuseAddr, KSolInetIp, 1);
137
138     /* Create socket stream */
139     silc_socket_tcp_stream_create(
140                         (SilcSocket)silc_create_symbian_socket(new_conn, NULL),
141                         listener->lookup, listener->require_fqdn,
142                         listener->schedule, silc_net_accept_stream,
143                         (void *)listener);
144     new_conn = NULL;
145
146     /* Continue listenning */
147     Listen();
148   }
149
150   /* Cancel */
151   virtual void DoCancel()
152   {
153     sock.CancelAll();
154     ss.Close();
155     if (new_conn)
156       delete new_conn;
157   }
158
159   RSocket *new_conn;
160   RSocket sock;
161   RSocketServ ss;
162   SilcNetListener listener;
163 };
164
165 extern "C" {
166
167 /* Create TCP listener */
168
169 SilcNetListener
170 silc_net_tcp_create_listener(const char **local_ip_addr,
171                              SilcUInt32 local_ip_count, int port,
172                              SilcBool lookup, SilcBool require_fqdn,
173                              SilcSchedule schedule,
174                              SilcNetCallback callback, void *context)
175 {
176   SilcNetListener listener = NULL;
177   SilcSymbianTCPListener *l = NULL;
178   TInetAddr server;
179   TInt ret;
180   int i;
181
182   SILC_LOG_DEBUG(("Creating TCP listener"));
183
184   if (port < 0 || !schedule || !callback)
185     goto err;
186
187   listener = (SilcNetListener)silc_calloc(1, sizeof(*listener));
188   if (!listener) {
189     callback(SILC_NET_NO_MEMORY, NULL, context);
190     return NULL;
191   }
192   listener->schedule = schedule;
193   listener->callback = callback;
194   listener->context = context;
195   listener->require_fqdn = require_fqdn;
196   listener->lookup = lookup;
197
198   if (local_ip_count > 0) {
199     listener->socks = (SilcSocket *)silc_calloc(local_ip_count,
200                                                 sizeof(*listener->socks));
201     if (!listener->socks) {
202       callback(SILC_NET_NO_MEMORY, NULL, context);
203       return NULL;
204     }
205   } else {
206     listener->socks = (SilcSocket *)silc_calloc(1, sizeof(*listener->socks));
207     if (!listener->socks) {
208       callback(SILC_NET_NO_MEMORY, NULL, context);
209       return NULL;
210     }
211
212     local_ip_count = 1;
213   }
214
215   /* Bind to local addresses */
216   for (i = 0; i < local_ip_count; i++) {
217     SILC_LOG_DEBUG(("Binding to local address %s",
218                     local_ip_addr ? local_ip_addr[i] : "0.0.0.0"));
219
220     l = new SilcSymbianTCPListener;
221     if (!l)
222       goto err;
223
224     /* Connect to socket server */
225     ret = l->ss.Connect();
226     if (ret != KErrNone)
227       goto err;
228
229 #ifdef SILC_THREADS
230     /* Make our socket shareable between threads */
231     l->ss.ShareAuto();
232 #endif /* SILC_THREADS */
233
234     /* Set listener address */
235     if (!silc_net_set_sockaddr(&server, local_ip_addr[i], port))
236       goto err;
237
238     /* Create the socket */
239     ret = l->sock.Open(l->ss, KAfInet, KSockStream, KProtocolInetTcp);
240     if (ret != KErrNone) {
241       SILC_LOG_ERROR(("Cannot create socket, error %d", ret));
242       goto err;
243     }
244
245     /* Set the socket options */
246     ret = l->sock.SetOpt(KSoReuseAddr, KSolInetIp, 1);
247     if (ret != KErrNone) {
248       SILC_LOG_ERROR(("Cannot set socket options, error %d", ret));
249       goto err;
250     }
251
252     /* Bind the listener socket */
253     ret = l->sock.Bind(server);
254     if (ret != KErrNone) {
255       SILC_LOG_DEBUG(("Cannot bind socket, error %d", ret));
256       goto err;
257     }
258
259     /* Specify that we are listenning */
260     ret = l->sock.Listen(5);
261     if (ret != KErrNone) {
262       SILC_LOG_ERROR(("Cannot set socket listenning, error %d", ret));
263       goto err;
264     }
265     l->Listen();
266
267     l->listener = listener;
268     listener->socks[i] = (SilcSocket)l;
269     listener->socks_count++;
270   }
271
272   SILC_LOG_DEBUG(("TCP listener created"));
273
274   return listener;
275
276  err:
277   if (l)
278     delete l;
279   if (callback)
280     callback(SILC_NET_ERROR, NULL, context);
281   if (listener)
282     silc_net_close_listener(listener);
283   return NULL;
284 }
285
286 /* Close network listener */
287
288 void silc_net_close_listener(SilcNetListener listener)
289 {
290   int i;
291
292   SILC_LOG_DEBUG(("Closing network listener"));
293
294   if (!listener)
295     return;
296
297   for (i = 0; i < listener->socks_count; i++) {
298     SilcSymbianTCPListener *l = (SilcSymbianTCPListener *)listener->socks[i];
299     l->sock.CancelAll();
300     l->sock.Close();
301     l->ss.Close();
302     if (l->new_conn)
303       delete l->new_conn;
304     delete l;
305   }
306
307   silc_free(listener->socks);
308   silc_free(listener);
309 }
310
311
312 /**************************** TCP/IP connecting *****************************/
313
314 static void silc_net_connect_stream(SilcSocketStreamStatus status,
315             SilcStream stream, void *context);
316
317 } /* extern "C" */
318
319 /* TCP connecting class */
320
321 class SilcSymbianTCPConnect : public CActive {
322 public:
323   /* Constructor */
324   SilcSymbianTCPConnect() : CActive(CActive::EPriorityStandard)
325   {
326     CActiveScheduler::Add(this);
327   }
328
329   /* Destructor */
330   ~SilcSymbianTCPConnect()
331   {
332     silc_free(remote);
333     if (op)
334       silc_async_free(op);
335     Cancel();
336   }
337
338   /* Connect to remote host */
339   void Connect(TSockAddr &addr)
340   {
341     SILC_LOG_DEBUG(("Connect()"));
342     sock->Connect(addr, iStatus);
343     SetActive();
344   }
345
346   /* Connection callback */
347   virtual void RunL()
348   {
349     SILC_LOG_DEBUG(("RunL(), iStatus=%d", iStatus));
350
351     if (iStatus != KErrNone) {
352       if (callback)
353         callback(SILC_NET_ERROR, NULL, context);
354       sock->CancelConnect();
355       delete sock;
356       ss->Close();
357       delete ss;
358       sock = NULL;
359       ss = NULL;
360       delete this;
361       return;
362     }
363
364     SILC_LOG_DEBUG(("Connected to host %s on %d", remote_ip, port));
365
366     /* Create stream */
367     if (callback) {
368       silc_socket_tcp_stream_create(
369                              (SilcSocket)silc_create_symbian_socket(sock, ss),
370                              TRUE, FALSE, schedule, silc_net_connect_stream,
371                              (void *)this);
372       sock = NULL;
373       ss = NULL;
374     } else {
375       sock->Close();
376       delete sock;
377       ss->Close();
378       delete ss;
379       sock = NULL;
380       ss = NULL;
381       delete this;
382     }
383   }
384
385   /* Cancel */
386   virtual void DoCancel()
387   {
388     if (ss) {
389       ss->Close();
390       delete ss;
391     }
392     if (sock) {
393       sock->CancelConnect();
394       delete sock;
395     }
396   }
397
398   RSocket *sock;
399   RSocketServ *ss;
400   char *remote;
401   char remote_ip[64];
402   int port;
403   SilcAsyncOperation op;
404   SilcSchedule schedule;
405   SilcNetCallback callback;
406   void *context;
407 };
408
409 extern "C" {
410
411 /* TCP stream creation callback */
412
413 static void silc_net_connect_stream(SilcSocketStreamStatus status,
414                                     SilcStream stream, void *context)
415 {
416   SilcSymbianTCPConnect *conn = (SilcSymbianTCPConnect *)context;
417   SilcNetStatus net_status = SILC_NET_OK;
418
419   SILC_LOG_DEBUG(("Socket stream creation status %d", status));
420
421   if (status != SILC_SOCKET_OK) {
422     /* In case of error, the socket has been destroyed already via
423        silc_stream_destroy. */
424     if (status == SILC_SOCKET_UNKNOWN_IP)
425       net_status = SILC_NET_UNKNOWN_IP;
426     else if (status == SILC_SOCKET_UNKNOWN_HOST)
427       net_status = SILC_NET_UNKNOWN_HOST;
428     else
429       net_status = SILC_NET_ERROR;
430   }
431
432   /* Call connection callback */
433   if (conn->callback)
434     conn->callback(net_status, stream, conn->context);
435   else if (stream)
436     silc_stream_destroy(stream);
437
438   delete conn;
439 }
440
441 /* Connecting abort callback */
442
443 static void silc_net_connect_abort(SilcAsyncOperation op, void *context)
444 {
445   SilcSymbianTCPConnect *conn = (SilcSymbianTCPConnect *)context;
446
447   /* Abort */
448   conn->callback = NULL;
449   conn->op = NULL;
450   if (conn->sock)
451     conn->sock->CancelConnect();
452 }
453
454 /* Create TCP/IP connection */
455
456 SilcAsyncOperation silc_net_tcp_connect(const char *local_ip_addr,
457                                         const char *remote_ip_addr,
458                                         int remote_port,
459                                         SilcSchedule schedule,
460                                         SilcNetCallback callback,
461                                         void *context)
462 {
463   SilcSymbianTCPConnect *conn;
464   TInetAddr local, remote;
465   SilcNetStatus status;
466   TInt ret;
467
468   if (!remote_ip_addr || remote_port < 1 || !schedule || !callback)
469     return NULL;
470
471   SILC_LOG_DEBUG(("Creating connection to host %s port %d",
472                   remote_ip_addr, remote_port));
473
474   conn = new SilcSymbianTCPConnect;
475   if (!conn) {
476     callback(SILC_NET_NO_MEMORY, NULL, context);
477     return NULL;
478   }
479   conn->schedule = schedule;
480   conn->callback = callback;
481   conn->context = context;
482   conn->port = remote_port;
483   conn->remote = strdup(remote_ip_addr);
484   if (!conn->remote) {
485     status = SILC_NET_NO_MEMORY;
486     goto err;
487   }
488
489   /* Allocate socket */
490   conn->sock = new RSocket;
491   if (!conn->sock) {
492     status = SILC_NET_NO_MEMORY;
493     goto err;
494   }
495
496   /* Allocate socket server */
497   conn->ss = new RSocketServ;
498   if (!conn->ss) {
499     status = SILC_NET_NO_MEMORY;
500     goto err;
501   }
502
503   /* Connect to socket server */
504   ret = conn->ss->Connect();
505   if (ret != KErrNone) {
506     SILC_LOG_ERROR(("Error connecting to socket server, error %d", ret));
507     status = SILC_NET_ERROR;
508     goto err;
509   }
510
511 #ifdef SILC_THREADS
512   /* Make our socket shareable between threads */
513   conn->ss->ShareAuto();
514 #endif /* SILC_THREADS */
515
516   /* Start async operation */
517   conn->op = silc_async_alloc(silc_net_connect_abort, NULL, (void *)conn);
518   if (!conn->op) {
519     status = SILC_NET_NO_MEMORY;
520     goto err;
521   }
522
523   /* Do host lookup */
524   if (!silc_net_gethostbyname(remote_ip_addr, FALSE, conn->remote_ip,
525                               sizeof(conn->remote_ip))) {
526     SILC_LOG_ERROR(("Network (%s) unreachable: could not resolve the "
527                     "host", conn->remote));
528     status = SILC_NET_HOST_UNREACHABLE;
529     goto err;
530   }
531
532   /* Create the connection socket */
533   ret = conn->sock->Open(*conn->ss, KAfInet, KSockStream, KProtocolInetTcp);
534   if (ret != KErrNone) {
535     SILC_LOG_ERROR(("Cannot create socket, error %d", ret));
536     status = SILC_NET_ERROR;
537     goto err;
538   }
539
540   /* Set appropriate options */
541   conn->sock->SetOpt(KSoTcpNoDelay, KSolInetTcp, 1);
542   conn->sock->SetOpt(KSoTcpKeepAlive, KSolInetTcp, 1);
543
544   /* Bind to the local address if provided */
545   if (local_ip_addr)
546     if (silc_net_set_sockaddr(&local, local_ip_addr, 0))
547       conn->sock->Bind(local);
548
549   /* Connect to the host */
550   if (!silc_net_set_sockaddr(&remote, conn->remote_ip, remote_port)) {
551     SILC_LOG_ERROR(("Cannot connect (cannot set address)"));
552     status = SILC_NET_ERROR;
553     goto err;
554   }
555   conn->Connect(remote);
556
557   SILC_LOG_DEBUG(("Connection operation in progress"));
558
559   return conn->op;
560
561  err:
562   if (conn->ss) {
563     conn->ss->Close();
564     delete conn->ss;
565   }
566   if (conn->sock)
567     delete conn->sock;
568   if (conn->remote)
569     silc_free(conn->remote);
570   if (conn->op)
571     silc_async_free(conn->op);
572   callback(status, NULL, context);
573   delete conn;
574   return NULL;
575 }
576
577 /****************************** UDP routines ********************************/
578
579 /* Create UDP/IP connection */
580
581 SilcStream silc_net_udp_connect(const char *local_ip_addr, int local_port,
582                                 const char *remote_ip_addr, int remote_port,
583                                 SilcSchedule schedule)
584 {
585   SilcSymbianSocket *s;
586   SilcStream stream;
587   TInetAddr local, remote;
588   TRequestStatus status;
589   RSocket *sock = NULL;
590   RSocketServ *ss = NULL;
591   TInt ret;
592
593   SILC_LOG_DEBUG(("Creating UDP stream"));
594
595   if (!schedule)
596     goto err;
597
598   SILC_LOG_DEBUG(("Binding to local address %s",
599                   local_ip_addr ? local_ip_addr : "0.0.0.0"));
600
601   sock = new RSocket;
602   if (!sock)
603     goto err;
604
605   ss = new RSocketServ;
606   if (!ss)
607     goto err;
608
609   /* Open socket server */
610   ret = ss->Connect();
611   if (ret != KErrNone)
612     goto err;
613
614 #ifdef SILC_THREADS
615   /* Make our socket shareable between threads */
616   ss->ShareAuto();
617 #endif /* SILC_THREADS */
618
619   /* Get local bind address */
620   if (!silc_net_set_sockaddr(&local, local_ip_addr, local_port))
621     goto err;
622
623   /* Create the socket */
624   ret = sock->Open(*ss, KAfInet, KSockDatagram, KProtocolInetUdp);
625   if (ret != KErrNone) {
626     SILC_LOG_ERROR(("Cannot create socket"));
627     goto err;
628   }
629
630   /* Set the socket options */
631   sock->SetOpt(KSoReuseAddr, KSolInetIp, 1);
632
633   /* Bind the listener socket */
634   ret = sock->Bind(local);
635   if (ret != KErrNone) {
636     SILC_LOG_DEBUG(("Cannot bind socket"));
637     goto err;
638   }
639
640   /* Set to connected state if remote address is provided. */
641   if (remote_ip_addr && remote_port) {
642     if (silc_net_set_sockaddr(&remote, remote_ip_addr, remote_port)) {
643       sock->Connect(remote, status);
644       if (status != KErrNone) {
645         SILC_LOG_DEBUG(("Cannot connect UDP stream"));
646         goto err;
647       }
648     }
649   }
650
651   /* Encapsulate into socket stream */
652   s = silc_create_symbian_socket(sock, ss);
653   if (!s)
654     goto err;
655   stream =
656     silc_socket_udp_stream_create((SilcSocket)s, local_ip_addr ?
657                                   silc_net_is_ip6(local_ip_addr) : FALSE,
658                                   remote_ip_addr ? TRUE : FALSE, schedule);
659   if (!stream)
660     goto err;
661
662   SILC_LOG_DEBUG(("UDP stream created, fd=%d", sock));
663   return stream;
664
665  err:
666   if (sock)
667     delete sock;
668   if (ss) {
669     ss->Close();
670     delete ss;
671   }
672   return NULL;
673 }
674
675 /* Sets socket to non-blocking mode */
676
677 int silc_net_set_socket_nonblock(SilcSocket sock)
678 {
679   /* Nothing to do in Symbian where blocking socket mode is asynchronous
680      already (ie. non-blocking). */
681   return 0;
682 }
683
684 /* Converts the IP number string from numbers-and-dots notation to
685    binary form. */
686
687 SilcBool silc_net_addr2bin(const char *addr, void *bin, SilcUInt32 bin_len)
688 {
689   int ret = 0;
690   struct in_addr tmp;
691
692   ret = inet_aton(addr, &tmp);
693   if (bin_len < 4)
694     return FALSE;
695
696   memcpy(bin, (unsigned char *)&tmp.s_addr, 4);
697
698   return ret != 0;
699 }
700
701 /* Get remote host and IP from socket */
702
703 SilcBool silc_net_check_host_by_sock(SilcSocket sock, char **hostname,
704                                      char **ip)
705 {
706   SilcSymbianSocket *s = (SilcSymbianSocket *)sock;
707   TInetAddr addr;
708   char host[256];
709   TBuf<64> tmp;
710
711   if (hostname)
712     *hostname = NULL;
713   *ip = NULL;
714
715   s->sock->RemoteName(addr);
716   addr.Output(tmp);
717
718   *ip = (char *)silc_memdup(tmp.Ptr(), tmp.Length());
719   if (*ip == NULL)
720     return FALSE;
721
722   /* Do reverse lookup if we want hostname too. */
723   if (hostname) {
724     /* Get host by address */
725     if (!silc_net_gethostbyaddr(*ip, host, sizeof(host)))
726       return FALSE;
727
728     *hostname = (char *)silc_memdup(host, strlen(host));
729     SILC_LOG_DEBUG(("Resolved hostname `%s'", *hostname));
730
731     /* Reverse */
732     if (!silc_net_gethostbyname(*hostname, TRUE, host, sizeof(host)))
733       return FALSE;
734
735     if (strcmp(*ip, host))
736       return FALSE;
737   }
738
739   SILC_LOG_DEBUG(("Resolved IP address `%s'", *ip));
740   return TRUE;
741 }
742
743 /* Get local host and IP from socket */
744
745 SilcBool silc_net_check_local_by_sock(SilcSocket sock, char **hostname,
746                                       char **ip)
747 {
748   SilcSymbianSocket *s = (SilcSymbianSocket *)sock;
749   TInetAddr addr;
750   char host[256];
751   TBuf<64> tmp;
752
753   if (hostname)
754     *hostname = NULL;
755   *ip = NULL;
756
757   s->sock->LocalName(addr);
758   addr.Output(tmp);
759
760   *ip = (char *)silc_memdup(tmp.Ptr(), tmp.Length());
761   if (*ip == NULL)
762     return FALSE;
763
764   /* Do reverse lookup if we want hostname too. */
765   if (hostname) {
766     /* Get host by address */
767     if (!silc_net_gethostbyaddr(*ip, host, sizeof(host)))
768       return FALSE;
769
770     *hostname = (char *)silc_memdup(host, strlen(host));
771     SILC_LOG_DEBUG(("Resolved hostname `%s'", *hostname));
772
773     /* Reverse */
774     if (!silc_net_gethostbyname(*hostname, TRUE, host, sizeof(host)))
775       return FALSE;
776
777     if (strcmp(*ip, host))
778       return FALSE;
779   }
780
781   SILC_LOG_DEBUG(("Resolved IP address `%s'", *ip));
782   return TRUE;
783 }
784
785 /* Get remote port from socket */
786
787 SilcUInt16 silc_net_get_remote_port(SilcSocket sock)
788 {
789   SilcSymbianSocket *s = (SilcSymbianSocket *)sock;
790   TInetAddr addr;
791
792   s->sock->RemoteName(addr);
793   return (SilcUInt16)addr.Port();
794 }
795
796 /* Get local port from socket */
797
798 SilcUInt16 silc_net_get_local_port(SilcSocket sock)
799 {
800   SilcSymbianSocket *s = (SilcSymbianSocket *)sock;
801   TInetAddr addr;
802
803   s->sock->LocalName(addr);
804   return (SilcUInt16)addr.Port();
805 }
806
807 } /* extern "C" */