/src/gpsd/gpsd-3.25.1~dev/libgps/netlib.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is Copyright 2010 by the GPSD project |
3 | | * SPDX-License-Identifier: BSD-2-clause |
4 | | */ |
5 | | |
6 | | #include "../include/gpsd_config.h" // must be before all includes |
7 | | |
8 | | #ifdef HAVE_ARPA_INET_H |
9 | | # include <arpa/inet.h> // for htons() and friends |
10 | | #endif // HAVE_ARPA_INET_H |
11 | | #include <errno.h> // for errno |
12 | | #include <fcntl.h> |
13 | | #include <sys/types.h> // FreeBSD needs it for netinet/ip.h |
14 | | #ifdef HAVE_NETDB_H |
15 | | # include <netdb.h> |
16 | | #endif // HAVE_NETDB_H |
17 | | #ifndef INADDR_ANY |
18 | | # ifdef HAVE_NETINET_IN_H |
19 | | # include <netinet/in.h> |
20 | | # endif // HAVE_NETINET_IN_H |
21 | | #endif // INADDR_ANY |
22 | | #ifdef HAVE_NETINET_IN_H |
23 | | # include <netinet/ip.h> |
24 | | #endif // HAVE_NETINET_IN_H |
25 | | #include <string.h> |
26 | | #ifndef AF_UNSPEC |
27 | | # include <sys/stat.h> |
28 | | # ifdef HAVE_SYS_SOCKET_H |
29 | | # include <sys/socket.h> |
30 | | # endif // HAVE_SYS_SOCKET_H |
31 | | #endif // AF_UNSPEC |
32 | | #ifdef HAVE_SYS_UN_H |
33 | | # include <sys/un.h> |
34 | | #endif // HAVE_SYS_UN_H |
35 | | #include <unistd.h> |
36 | | #ifdef HAVE_WINSOCK2_H |
37 | | # include <winsock2.h> |
38 | | # include <ws2tcpip.h> |
39 | | #endif // HAVE_WINSOCK2_H |
40 | | |
41 | | #include "../include/gpsd.h" |
42 | | |
43 | | // work around the unfinished ipv6 implementation on hurd and OSX <10.6 |
44 | | #ifndef IPV6_TCLASS |
45 | | # if defined(__GNU__) |
46 | | # define IPV6_TCLASS 61 |
47 | | # elif defined(__APPLE__) |
48 | | # define IPV6_TCLASS 36 |
49 | | # endif |
50 | | #endif |
51 | | |
52 | | /* connect to host, using service (port) on protocol (TCP/UDP) |
53 | | * af - Adress Family |
54 | | * host - host to connect to |
55 | | * service -- aka port |
56 | | * protocol |
57 | | * nonblock -- 1 sets the socket as non-blocking before connect() if |
58 | | * SOCK_NONBLOCK is supported, |
59 | | * >1 sets the socket as non-blocking after connect() |
60 | | * bind_me -- call bind() on the socket instead of connect() |
61 | | * addrbuf -- 50 char buf to put string of IP address conencting |
62 | | * INET6_ADDRSTRLEN |
63 | | * addrbuf_sz -- sizeof(adddrbuf) |
64 | | * |
65 | | * Notes on nonblocking: |
66 | | * The connect may be non-blocking, but the DNS lookup is blocking. |
67 | | * On non-blocking connect only the first DNS entry is ever used. |
68 | | * FIXME: cache DNS to avoid DNS lookup on re-connect |
69 | | * |
70 | | * return socket on success |
71 | | * less than zero on error (NL_*) |
72 | | */ |
73 | | socket_t netlib_connectsock1(int af, const char *host, const char *service, |
74 | | const char *protocol, int nonblock, bool bind_me, |
75 | | char *addrbuf, size_t addrbuf_sz) |
76 | 0 | { |
77 | 0 | struct protoent *ppe; |
78 | 0 | struct addrinfo hints = {0}; |
79 | 0 | struct addrinfo *result = NULL; |
80 | 0 | struct addrinfo *rp; |
81 | 0 | int ret, flags, type, proto, one; |
82 | 0 | socket_t s; |
83 | |
|
84 | 0 | if (NULL != addrbuf) { |
85 | 0 | addrbuf[0] = '\0'; |
86 | 0 | } |
87 | 0 | INVALIDATE_SOCKET(s); |
88 | 0 | ppe = getprotobyname(protocol); |
89 | 0 | if (0 == strcmp(protocol, "udp")) { |
90 | 0 | type = SOCK_DGRAM; |
91 | 0 | proto = (ppe) ? ppe->p_proto : IPPROTO_UDP; |
92 | 0 | } else if (0 == strcmp(protocol, "tcp")) { |
93 | 0 | type = SOCK_STREAM; |
94 | 0 | proto = (ppe) ? ppe->p_proto : IPPROTO_TCP; |
95 | 0 | } else { |
96 | | // Unknown protocol (sctp, etc.) |
97 | 0 | return NL_NOPROTO; |
98 | 0 | } |
99 | | |
100 | 0 | hints.ai_family = af; |
101 | 0 | hints.ai_socktype = type; |
102 | 0 | hints.ai_protocol = proto; |
103 | 0 | if (bind_me) { |
104 | 0 | hints.ai_flags = AI_PASSIVE; |
105 | 0 | } |
106 | 0 | #if defined(SOCK_NONBLOCK) |
107 | 0 | flags = nonblock == 1 ? SOCK_NONBLOCK : 0; |
108 | | #else |
109 | | // macOS has no SOCK_NONBLOCK |
110 | | flags = 0; |
111 | | if (1 == nonblock) { |
112 | | nonblock = 2; |
113 | | } |
114 | | #endif |
115 | | |
116 | | // FIXME: need a way to bypass these DNS calls if host is an IP. |
117 | 0 | if ((ret = getaddrinfo(host, service, &hints, &result))) { |
118 | | // result is unchanged on error, so we need to have set it to NULL |
119 | | // freeaddrinfo() checks for NULL, the NULL we provided. |
120 | 0 | freeaddrinfo(result); |
121 | 0 | result = NULL; |
122 | | // quick check to see if the problem was host or service |
123 | 0 | ret = getaddrinfo(NULL, service, &hints, &result); |
124 | 0 | freeaddrinfo(result); |
125 | 0 | if (ret) { |
126 | 0 | return NL_NOSERVICE; |
127 | 0 | } |
128 | 0 | return NL_NOHOST; |
129 | 0 | } |
130 | | |
131 | | /* |
132 | | * Try to connect to each of the DNS returned addresses, one at a time. |
133 | | * Until success, or no more addresses. |
134 | | * |
135 | | * From getaddrinfo(3): |
136 | | * Normally, the application should try using the addresses in the |
137 | | * order in which they are returned. The sorting function used within |
138 | | * getaddrinfo() is defined in RFC 3484). |
139 | | * From RFC 3484 (Section 10.3): |
140 | | * The default policy table gives IPv6 addresses higher precedence than |
141 | | * IPv4 addresses. |
142 | | * Thus, with the default parameters, we get IPv6 addresses first. |
143 | | */ |
144 | 0 | for (rp = result; NULL != rp; rp = rp->ai_next) { |
145 | 0 | ret = NL_NOCONNECT; |
146 | | // flags might be zero or SOCK_NONBLOCK |
147 | 0 | s = socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol); |
148 | 0 | if (0 > s) { |
149 | | // can't get a socket. Maybe should give up right away? |
150 | 0 | ret = NL_NOSOCK; |
151 | 0 | continue; |
152 | 0 | } |
153 | | // allow reuse of local address is in TIMEWAIT state |
154 | | // useful on a quick gpsd restart to reuse the address. |
155 | 0 | one = 1; |
156 | 0 | if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&one, |
157 | 0 | sizeof(one))) { |
158 | 0 | ret = NL_NOSOCKOPT; |
159 | 0 | } else if (bind_me) { |
160 | | // want a passive socket (SOCK_DGRAM), UDP. |
161 | 0 | if (0 == bind(s, rp->ai_addr, rp->ai_addrlen)) { |
162 | | // got a good one |
163 | 0 | ret = 0; |
164 | 0 | break; |
165 | 0 | } |
166 | 0 | } else if (0 == connect(s, rp->ai_addr, rp->ai_addrlen)) { |
167 | | // got a good connection |
168 | 0 | ret = 0; |
169 | 0 | break; |
170 | 0 | } else if (EINPROGRESS == errno) { |
171 | | // EINPROGRESS means non-blocking connect() in progress |
172 | | // we will not try next address... |
173 | | // separate case from 0 == connect() to ease debug. |
174 | 0 | ret = 0; |
175 | 0 | break; |
176 | 0 | } |
177 | 0 | if (NULL != addrbuf) { |
178 | | // save the IP used, as a string. into addrbuf |
179 | 0 | if (NULL == inet_ntop(af, rp->ai_addr, addrbuf, addrbuf_sz)) { |
180 | 0 | addrbuf[0] = '\0'; |
181 | 0 | } |
182 | 0 | } |
183 | |
|
184 | 0 | if (!BAD_SOCKET(s)) { |
185 | | #ifdef HAVE_WINSOCK2_H |
186 | | (void)closesocket(s); |
187 | | #else |
188 | 0 | (void)close(s); |
189 | 0 | #endif |
190 | 0 | } |
191 | 0 | } |
192 | 0 | freeaddrinfo(result); |
193 | 0 | if (0 != ret || |
194 | 0 | BAD_SOCKET(s)) { |
195 | 0 | return ret; |
196 | 0 | } |
197 | | |
198 | 0 | #ifdef IPTOS_LOWDELAY |
199 | 0 | { |
200 | 0 | int opt = IPTOS_LOWDELAY; |
201 | |
|
202 | 0 | (void)setsockopt(s, IPPROTO_IP, IP_TOS, &opt, sizeof(opt)); |
203 | 0 | #ifdef IPV6_TCLASS |
204 | 0 | (void)setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &opt, sizeof(opt)); |
205 | 0 | #endif |
206 | 0 | } |
207 | 0 | #endif |
208 | | #ifdef TCP_NODELAY |
209 | | /* |
210 | | * This is a good performance enhancement when the socket is going to |
211 | | * be used to pass a lot of short commands. It prevents them from being |
212 | | * delayed by the Nagle algorithm until they can be aggreagated into |
213 | | * a large packet. See https://en.wikipedia.org/wiki/Nagle%27s_algorithm |
214 | | * for discussion. |
215 | | */ |
216 | | if (SOCK_STREAM == type) { |
217 | | one = 1; |
218 | | (void)setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&one, |
219 | | sizeof(one)); |
220 | | } |
221 | | #endif |
222 | 0 | if (SOCK_STREAM == type) { |
223 | | // Set keepalive on TCP connections. Maybe detect disconnects better. |
224 | 0 | one = 1; |
225 | 0 | (void)setsockopt(s, IPPROTO_TCP, SO_KEEPALIVE, (char *)&one, |
226 | 0 | sizeof(one)); |
227 | 0 | } |
228 | |
|
229 | 0 | if (1 < nonblock) { |
230 | | // set socket to noblocking |
231 | 0 | #ifdef HAVE_FCNTL |
232 | 0 | (void)fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); |
233 | | #elif defined(HAVE_WINSOCK2_H) |
234 | | u_long one1 = 1; |
235 | | (void)ioctlsocket(s, FIONBIO, &one1); |
236 | | #endif |
237 | 0 | } |
238 | 0 | return s; |
239 | 0 | } |
240 | | |
241 | | // legacy entry point |
242 | | // just call netlib_connectsock() with options = 0; |
243 | | // return the result |
244 | | socket_t netlib_connectsock(int af, const char *host, const char *service, |
245 | | const char *protocol) |
246 | 0 | { |
247 | 0 | return netlib_connectsock1(af, host, service, protocol, 2, false, NULL, 0); |
248 | 0 | } |
249 | | |
250 | | // Convert NL_* error code to a string |
251 | | const char *netlib_errstr(const int err) |
252 | 0 | { |
253 | 0 | switch (err) { |
254 | 0 | case NL_NOSERVICE: |
255 | 0 | return "can't get service entry"; |
256 | 0 | case NL_NOHOST: |
257 | 0 | return "can't get host entry"; |
258 | 0 | case NL_NOPROTO: |
259 | 0 | return "can't get protocol entry"; |
260 | 0 | case NL_NOSOCK: |
261 | 0 | return "can't create socket"; |
262 | 0 | case NL_NOSOCKOPT: |
263 | 0 | return "error SETSOCKOPT SO_REUSEADDR"; |
264 | 0 | case NL_NOCONNECT: |
265 | 0 | return "can't connect to host/port pair"; |
266 | 0 | default: |
267 | 0 | break; |
268 | 0 | } |
269 | 0 | return "unknown error"; |
270 | 0 | } |
271 | | |
272 | | // acquire a connection to an existing Unix-domain socket |
273 | | socket_t netlib_localsocket(const char *sockfile, int socktype) |
274 | 0 | { |
275 | 0 | #ifdef HAVE_SYS_UN_H |
276 | 0 | int sock; |
277 | 0 | struct sockaddr_un saddr = {0}; |
278 | |
|
279 | 0 | if (0 > (sock = socket(AF_UNIX, socktype, 0))) { |
280 | 0 | return -1; |
281 | 0 | } // else |
282 | | |
283 | 0 | saddr.sun_family = AF_UNIX; |
284 | 0 | (void)strlcpy(saddr.sun_path, sockfile, sizeof(saddr.sun_path)); |
285 | |
|
286 | 0 | if (0 < connect(sock, (struct sockaddr *)&saddr, SUN_LEN(&saddr))) { |
287 | 0 | (void)close(sock); |
288 | 0 | return -2; |
289 | 0 | } // else |
290 | | |
291 | 0 | return sock; |
292 | | #else |
293 | | return -1; |
294 | | #endif // HAVE_SYS_UN_H |
295 | 0 | } |
296 | | |
297 | | // socka2a() -- convert fsin to ascii address |
298 | | char *socka2a(sockaddr_t *fsin, char *buf, size_t buflen) |
299 | 0 | { |
300 | 0 | const char *r; |
301 | |
|
302 | 0 | switch (fsin->sa.sa_family) { |
303 | 0 | case AF_INET: |
304 | 0 | FALLTHROUGH |
305 | 0 | case AF_INET6: |
306 | 0 | r = inet_ntop(fsin->sa.sa_family, &(fsin->sa_in.sin_addr), |
307 | 0 | buf, buflen); |
308 | 0 | break; |
309 | 0 | default: |
310 | 0 | (void)strlcpy(buf, "<unknown AF>", buflen); |
311 | 0 | r = buf; |
312 | 0 | } |
313 | 0 | if (NULL == r) { |
314 | 0 | (void)strlcpy(buf, "<error>", buflen); |
315 | 0 | } |
316 | 0 | return buf; |
317 | 0 | } |
318 | | |
319 | | // retrieve the IP address corresponding to a socket |
320 | | char *netlib_sock2ip(socket_t fd) |
321 | 0 | { |
322 | 0 | static char ip[INET6_ADDRSTRLEN]; |
323 | 0 | int r = 0; |
324 | 0 | sockaddr_t fsin; |
325 | 0 | socklen_t alen = (socklen_t) sizeof(fsin); |
326 | |
|
327 | 0 | r = getpeername(fd, &(fsin.sa), &alen); |
328 | 0 | if (0 == r) { |
329 | 0 | socka2a(&fsin, ip, sizeof(ip)); |
330 | 0 | } else { |
331 | 0 | (void)strlcpy(ip, "<unknown>", sizeof(ip)); |
332 | 0 | } |
333 | 0 | return ip; |
334 | 0 | } |
335 | | // vim: set expandtab shiftwidth=4 |