1/* $Id: connecthostport.c,v 1.13 2014/03/31 12:36:36 nanard Exp $ */
2/* Project : miniupnp
3 * Author : Thomas Bernard
4 * Copyright (c) 2010-2014 Thomas Bernard
5 * This software is subject to the conditions detailed in the
6 * LICENCE file provided in this distribution. */
7
8/* use getaddrinfo() or gethostbyname()
9 * uncomment the following line in order to use gethostbyname() */
10#ifdef NO_GETADDRINFO
11#define USE_GETHOSTBYNAME
12#endif
13
14#include <string.h>
15#include <stdio.h>
16#ifdef _WIN32
17#include <winsock2.h>
18#include <ws2tcpip.h>
19#include <io.h>
20#define MAXHOSTNAMELEN 64
21#define snprintf _snprintf
22#define herror
23#define socklen_t int
24#else /* #ifdef _WIN32 */
25#include <unistd.h>
26#include <sys/param.h>
27#include <sys/select.h>
28#include <errno.h>
29#define closesocket close
30#include <netdb.h>
31#include <netinet/in.h>
32/* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions
33 * during the connect() call */
34#define MINIUPNPC_IGNORE_EINTR
35#ifndef USE_GETHOSTBYNAME
36#include <sys/types.h>
37#include <sys/socket.h>
38#include <sys/select.h>
39#endif /* #ifndef USE_GETHOSTBYNAME */
40#endif /* #else _WIN32 */
41
42/* definition of PRINT_SOCKET_ERROR */
43#ifdef _WIN32
44#define PRINT_SOCKET_ERROR(x)    printf("Socket error: %s, %d\n", x, WSAGetLastError());
45#else
46#define PRINT_SOCKET_ERROR(x) perror(x)
47#endif
48
49#if defined(__amigaos__) || defined(__amigaos4__)
50#define herror(A) printf("%s\n", A)
51#endif
52
53#include "connecthostport.h"
54
55#ifndef MAXHOSTNAMELEN
56#define MAXHOSTNAMELEN 64
57#endif
58
59/* connecthostport()
60 * return a socket connected (TCP) to the host and port
61 * or -1 in case of error */
62int connecthostport(const char * host, unsigned short port,
63                    unsigned int scope_id)
64{
65	int s, n;
66#ifdef USE_GETHOSTBYNAME
67	struct sockaddr_in dest;
68	struct hostent *hp;
69#else /* #ifdef USE_GETHOSTBYNAME */
70	char tmp_host[MAXHOSTNAMELEN+1];
71	char port_str[8];
72	struct addrinfo *ai, *p;
73	struct addrinfo hints;
74#endif /* #ifdef USE_GETHOSTBYNAME */
75#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT
76	struct timeval timeout;
77#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */
78
79#ifdef USE_GETHOSTBYNAME
80	hp = gethostbyname(host);
81	if(hp == NULL)
82	{
83		herror(host);
84		return -1;
85	}
86	memcpy(&dest.sin_addr, hp->h_addr, sizeof(dest.sin_addr));
87	memset(dest.sin_zero, 0, sizeof(dest.sin_zero));
88	s = socket(PF_INET, SOCK_STREAM, 0);
89	if(s < 0)
90	{
91		PRINT_SOCKET_ERROR("socket");
92		return -1;
93	}
94#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT
95	/* setting a 3 seconds timeout for the connect() call */
96	timeout.tv_sec = 3;
97	timeout.tv_usec = 0;
98	if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0)
99	{
100		PRINT_SOCKET_ERROR("setsockopt");
101	}
102	timeout.tv_sec = 3;
103	timeout.tv_usec = 0;
104	if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0)
105	{
106		PRINT_SOCKET_ERROR("setsockopt");
107	}
108#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */
109	dest.sin_family = AF_INET;
110	dest.sin_port = htons(port);
111	n = connect(s, (struct sockaddr *)&dest, sizeof(struct sockaddr_in));
112#ifdef MINIUPNPC_IGNORE_EINTR
113	/* EINTR The system call was interrupted by a signal that was caught
114	 * EINPROGRESS The socket is nonblocking and the connection cannot
115	 *             be completed immediately. */
116	while(n < 0 && (errno == EINTR || errno = EINPROGRESS))
117	{
118		socklen_t len;
119		fd_set wset;
120		int err;
121		FD_ZERO(&wset);
122		FD_SET(s, &wset);
123		if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR)
124			continue;
125		/*len = 0;*/
126		/*n = getpeername(s, NULL, &len);*/
127		len = sizeof(err);
128		if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
129			PRINT_SOCKET_ERROR("getsockopt");
130			closesocket(s);
131			return -1;
132		}
133		if(err != 0) {
134			errno = err;
135			n = -1;
136		}
137	}
138#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */
139	if(n<0)
140	{
141		PRINT_SOCKET_ERROR("connect");
142		closesocket(s);
143		return -1;
144	}
145#else /* #ifdef USE_GETHOSTBYNAME */
146	/* use getaddrinfo() instead of gethostbyname() */
147	memset(&hints, 0, sizeof(hints));
148	/* hints.ai_flags = AI_ADDRCONFIG; */
149#ifdef AI_NUMERICSERV
150	hints.ai_flags = AI_NUMERICSERV;
151#endif
152	hints.ai_socktype = SOCK_STREAM;
153	hints.ai_family = AF_UNSPEC; /* AF_INET, AF_INET6 or AF_UNSPEC */
154	/* hints.ai_protocol = IPPROTO_TCP; */
155	snprintf(port_str, sizeof(port_str), "%hu", port);
156	if(host[0] == '[')
157	{
158		/* literal ip v6 address */
159		int i, j;
160		for(i = 0, j = 1; host[j] && (host[j] != ']') && i < MAXHOSTNAMELEN; i++, j++)
161		{
162			tmp_host[i] = host[j];
163			if(0 == memcmp(host+j, "%25", 3))	/* %25 is just url encoding for '%' */
164				j+=2;							/* skip "25" */
165		}
166		tmp_host[i] = '\0';
167	}
168	else
169	{
170		strncpy(tmp_host, host, MAXHOSTNAMELEN);
171	}
172	tmp_host[MAXHOSTNAMELEN] = '\0';
173	n = getaddrinfo(tmp_host, port_str, &hints, &ai);
174	if(n != 0)
175	{
176#ifdef _WIN32
177		fprintf(stderr, "getaddrinfo() error : %d\n", n);
178#else
179		fprintf(stderr, "getaddrinfo() error : %s\n", gai_strerror(n));
180#endif
181		return -1;
182	}
183	s = -1;
184	for(p = ai; p; p = p->ai_next)
185	{
186		s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
187		if(s < 0)
188			continue;
189		if(p->ai_addr->sa_family == AF_INET6 && scope_id > 0) {
190			struct sockaddr_in6 * addr6 = (struct sockaddr_in6 *)p->ai_addr;
191			addr6->sin6_scope_id = scope_id;
192		}
193#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT
194		/* setting a 3 seconds timeout for the connect() call */
195		timeout.tv_sec = 3;
196		timeout.tv_usec = 0;
197		if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0)
198		{
199			PRINT_SOCKET_ERROR("setsockopt");
200		}
201		timeout.tv_sec = 3;
202		timeout.tv_usec = 0;
203		if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0)
204		{
205			PRINT_SOCKET_ERROR("setsockopt");
206		}
207#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */
208		n = connect(s, p->ai_addr, p->ai_addrlen);
209#ifdef MINIUPNPC_IGNORE_EINTR
210		/* EINTR The system call was interrupted by a signal that was caught
211		 * EINPROGRESS The socket is nonblocking and the connection cannot
212		 *             be completed immediately. */
213		while(n < 0 && (errno == EINTR || errno == EINPROGRESS))
214		{
215			socklen_t len;
216			fd_set wset;
217			int err;
218			FD_ZERO(&wset);
219			FD_SET(s, &wset);
220			if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR)
221				continue;
222			/*len = 0;*/
223			/*n = getpeername(s, NULL, &len);*/
224			len = sizeof(err);
225			if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
226				PRINT_SOCKET_ERROR("getsockopt");
227				closesocket(s);
228				freeaddrinfo(ai);
229				return -1;
230			}
231			if(err != 0) {
232				errno = err;
233				n = -1;
234			}
235		}
236#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */
237		if(n < 0)
238		{
239			closesocket(s);
240			continue;
241		}
242		else
243		{
244			break;
245		}
246	}
247	freeaddrinfo(ai);
248	if(s < 0)
249	{
250		PRINT_SOCKET_ERROR("socket");
251		return -1;
252	}
253	if(n < 0)
254	{
255		PRINT_SOCKET_ERROR("connect");
256		return -1;
257	}
258#endif /* #ifdef USE_GETHOSTBYNAME */
259	return s;
260}
261
262