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
|
|
45 typedef struct {
|
|
46 int listenfd;
|
|
47 int socket_type;
|
|
48 gboolean retry;
|
|
49 gboolean adding;
|
|
50 GaimNetworkListenCallback cb;
|
|
51 gpointer cb_data;
|
|
52 } ListenUPnPData;
|
|
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 {
|
|
172 ListenUPnPData *ldata = data;
|
|
173
|
|
174 if (!success) {
|
|
175 gaim_debug_info("network", "Couldn't create UPnP mapping\n");
|
|
176 if (ldata->retry) {
|
|
177 ldata->retry = FALSE;
|
|
178 ldata->adding = FALSE;
|
|
179 gaim_upnp_remove_port_mapping(
|
|
180 gaim_network_get_port_from_fd(ldata->listenfd),
|
|
181 (ldata->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
|
|
182 gaim_network_set_upnp_port_mapping_cb, ldata);
|
|
183 return;
|
|
184 }
|
|
185 } else if (!ldata->adding) {
|
|
186 /* We've tried successfully to remove the port mapping.
|
|
187 * Try to add it again */
|
|
188 ldata->adding = TRUE;
|
|
189 gaim_upnp_set_port_mapping(
|
|
190 gaim_network_get_port_from_fd(ldata->listenfd),
|
|
191 (ldata->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
|
|
192 gaim_network_set_upnp_port_mapping_cb, ldata);
|
|
193 return;
|
|
194 }
|
|
195
|
|
196 if (ldata->cb)
|
|
197 ldata->cb(ldata->listenfd, ldata->cb_data);
|
|
198
|
|
199 g_free(ldata);
|
|
200 }
|
|
201
|
|
202
|
|
203 static gboolean
|
|
204 gaim_network_do_listen(unsigned short port, int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
|
|
205 {
|
|
206 int listenfd = -1;
|
|
207 const int on = 1;
|
|
208 ListenUPnPData *ld;
|
|
209 #ifdef HAVE_GETADDRINFO
|
|
210 int errnum;
|
|
211 struct addrinfo hints, *res, *next;
|
|
212 char serv[6];
|
|
213
|
|
214 /*
|
|
215 * Get a list of addresses on this machine.
|
|
216 */
|
|
217 snprintf(serv, sizeof(serv), "%hu", port);
|
|
218 memset(&hints, 0, sizeof(struct addrinfo));
|
|
219 hints.ai_flags = AI_PASSIVE;
|
|
220 hints.ai_family = AF_UNSPEC;
|
|
221 hints.ai_socktype = socket_type;
|
|
222 errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res);
|
|
223 if (errnum != 0) {
|
|
224 #ifndef _WIN32
|
|
225 gaim_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum));
|
|
226 if (errnum == EAI_SYSTEM)
|
|
227 gaim_debug_warning("network", "getaddrinfo: system error: %s\n", strerror(errno));
|
|
228 #else
|
|
229 gaim_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum);
|
|
230 #endif
|
|
231 return FALSE;
|
|
232 }
|
|
233
|
|
234 /*
|
|
235 * Go through the list of addresses and attempt to listen on
|
|
236 * one of them.
|
|
237 * XXX - Try IPv6 addresses first?
|
|
238 */
|
|
239 for (next = res; next != NULL; next = next->ai_next) {
|
|
240 listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol);
|
|
241 if (listenfd < 0)
|
|
242 continue;
|
|
243 if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
|
|
244 gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno));
|
|
245 if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0)
|
|
246 break; /* success */
|
|
247 /* XXX - It is unclear to me (datallah) whether we need to be
|
|
248 using a new socket each time */
|
|
249 close(listenfd);
|
|
250 }
|
|
251
|
|
252 freeaddrinfo(res);
|
|
253
|
|
254 if (next == NULL)
|
|
255 return FALSE;
|
|
256 #else
|
|
257 struct sockaddr_in sockin;
|
|
258
|
|
259 if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) {
|
|
260 gaim_debug_warning("network", "socket: %s\n", strerror(errno));
|
|
261 return FALSE;
|
|
262 }
|
|
263
|
|
264 if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
|
|
265 gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno));
|
|
266
|
|
267 memset(&sockin, 0, sizeof(struct sockaddr_in));
|
|
268 sockin.sin_family = PF_INET;
|
|
269 sockin.sin_port = htons(port);
|
|
270
|
|
271 if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) {
|
|
272 gaim_debug_warning("network", "bind: %s\n", strerror(errno));
|
|
273 close(listenfd);
|
|
274 return FALSE;
|
|
275 }
|
|
276 #endif
|
|
277
|
|
278 if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) {
|
|
279 gaim_debug_warning("network", "listen: %s\n", strerror(errno));
|
|
280 close(listenfd);
|
|
281 return FALSE;
|
|
282 }
|
|
283 fcntl(listenfd, F_SETFL, O_NONBLOCK);
|
|
284
|
|
285 gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd));
|
|
286
|
|
287 ld = g_new0(ListenUPnPData, 1);
|
|
288 ld->listenfd = listenfd;
|
|
289 ld->adding = TRUE;
|
|
290 ld->retry = TRUE;
|
|
291 ld->cb = cb;
|
|
292 ld->cb_data = cb_data;
|
|
293
|
|
294 gaim_upnp_set_port_mapping(
|
|
295 gaim_network_get_port_from_fd(listenfd),
|
|
296 (socket_type == SOCK_STREAM) ? "TCP" : "UDP",
|
|
297 gaim_network_set_upnp_port_mapping_cb, ld);
|
|
298
|
|
299 return TRUE;
|
|
300 }
|
|
301
|
|
302 gboolean
|
|
303 gaim_network_listen(unsigned short port, int socket_type,
|
|
304 GaimNetworkListenCallback cb, gpointer cb_data)
|
|
305 {
|
|
306 g_return_val_if_fail(port != 0, -1);
|
|
307
|
|
308 return gaim_network_do_listen(port, socket_type, cb, cb_data);
|
|
309 }
|
|
310
|
|
311 gboolean
|
|
312 gaim_network_listen_range(unsigned short start, unsigned short end,
|
|
313 int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
|
|
314 {
|
|
315 gboolean ret = FALSE;
|
|
316
|
|
317 if (gaim_prefs_get_bool("/core/network/ports_range_use")) {
|
|
318 start = gaim_prefs_get_int("/core/network/ports_range_start");
|
|
319 end = gaim_prefs_get_int("/core/network/ports_range_end");
|
|
320 } else {
|
|
321 if (end < start)
|
|
322 end = start;
|
|
323 }
|
|
324
|
|
325 for (; start <= end; start++) {
|
|
326 ret = gaim_network_do_listen(start, socket_type, cb, cb_data);
|
|
327 if (ret)
|
|
328 break;
|
|
329 }
|
|
330
|
|
331 return ret;
|
|
332 }
|
|
333
|
|
334 unsigned short
|
|
335 gaim_network_get_port_from_fd(int fd)
|
|
336 {
|
|
337 struct sockaddr_in addr;
|
|
338 socklen_t len;
|
|
339
|
|
340 g_return_val_if_fail(fd >= 0, 0);
|
|
341
|
|
342 len = sizeof(addr);
|
|
343 if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) {
|
|
344 gaim_debug_warning("network", "getsockname: %s\n", strerror(errno));
|
|
345 return 0;
|
|
346 }
|
|
347
|
|
348 return ntohs(addr.sin_port);
|
|
349 }
|
|
350
|
|
351 void
|
|
352 gaim_network_init(void)
|
|
353 {
|
|
354 gaim_prefs_add_none ("/core/network");
|
|
355 gaim_prefs_add_bool ("/core/network/auto_ip", TRUE);
|
|
356 gaim_prefs_add_string("/core/network/public_ip", "");
|
|
357 gaim_prefs_add_bool ("/core/network/ports_range_use", FALSE);
|
|
358 gaim_prefs_add_int ("/core/network/ports_range_start", 1024);
|
|
359 gaim_prefs_add_int ("/core/network/ports_range_end", 2048);
|
|
360
|
|
361 gaim_upnp_discover(NULL, NULL);
|
|
362 }
|