Merged silc_1_0_branch to trunk.
[silc.git] / lib / silcutil / unix / silcunixnet.c
1 /*
2
3   silcunixnet.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1997 - 2005 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 /* $Id$ */
20
21 #include "silcincludes.h"
22 #include "silcnet.h"
23
24 #ifdef HAVE_IPV6
25 #define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ?    \
26   sizeof(so.sin6) : sizeof(so.sin))
27 #else
28 #define SIZEOF_SOCKADDR(so) (sizeof(so.sin))
29 #endif
30
31 typedef union {
32   struct sockaddr sa;
33   struct sockaddr_in sin;
34 #ifdef HAVE_IPV6
35   struct sockaddr_in6 sin6;
36 #endif
37 } SilcSockaddr;
38
39 static bool silc_net_set_sockaddr(SilcSockaddr *addr, const char *ip_addr,
40                                   int port)
41 {
42   int len;
43
44   memset(addr, 0, sizeof(*addr));
45
46   /* Check for IPv4 and IPv6 addresses */
47   if (ip_addr) {
48     if (!silc_net_is_ip(ip_addr)) {
49       SILC_LOG_ERROR(("%s is not IP address", ip_addr));
50       return FALSE;
51     }
52
53     if (silc_net_is_ip4(ip_addr)) {
54       /* IPv4 address */
55       len = sizeof(addr->sin.sin_addr);
56       silc_net_addr2bin(ip_addr, 
57                         (unsigned char *)&addr->sin.sin_addr.s_addr, len);
58       addr->sin.sin_family = AF_INET;
59       addr->sin.sin_port = port ? htons(port) : 0;
60     } else {
61 #ifdef HAVE_IPV6
62       /* IPv6 address */
63       len = sizeof(addr->sin6.sin6_addr);
64       silc_net_addr2bin(ip_addr, 
65                         (unsigned char *)&addr->sin6.sin6_addr, len);
66       addr->sin6.sin6_family = AF_INET6;
67       addr->sin6.sin6_port = port ? htons(port) : 0;
68 #else
69       SILC_LOG_ERROR(("IPv6 support is not compiled in"));
70       return FALSE;
71 #endif
72     }
73   } else {
74     /* Any address */
75     addr->sin.sin_family = AF_INET;
76     addr->sin.sin_addr.s_addr = INADDR_ANY;
77     if (port)
78       addr->sin.sin_port = htons(port);
79   }
80
81   return TRUE;
82 }
83
84 /* This function creates server or daemon or listener or what ever. This
85    does not fork a new process, it must be done by the caller if caller
86    wants to create a child process. This is used by the SILC server. 
87    If argument `ip_addr' is NULL `any' address will be used. Returns 
88    the created socket or -1 on error. */
89
90 int silc_net_create_server(int port, const char *ip_addr)
91 {
92   int sock, rval;
93   SilcSockaddr server;
94
95   SILC_LOG_DEBUG(("Creating a new server listener"));
96
97   /* Set sockaddr for server */
98   if (!silc_net_set_sockaddr(&server, ip_addr, port))
99     return -1;
100
101   /* Create the socket */
102   sock = socket(server.sin.sin_family, SOCK_STREAM, 0);
103   if (sock < 0) {
104     SILC_LOG_ERROR(("Cannot create socket: %s", strerror(errno)));
105     return -1;
106   }
107
108   /* Set the socket options */
109   rval = silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
110   if (rval < 0) {
111     SILC_LOG_ERROR(("Cannot set socket options: %s", strerror(errno)));
112     return -1;
113   }
114
115   /* Bind the server socket */
116   rval = bind(sock, &server.sa, SIZEOF_SOCKADDR(server));
117   if (rval < 0) {
118     SILC_LOG_DEBUG(("Cannot bind socket: %s", strerror(errno)));
119     return -1;
120   }
121
122   /* Specify that we are listenning */
123   rval = listen(sock, 5);
124   if (rval < 0) {
125     SILC_LOG_ERROR(("Cannot set socket listenning: %s", strerror(errno)));
126     return -1;
127   }
128
129   /* Set the server socket to non-blocking mode */
130   silc_net_set_socket_nonblock(sock);
131
132   SILC_LOG_DEBUG(("Server listener created, fd=%d", sock));
133
134   return sock;
135 }
136
137 /* Closes the server by closing the socket connection. */
138
139 void silc_net_close_server(int sock)
140 {
141   shutdown(sock, 2);
142   close(sock);
143
144   SILC_LOG_DEBUG(("Server socket closed"));
145 }
146
147 /* Creates a connection (TCP/IP) to a remote host. Returns the connection
148    socket or -1 on error. This blocks the process while trying to create
149    the connection. */
150
151 int silc_net_create_connection(const char *local_ip, int port, 
152                                const char *host)
153 {
154   int sock, rval;
155   char ip_addr[64];
156   SilcSockaddr desthost;
157   bool prefer_ipv6 = TRUE;
158
159   SILC_LOG_DEBUG(("Creating connection to host %s port %d", host, port));
160
161   /* Do host lookup */
162  retry:
163   if (!silc_net_gethostbyname(host, prefer_ipv6, ip_addr, sizeof(ip_addr))) {
164     SILC_LOG_ERROR(("Network (%s) unreachable: could not resolve the "
165                     "IP address", host));
166     return -1;
167   }
168
169   /* Set sockaddr for this connection */
170   if (!silc_net_set_sockaddr(&desthost, ip_addr, port))
171     return -1;
172
173   /* Create the connection socket */
174   sock = socket(desthost.sin.sin_family, SOCK_STREAM, 0);
175   if (sock < 0) {
176     /* If address is IPv6, then fallback to IPv4 and see whether we can do
177        better with that on socket creation. */
178     if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) {
179       prefer_ipv6 = FALSE;
180       goto retry;
181     }
182
183     SILC_LOG_ERROR(("Cannot create socket: %s", strerror(errno)));
184     return -1;
185   }
186
187   /* Bind to the local address if provided */
188   if (local_ip) {
189     SilcSockaddr local;
190
191     /* Set sockaddr for local listener, and try to bind it. */
192     if (silc_net_set_sockaddr(&local, local_ip, 0))
193       bind(sock, &local.sa, SIZEOF_SOCKADDR(local));
194   }
195
196   /* Connect to the host */
197   rval = connect(sock, &desthost.sa, SIZEOF_SOCKADDR(desthost));
198   if (rval < 0) {
199     /* retry using an IPv4 adress, if IPv6 didn't work */
200     if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) {
201       shutdown(sock, 2);
202       close(sock);
203
204       prefer_ipv6 = FALSE;
205       goto retry;
206     }
207     SILC_LOG_ERROR(("Cannot connect to remote host: %s", strerror(errno)));
208     shutdown(sock, 2);
209     close(sock);
210     return -1;
211   }
212
213   /* Set appropriate options */
214 #if defined(TCP_NODELAY)
215   silc_net_set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1);
216 #endif
217   silc_net_set_socket_opt(sock, SOL_SOCKET, SO_KEEPALIVE, 1);
218
219   SILC_LOG_DEBUG(("Connection created"));
220
221   return sock;
222 }
223
224 /* Creates a connection (TCP/IP) to a remote host. Returns the connection
225    socket or -1 on error. This creates non-blocking socket hence the
226    connection returns directly. To get the result of the connect() one
227    must select() the socket and read the result after it's ready. */
228
229 int silc_net_create_connection_async(const char *local_ip, int port, 
230                                      const char *host)
231 {
232   int sock, rval;
233   char ip_addr[64];
234   SilcSockaddr desthost;
235   bool prefer_ipv6 = TRUE;
236
237   SILC_LOG_DEBUG(("Creating connection (async) to host %s port %d", 
238                   host, port));
239
240   /* Do host lookup */
241  retry:
242   if (!silc_net_gethostbyname(host, prefer_ipv6, ip_addr, sizeof(ip_addr))) {
243     SILC_LOG_ERROR(("Network (%s) unreachable: could not resolve the "
244                     "IP address", host));
245     return -1;
246   }
247
248   /* Set sockaddr for this connection */
249   if (!silc_net_set_sockaddr(&desthost, ip_addr, port))
250     return -1;
251
252   /* Create the connection socket */
253   sock = socket(desthost.sin.sin_family, SOCK_STREAM, 0);
254   if (sock < 0) {
255     /* If address is IPv6, then fallback to IPv4 and see whether we can do
256        better with that on socket creation. */
257     if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) {
258       prefer_ipv6 = FALSE;
259       goto retry;
260     }
261
262     SILC_LOG_ERROR(("Cannot create socket: %s", strerror(errno)));
263     return -1;
264   }
265
266   /* Bind to the local address if provided */
267   if (local_ip) {
268     SilcSockaddr local;
269
270     /* Set sockaddr for local listener, and try to bind it. */
271     if (silc_net_set_sockaddr(&local, local_ip, 0))
272       bind(sock, &local.sa, SIZEOF_SOCKADDR(local));
273   }
274
275   /* Set the socket to non-blocking mode */
276   silc_net_set_socket_nonblock(sock);
277
278   /* Connect to the host */
279   rval = connect(sock, &desthost.sa, SIZEOF_SOCKADDR(desthost));
280   if (rval < 0) {
281     if (errno !=  EINPROGRESS) {
282       /* retry using an IPv4 adress, if IPv6 didn't work */
283       if (prefer_ipv6 && silc_net_is_ip6(ip_addr)) {
284         shutdown(sock, 2);
285         close(sock);
286
287         prefer_ipv6 = FALSE;
288         goto retry;
289       }
290
291       SILC_LOG_ERROR(("Cannot connect to remote host: %s", strerror(errno)));
292       shutdown(sock, 2);
293       close(sock);
294       return -1;
295     }
296   }
297
298   /* Set appropriate options */
299 #if defined(TCP_NODELAY)
300   silc_net_set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1);
301 #endif
302   silc_net_set_socket_opt(sock, SOL_SOCKET, SO_KEEPALIVE, 1);
303
304   SILC_LOG_DEBUG(("Connection operation in progress"));
305
306   return sock;
307 }
308
309 /* Closes the connection by closing the socket connection. */
310
311 void silc_net_close_connection(int sock)
312 {
313   close(sock);
314 }
315
316 /* Set's the socket to non-blocking mode. */
317
318 int silc_net_set_socket_nonblock(int sock)
319 {
320   return fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
321 }
322
323 /* Converts the IP number string from numbers-and-dots notation to
324    binary form. */
325
326 bool silc_net_addr2bin(const char *addr, void *bin, SilcUInt32 bin_len)
327 {
328   int ret = 0;
329
330   if (silc_net_is_ip4(addr)) {
331     /* IPv4 address */
332     struct in_addr tmp;
333     ret = inet_aton(addr, &tmp);
334     if (bin_len < 4)
335       return FALSE;
336     
337     memcpy(bin, (unsigned char *)&tmp.s_addr, 4);
338 #ifdef HAVE_IPV6
339   } else {
340     struct addrinfo hints, *ai;
341     SilcSockaddr *s;
342
343     /* IPv6 address */
344     if (bin_len < 16)
345       return FALSE;
346
347     memset(&hints, 0, sizeof(hints));
348     hints.ai_family = AF_INET6;
349     if (getaddrinfo(addr, NULL, &hints, &ai))
350       return FALSE;
351
352     if (ai) {
353       s = (SilcSockaddr *)ai->ai_addr;
354       memcpy(bin, &s->sin6.sin6_addr, sizeof(s->sin6.sin6_addr));
355       freeaddrinfo(ai);
356     }
357
358     ret = TRUE;
359 #endif /* HAVE_IPV6 */
360   }
361
362   return ret != 0;
363 }