Coverage Report

Created: 2024-10-20 06:20

/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