comparison libgaim/network.c @ 14192:60b1bc8dbf37

[gaim-migrate @ 16863] Renamed 'core' to 'libgaim' committer: Tailor Script <tailor@pidgin.im>
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 19 Aug 2006 01:50:10 +0000
parents
children 645598a4ec04
comparison
equal deleted inserted replaced
14191:009db0b357b5 14192:60b1bc8dbf37
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 }