14192
|
1 /**
|
|
2 * @file network.c Network Implementation
|
|
3 * @ingroup core
|
|
4 *
|
|
5 * gaim
|
|
6 *
|
|
7 * Gaim is the legal property of its developers, whose names are too numerous
|
|
8 * to list here. Please refer to the COPYRIGHT file distributed with this
|
|
9 * source distribution.
|
|
10 *
|
|
11 * This program is free software; you can redistribute it and/or modify
|
|
12 * it under the terms of the GNU General Public License as published by
|
|
13 * the Free Software Foundation; either version 2 of the License, or
|
|
14 * (at your option) any later version.
|
|
15 *
|
|
16 * This program is distributed in the hope that it will be useful,
|
|
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19 * GNU General Public License for more details.
|
|
20 *
|
|
21 * You should have received a copy of the GNU General Public License
|
|
22 * along with this program; if not, write to the Free Software
|
|
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
24 */
|
|
25
|
|
26 #include "internal.h"
|
|
27
|
|
28 #ifndef _WIN32
|
|
29 #include <net/if.h>
|
|
30 #include <sys/ioctl.h>
|
|
31 #endif
|
|
32
|
|
33 /* Solaris */
|
|
34 #if defined (__SVR4) && defined (__sun)
|
|
35 #include <sys/sockio.h>
|
|
36 #endif
|
|
37
|
|
38 #include "debug.h"
|
|
39 #include "account.h"
|
|
40 #include "network.h"
|
|
41 #include "prefs.h"
|
|
42 #include "stun.h"
|
|
43 #include "upnp.h"
|
|
44
|
14267
|
45 struct _GaimNetworkListenData {
|
14192
|
46 int listenfd;
|
|
47 int socket_type;
|
|
48 gboolean retry;
|
|
49 gboolean adding;
|
|
50 GaimNetworkListenCallback cb;
|
|
51 gpointer cb_data;
|
14267
|
52 };
|
14192
|
53
|
|
54 const unsigned char *
|
|
55 gaim_network_ip_atoi(const char *ip)
|
|
56 {
|
|
57 static unsigned char ret[4];
|
|
58 gchar *delimiter = ".";
|
|
59 gchar **split;
|
|
60 int i;
|
|
61
|
|
62 g_return_val_if_fail(ip != NULL, NULL);
|
|
63
|
|
64 split = g_strsplit(ip, delimiter, 4);
|
|
65 for (i = 0; split[i] != NULL; i++)
|
|
66 ret[i] = atoi(split[i]);
|
|
67 g_strfreev(split);
|
|
68
|
|
69 /* i should always be 4 */
|
|
70 if (i != 4)
|
|
71 return NULL;
|
|
72
|
|
73 return ret;
|
|
74 }
|
|
75
|
|
76 void
|
|
77 gaim_network_set_public_ip(const char *ip)
|
|
78 {
|
|
79 g_return_if_fail(ip != NULL);
|
|
80
|
|
81 /* XXX - Ensure the IP address is valid */
|
|
82
|
|
83 gaim_prefs_set_string("/core/network/public_ip", ip);
|
|
84 }
|
|
85
|
|
86 const char *
|
|
87 gaim_network_get_public_ip(void)
|
|
88 {
|
|
89 return gaim_prefs_get_string("/core/network/public_ip");
|
|
90 }
|
|
91
|
|
92 const char *
|
|
93 gaim_network_get_local_system_ip(int fd)
|
|
94 {
|
|
95 char buffer[1024];
|
|
96 static char ip[16];
|
|
97 char *tmp;
|
|
98 struct ifconf ifc;
|
|
99 struct ifreq *ifr;
|
|
100 struct sockaddr_in *sinptr;
|
|
101 guint32 lhost = htonl(127 * 256 * 256 * 256 + 1);
|
|
102 long unsigned int add;
|
|
103 int source = fd;
|
|
104
|
|
105 if (fd < 0)
|
|
106 source = socket(PF_INET,SOCK_STREAM, 0);
|
|
107
|
|
108 ifc.ifc_len = sizeof(buffer);
|
|
109 ifc.ifc_req = (struct ifreq *)buffer;
|
|
110 ioctl(source, SIOCGIFCONF, &ifc);
|
|
111
|
|
112 if (fd < 0)
|
|
113 close(source);
|
|
114
|
|
115 tmp = buffer;
|
|
116 while (tmp < buffer + ifc.ifc_len)
|
|
117 {
|
|
118 ifr = (struct ifreq *)tmp;
|
|
119 tmp += sizeof(struct ifreq);
|
|
120
|
|
121 if (ifr->ifr_addr.sa_family == AF_INET)
|
|
122 {
|
|
123 sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
|
|
124 if (sinptr->sin_addr.s_addr != lhost)
|
|
125 {
|
|
126 add = ntohl(sinptr->sin_addr.s_addr);
|
|
127 g_snprintf(ip, 16, "%lu.%lu.%lu.%lu",
|
|
128 ((add >> 24) & 255),
|
|
129 ((add >> 16) & 255),
|
|
130 ((add >> 8) & 255),
|
|
131 add & 255);
|
|
132
|
|
133 return ip;
|
|
134 }
|
|
135 }
|
|
136 }
|
|
137
|
|
138 return "0.0.0.0";
|
|
139 }
|
|
140
|
|
141 const char *
|
|
142 gaim_network_get_my_ip(int fd)
|
|
143 {
|
|
144 const char *ip = NULL;
|
|
145 GaimStunNatDiscovery *stun;
|
|
146
|
|
147 /* Check if the user specified an IP manually */
|
|
148 if (!gaim_prefs_get_bool("/core/network/auto_ip")) {
|
|
149 ip = gaim_network_get_public_ip();
|
|
150 if ((ip != NULL) && (*ip != '\0'))
|
|
151 return ip;
|
|
152 }
|
|
153
|
|
154 /* Check if STUN discovery was already done */
|
|
155 stun = gaim_stun_discover(NULL);
|
|
156 if ((stun != NULL) && (stun->status == GAIM_STUN_STATUS_DISCOVERED))
|
|
157 return stun->publicip;
|
|
158
|
|
159 /* Attempt to get the IP from a NAT device using UPnP */
|
|
160 ip = gaim_upnp_get_public_ip();
|
|
161 if (ip != NULL)
|
|
162 return ip;
|
|
163
|
|
164 /* Just fetch the IP of the local system */
|
|
165 return gaim_network_get_local_system_ip(fd);
|
|
166 }
|
|
167
|
|
168
|
|
169 static void
|
|
170 gaim_network_set_upnp_port_mapping_cb(gboolean success, gpointer data)
|
|
171 {
|
14267
|
172 GaimNetworkListenData *listen_data;
|
|
173
|
|
174 listen_data = data;
|
|
175 /* TODO: Once we're keeping track of upnp requests... */
|
|
176 /* listen_data->pnp_data = NULL; */
|
14192
|
177
|
|
178 if (!success) {
|
|
179 gaim_debug_info("network", "Couldn't create UPnP mapping\n");
|
14267
|
180 if (listen_data->retry) {
|
|
181 listen_data->retry = FALSE;
|
|
182 listen_data->adding = FALSE;
|
|
183 /* TODO: Need to keep track of this return value! */
|
14192
|
184 gaim_upnp_remove_port_mapping(
|
14267
|
185 gaim_network_get_port_from_fd(listen_data->listenfd),
|
|
186 (listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
|
|
187 gaim_network_set_upnp_port_mapping_cb, listen_data);
|
14192
|
188 return;
|
|
189 }
|
14267
|
190 } else if (!listen_data->adding) {
|
14192
|
191 /* We've tried successfully to remove the port mapping.
|
|
192 * Try to add it again */
|
14267
|
193 listen_data->adding = TRUE;
|
|
194 /* TODO: Need to keep track of this return value! */
|
14192
|
195 gaim_upnp_set_port_mapping(
|
14267
|
196 gaim_network_get_port_from_fd(listen_data->listenfd),
|
|
197 (listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
|
|
198 gaim_network_set_upnp_port_mapping_cb, listen_data);
|
14192
|
199 return;
|
|
200 }
|
|
201
|
14267
|
202 if (listen_data->cb)
|
|
203 listen_data->cb(listen_data->listenfd, listen_data->cb_data);
|
14192
|
204
|
14267
|
205 gaim_network_listen_cancel(listen_data);
|
14192
|
206 }
|
|
207
|
|
208
|
14267
|
209 static GaimNetworkListenData *
|
14192
|
210 gaim_network_do_listen(unsigned short port, int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
|
|
211 {
|
|
212 int listenfd = -1;
|
|
213 const int on = 1;
|
14267
|
214 GaimNetworkListenData *listen_data;
|
14192
|
215 #ifdef HAVE_GETADDRINFO
|
|
216 int errnum;
|
|
217 struct addrinfo hints, *res, *next;
|
|
218 char serv[6];
|
|
219
|
|
220 /*
|
|
221 * Get a list of addresses on this machine.
|
|
222 */
|
|
223 snprintf(serv, sizeof(serv), "%hu", port);
|
|
224 memset(&hints, 0, sizeof(struct addrinfo));
|
|
225 hints.ai_flags = AI_PASSIVE;
|
|
226 hints.ai_family = AF_UNSPEC;
|
|
227 hints.ai_socktype = socket_type;
|
|
228 errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res);
|
|
229 if (errnum != 0) {
|
|
230 #ifndef _WIN32
|
|
231 gaim_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum));
|
|
232 if (errnum == EAI_SYSTEM)
|
|
233 gaim_debug_warning("network", "getaddrinfo: system error: %s\n", strerror(errno));
|
|
234 #else
|
|
235 gaim_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum);
|
|
236 #endif
|
14267
|
237 return NULL;
|
14192
|
238 }
|
|
239
|
|
240 /*
|
|
241 * Go through the list of addresses and attempt to listen on
|
|
242 * one of them.
|
|
243 * XXX - Try IPv6 addresses first?
|
|
244 */
|
|
245 for (next = res; next != NULL; next = next->ai_next) {
|
|
246 listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol);
|
|
247 if (listenfd < 0)
|
|
248 continue;
|
|
249 if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
|
|
250 gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno));
|
|
251 if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0)
|
|
252 break; /* success */
|
|
253 /* XXX - It is unclear to me (datallah) whether we need to be
|
|
254 using a new socket each time */
|
|
255 close(listenfd);
|
|
256 }
|
|
257
|
|
258 freeaddrinfo(res);
|
|
259
|
|
260 if (next == NULL)
|
14267
|
261 return NULL;
|
14192
|
262 #else
|
|
263 struct sockaddr_in sockin;
|
|
264
|
|
265 if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) {
|
|
266 gaim_debug_warning("network", "socket: %s\n", strerror(errno));
|
14267
|
267 return NULL;
|
14192
|
268 }
|
|
269
|
|
270 if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
|
|
271 gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno));
|
|
272
|
|
273 memset(&sockin, 0, sizeof(struct sockaddr_in));
|
|
274 sockin.sin_family = PF_INET;
|
|
275 sockin.sin_port = htons(port);
|
|
276
|
|
277 if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) {
|
|
278 gaim_debug_warning("network", "bind: %s\n", strerror(errno));
|
|
279 close(listenfd);
|
14267
|
280 return NULL;
|
14192
|
281 }
|
|
282 #endif
|
|
283
|
|
284 if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) {
|
|
285 gaim_debug_warning("network", "listen: %s\n", strerror(errno));
|
|
286 close(listenfd);
|
14267
|
287 return NULL;
|
14192
|
288 }
|
|
289 fcntl(listenfd, F_SETFL, O_NONBLOCK);
|
|
290
|
|
291 gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd));
|
|
292
|
14267
|
293 listen_data = g_new0(GaimNetworkListenData, 1);
|
|
294 listen_data->listenfd = listenfd;
|
|
295 listen_data->adding = TRUE;
|
|
296 listen_data->retry = TRUE;
|
|
297 listen_data->cb = cb;
|
|
298 listen_data->cb_data = cb_data;
|
14192
|
299
|
14267
|
300 /* TODO: Need to keep track of this return value! */
|
14192
|
301 gaim_upnp_set_port_mapping(
|
|
302 gaim_network_get_port_from_fd(listenfd),
|
|
303 (socket_type == SOCK_STREAM) ? "TCP" : "UDP",
|
14267
|
304 gaim_network_set_upnp_port_mapping_cb, listen_data);
|
14192
|
305
|
14267
|
306 return listen_data;
|
14192
|
307 }
|
|
308
|
14267
|
309 GaimNetworkListenData *
|
14192
|
310 gaim_network_listen(unsigned short port, int socket_type,
|
|
311 GaimNetworkListenCallback cb, gpointer cb_data)
|
|
312 {
|
14267
|
313 g_return_val_if_fail(port != 0, NULL);
|
14192
|
314
|
|
315 return gaim_network_do_listen(port, socket_type, cb, cb_data);
|
|
316 }
|
|
317
|
14267
|
318 GaimNetworkListenData *
|
14192
|
319 gaim_network_listen_range(unsigned short start, unsigned short end,
|
|
320 int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
|
|
321 {
|
14267
|
322 GaimNetworkListenData *ret = NULL;
|
14192
|
323
|
|
324 if (gaim_prefs_get_bool("/core/network/ports_range_use")) {
|
|
325 start = gaim_prefs_get_int("/core/network/ports_range_start");
|
|
326 end = gaim_prefs_get_int("/core/network/ports_range_end");
|
|
327 } else {
|
|
328 if (end < start)
|
|
329 end = start;
|
|
330 }
|
|
331
|
|
332 for (; start <= end; start++) {
|
|
333 ret = gaim_network_do_listen(start, socket_type, cb, cb_data);
|
14267
|
334 if (ret != NULL)
|
14192
|
335 break;
|
|
336 }
|
|
337
|
|
338 return ret;
|
|
339 }
|
|
340
|
14267
|
341 void gaim_network_listen_cancel(GaimNetworkListenData *listen_data)
|
|
342 {
|
|
343 g_free(listen_data);
|
|
344 }
|
|
345
|
14192
|
346 unsigned short
|
|
347 gaim_network_get_port_from_fd(int fd)
|
|
348 {
|
|
349 struct sockaddr_in addr;
|
|
350 socklen_t len;
|
|
351
|
|
352 g_return_val_if_fail(fd >= 0, 0);
|
|
353
|
|
354 len = sizeof(addr);
|
|
355 if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) {
|
|
356 gaim_debug_warning("network", "getsockname: %s\n", strerror(errno));
|
|
357 return 0;
|
|
358 }
|
|
359
|
|
360 return ntohs(addr.sin_port);
|
|
361 }
|
|
362
|
|
363 void
|
|
364 gaim_network_init(void)
|
|
365 {
|
|
366 gaim_prefs_add_none ("/core/network");
|
|
367 gaim_prefs_add_bool ("/core/network/auto_ip", TRUE);
|
|
368 gaim_prefs_add_string("/core/network/public_ip", "");
|
|
369 gaim_prefs_add_bool ("/core/network/ports_range_use", FALSE);
|
|
370 gaim_prefs_add_int ("/core/network/ports_range_start", 1024);
|
|
371 gaim_prefs_add_int ("/core/network/ports_range_end", 2048);
|
|
372
|
|
373 gaim_upnp_discover(NULL, NULL);
|
|
374 }
|