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