/src/gpsd/gpsd-3.26.2~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 - Address 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 connecting |
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 | 0 | if (NULL != result) { |
119 | | /* Free the space getaddrinfo() allocated, if any. |
120 | | * glibc can handle freeaddrinfo(NULL), |
121 | | * but musl 1.2.5 (2024), and earlier, can not. */ |
122 | 0 | freeaddrinfo(result); |
123 | 0 | } |
124 | 0 | result = NULL; |
125 | | // quick check to see if the problem was host or service |
126 | 0 | ret = getaddrinfo(NULL, service, &hints, &result); |
127 | 0 | if (NULL != result) { |
128 | | // Free the space getaddrinfo() allocated, if any. |
129 | 0 | freeaddrinfo(result); |
130 | 0 | } |
131 | 0 | if (ret) { |
132 | 0 | return NL_NOSERVICE; |
133 | 0 | } |
134 | 0 | return NL_NOHOST; |
135 | 0 | } |
136 | | |
137 | | /* |
138 | | * Try to connect to each of the DNS returned addresses, one at a time. |
139 | | * Until success, or no more addresses. |
140 | | * |
141 | | * From getaddrinfo(3): |
142 | | * Normally, the application should try using the addresses in the |
143 | | * order in which they are returned. The sorting function used within |
144 | | * getaddrinfo() is defined in RFC 3484). |
145 | | * From RFC 3484 (Section 10.3): |
146 | | * The default policy table gives IPv6 addresses higher precedence than |
147 | | * IPv4 addresses. |
148 | | * Thus, with the default parameters, we get IPv6 addresses first. |
149 | | */ |
150 | 0 | for (rp = result; NULL != rp; rp = rp->ai_next) { |
151 | 0 | ret = NL_NOCONNECT; |
152 | | // flags might be zero or SOCK_NONBLOCK |
153 | 0 | s = socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol); |
154 | 0 | if (0 > s) { |
155 | | // can't get a socket. Maybe should give up right away? |
156 | 0 | ret = NL_NOSOCK; |
157 | 0 | continue; |
158 | 0 | } |
159 | | // allow reuse of local address is in TIMEWAIT state |
160 | | // useful on a quick gpsd restart to reuse the address. |
161 | 0 | one = 1; |
162 | 0 | if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&one, |
163 | 0 | sizeof(one))) { |
164 | 0 | ret = NL_NOSOCKOPT; |
165 | 0 | } else if (bind_me) { |
166 | | // want a passive socket (SOCK_DGRAM), UDP. |
167 | 0 | if (0 == bind(s, rp->ai_addr, rp->ai_addrlen)) { |
168 | | // got a good one |
169 | 0 | ret = 0; |
170 | 0 | break; |
171 | 0 | } |
172 | 0 | } else if (0 == connect(s, rp->ai_addr, rp->ai_addrlen)) { |
173 | | // got a good connection |
174 | 0 | ret = 0; |
175 | 0 | break; |
176 | 0 | } else if (EINPROGRESS == errno) { |
177 | | // EINPROGRESS means non-blocking connect() in progress |
178 | | // we will not try next address... |
179 | | // separate case from 0 == connect() to ease debug. |
180 | 0 | ret = 0; |
181 | 0 | break; |
182 | 0 | } |
183 | 0 | if (NULL != addrbuf) { |
184 | | // save the IP used, as a string. into addrbuf |
185 | 0 | if (NULL == inet_ntop(af, rp->ai_addr, addrbuf, addrbuf_sz)) { |
186 | 0 | addrbuf[0] = '\0'; |
187 | 0 | } |
188 | 0 | } |
189 | |
|
190 | 0 | if (!BAD_SOCKET(s)) { |
191 | | #ifdef HAVE_WINSOCK2_H |
192 | | (void)closesocket(s); |
193 | | #else |
194 | 0 | (void)close(s); |
195 | 0 | #endif |
196 | 0 | } |
197 | 0 | } |
198 | 0 | if (NULL != result) { |
199 | | // Free the space getaddrinfo() allocated, if any. |
200 | 0 | freeaddrinfo(result); |
201 | 0 | } |
202 | 0 | if (0 != ret || |
203 | 0 | BAD_SOCKET(s)) { |
204 | 0 | return ret; |
205 | 0 | } |
206 | | |
207 | 0 | #ifdef IPTOS_LOWDELAY |
208 | 0 | { |
209 | 0 | int opt = IPTOS_LOWDELAY; |
210 | |
|
211 | 0 | (void)setsockopt(s, IPPROTO_IP, IP_TOS, &opt, sizeof(opt)); |
212 | 0 | #ifdef IPV6_TCLASS |
213 | 0 | (void)setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &opt, sizeof(opt)); |
214 | 0 | #endif |
215 | 0 | } |
216 | 0 | #endif |
217 | | #ifdef TCP_NODELAY |
218 | | /* |
219 | | * This is a good performance enhancement when the socket is going to |
220 | | * be used to pass a lot of short commands. It prevents them from being |
221 | | * delayed by the Nagle algorithm until they can be aggregated into |
222 | | * a large packet. See https://en.wikipedia.org/wiki/Nagle%27s_algorithm |
223 | | * for discussion. |
224 | | */ |
225 | | if (SOCK_STREAM == type) { |
226 | | one = 1; |
227 | | (void)setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&one, |
228 | | sizeof(one)); |
229 | | } |
230 | | #endif |
231 | 0 | if (SOCK_STREAM == type) { |
232 | | // Set keepalive on TCP connections. Maybe detect disconnects better. |
233 | 0 | one = 1; |
234 | 0 | (void)setsockopt(s, IPPROTO_TCP, SO_KEEPALIVE, (char *)&one, |
235 | 0 | sizeof(one)); |
236 | 0 | } |
237 | |
|
238 | 0 | if (1 < nonblock) { |
239 | | // set socket to noblocking |
240 | 0 | #ifdef HAVE_FCNTL |
241 | 0 | (void)fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); |
242 | | #elif defined(HAVE_WINSOCK2_H) |
243 | | u_long one1 = 1; |
244 | | (void)ioctlsocket(s, FIONBIO, &one1); |
245 | | #endif |
246 | 0 | } |
247 | 0 | return s; |
248 | 0 | } |
249 | | |
250 | | // legacy entry point |
251 | | // just call netlib_connectsock() with options = 0; |
252 | | // return the result |
253 | | socket_t netlib_connectsock(int af, const char *host, const char *service, |
254 | | const char *protocol) |
255 | 0 | { |
256 | 0 | return netlib_connectsock1(af, host, service, protocol, 2, false, NULL, 0); |
257 | 0 | } |
258 | | |
259 | | // Convert NL_* error code to a string |
260 | | const char *netlib_errstr(const int err) |
261 | 0 | { |
262 | 0 | switch (err) { |
263 | 0 | case NL_NOSERVICE: |
264 | 0 | return "can't get service entry"; |
265 | 0 | case NL_NOHOST: |
266 | 0 | return "can't get host entry"; |
267 | 0 | case NL_NOPROTO: |
268 | 0 | return "can't get protocol entry"; |
269 | 0 | case NL_NOSOCK: |
270 | 0 | return "can't create socket"; |
271 | 0 | case NL_NOSOCKOPT: |
272 | 0 | return "error SETSOCKOPT SO_REUSEADDR"; |
273 | 0 | case NL_NOCONNECT: |
274 | 0 | return "can't connect to host/port pair"; |
275 | 0 | default: |
276 | 0 | break; |
277 | 0 | } |
278 | 0 | return "unknown error"; |
279 | 0 | } |
280 | | |
281 | | // acquire a connection to an existing Unix-domain socket |
282 | | socket_t netlib_localsocket(const char *sockfile, int socktype) |
283 | 0 | { |
284 | 0 | #ifdef HAVE_SYS_UN_H |
285 | 0 | int sock; |
286 | 0 | struct sockaddr_un saddr = {0}; |
287 | |
|
288 | 0 | if (0 > (sock = socket(AF_UNIX, socktype, 0))) { |
289 | 0 | return -1; |
290 | 0 | } // else |
291 | | |
292 | 0 | saddr.sun_family = AF_UNIX; |
293 | 0 | (void)strlcpy(saddr.sun_path, sockfile, sizeof(saddr.sun_path)); |
294 | |
|
295 | 0 | if (0 < connect(sock, (struct sockaddr *)&saddr, SUN_LEN(&saddr))) { |
296 | 0 | (void)close(sock); |
297 | 0 | return -2; |
298 | 0 | } // else |
299 | | |
300 | 0 | return sock; |
301 | | #else |
302 | | return -1; |
303 | | #endif // HAVE_SYS_UN_H |
304 | 0 | } |
305 | | |
306 | | // socka2a() -- convert fsin to ascii address |
307 | | char *socka2a(sockaddr_t *fsin, char *buf, size_t buflen) |
308 | 0 | { |
309 | 0 | const char *r; |
310 | |
|
311 | 0 | switch (fsin->sa.sa_family) { |
312 | 0 | case AF_INET: |
313 | 0 | FALLTHROUGH |
314 | 0 | case AF_INET6: |
315 | 0 | r = inet_ntop(fsin->sa.sa_family, &(fsin->sa_in.sin_addr), |
316 | 0 | buf, buflen); |
317 | 0 | break; |
318 | 0 | default: |
319 | 0 | (void)strlcpy(buf, "<unknown AF>", buflen); |
320 | 0 | r = buf; |
321 | 0 | } |
322 | 0 | if (NULL == r) { |
323 | 0 | (void)strlcpy(buf, "<error>", buflen); |
324 | 0 | } |
325 | 0 | return buf; |
326 | 0 | } |
327 | | |
328 | | // retrieve the IP address corresponding to a socket |
329 | | char *netlib_sock2ip(socket_t fd) |
330 | 0 | { |
331 | 0 | static char ip[INET6_ADDRSTRLEN]; |
332 | 0 | int r = 0; |
333 | 0 | sockaddr_t fsin; |
334 | 0 | socklen_t alen = (socklen_t) sizeof(fsin); |
335 | |
|
336 | 0 | r = getpeername(fd, &(fsin.sa), &alen); |
337 | 0 | if (0 == r) { |
338 | 0 | socka2a(&fsin, ip, sizeof(ip)); |
339 | 0 | } else { |
340 | 0 | (void)strlcpy(ip, "<unknown>", sizeof(ip)); |
341 | 0 | } |
342 | 0 | return ip; |
343 | 0 | } |
344 | | // vim: set expandtab shiftwidth=4 |