1/*
2** Copyright 2006, The Android Open Source Project
3**
4** Licensed under the Apache License, Version 2.0 (the "License");
5** you may not use this file except in compliance with the License.
6** You may obtain a copy of the License at
7**
8**     http://www.apache.org/licenses/LICENSE-2.0
9**
10** Unless required by applicable law or agreed to in writing, software
11** distributed under the License is distributed on an "AS IS" BASIS,
12** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13** See the License for the specific language governing permissions and
14** limitations under the License.
15*/
16
17#include <errno.h>
18#include <fcntl.h>
19#include <stddef.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23
24#include <sys/socket.h>
25#include <sys/select.h>
26#include <sys/types.h>
27#include <netinet/in.h>
28#include <netdb.h>
29
30#include <cutils/sockets.h>
31
32static int toggle_O_NONBLOCK(int s) {
33    int flags = fcntl(s, F_GETFL);
34    if (flags == -1 || fcntl(s, F_SETFL, flags ^ O_NONBLOCK) == -1) {
35        close(s);
36        return -1;
37    }
38    return s;
39}
40
41// Connect to the given host and port.
42// 'timeout' is in seconds (0 for no timeout).
43// Returns a file descriptor or -1 on error.
44// On error, check *getaddrinfo_error (for use with gai_strerror) first;
45// if that's 0, use errno instead.
46int socket_network_client_timeout(const char* host, int port, int type, int timeout,
47                                  int* getaddrinfo_error) {
48    struct addrinfo hints;
49    memset(&hints, 0, sizeof(hints));
50    hints.ai_family = AF_UNSPEC;
51    hints.ai_socktype = type;
52
53    char port_str[16];
54    snprintf(port_str, sizeof(port_str), "%d", port);
55
56    struct addrinfo* addrs;
57    *getaddrinfo_error = getaddrinfo(host, port_str, &hints, &addrs);
58    if (*getaddrinfo_error != 0) {
59        return -1;
60    }
61
62    int result = -1;
63    for (struct addrinfo* addr = addrs; addr != NULL; addr = addr->ai_next) {
64        // The Mac doesn't have SOCK_NONBLOCK.
65        int s = socket(addr->ai_family, type, addr->ai_protocol);
66        if (s == -1 || toggle_O_NONBLOCK(s) == -1) return -1;
67
68        int rc = connect(s, addr->ai_addr, addr->ai_addrlen);
69        if (rc == 0) {
70            result = toggle_O_NONBLOCK(s);
71            break;
72        } else if (rc == -1 && errno != EINPROGRESS) {
73            close(s);
74            continue;
75        }
76
77        fd_set r_set;
78        FD_ZERO(&r_set);
79        FD_SET(s, &r_set);
80        fd_set w_set = r_set;
81
82        struct timeval ts;
83        ts.tv_sec = timeout;
84        ts.tv_usec = 0;
85        if ((rc = select(s + 1, &r_set, &w_set, NULL, (timeout != 0) ? &ts : NULL)) == -1) {
86            close(s);
87            break;
88        }
89        if (rc == 0) {  // we had a timeout
90            errno = ETIMEDOUT;
91            close(s);
92            break;
93        }
94
95        int error = 0;
96        socklen_t len = sizeof(error);
97        if (FD_ISSET(s, &r_set) || FD_ISSET(s, &w_set)) {
98            if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
99                close(s);
100                break;
101            }
102        } else {
103            close(s);
104            break;
105        }
106
107        if (error) {  // check if we had a socket error
108            // TODO: Update the timeout.
109            errno = error;
110            close(s);
111            continue;
112        }
113
114        result = toggle_O_NONBLOCK(s);
115        break;
116    }
117
118    freeaddrinfo(addrs);
119    return result;
120}
121
122int socket_network_client(const char* host, int port, int type) {
123    int getaddrinfo_error;
124    return socket_network_client_timeout(host, port, type, 0, &getaddrinfo_error);
125}
126