summaryrefslogtreecommitdiffstats
path: root/wsrep-lib/wsrep-API/v26/examples/node/socket.c
blob: 377abcaf197c284b744f5a25f553600203e8ac46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/* Copyright (c) 2019, Codership Oy. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include "socket.h"

#include "log.h"

#include <assert.h>
#include <ctype.h>      // isspace()
#include <errno.h>
#include <limits.h>     // USHRT_MAX
#include <netdb.h>      // struct addrinfo
#include <stdio.h>      // snprintf()
#include <string.h>     // strerror()
#include <sys/socket.h> // bind(), connect(), accept(), send(), recv()

struct node_socket
{
    int fd;
};

/**
 * Initializes addrinfo from the separate host address and port arguments
 *
 * Requires calling freeaddrinfo() later
 *
 * @param[in] host - if NULL, will be initialized for listening
 * @param[in] port
 *
 * @return struct addrinfo* or NULL in case of error
 */
static struct addrinfo*
socket_get_addrinfo2(const char* const host,
                     uint16_t    const port)
{
    struct addrinfo const hints =
    {
        .ai_flags     = AI_PASSIVE |   /** will be ignored if host is not NULL */
                        AI_NUMERICSERV, /** service is a numeric port */
        .ai_family    = AF_UNSPEC,      /** either IPv4 or IPv6 */
        .ai_socktype  = SOCK_STREAM,    /** STREAM or DGRAM */
        .ai_protocol  = 0,
        .ai_addrlen   = 0,
        .ai_addr      = NULL,
        .ai_canonname = NULL,
        .ai_next      = NULL
    };

    char service[6];
    snprintf(service, sizeof(service), "%hu", port);

    struct addrinfo* info;
    int err = getaddrinfo(host, service, &hints, &info);
    if (err)
    {
        NODE_ERROR("Failed to resolve '%s': %d (%s)",
                   host, err, gai_strerror(err));
        return NULL;
    }

    return info;
}

/**
 * Initializes addrinfo from single address and port string
 * The port is expected to be in numerical form and appended to the host address
 * via colon.
 *
 * Requires calling freeaddrinfo() later
 *
 * @param[in] addr full address specification, including port
 *
 * @return struct addrinfo* or NULL in case of error
 */
static struct addrinfo*
socket_get_addrinfo1(const char* const addr)
{
    int   const addr_len = (int)strlen(addr);
    char* const addr_buf = strdup(addr);
    if (!addr_buf)
    {
        NODE_ERROR("strdup(%s) failed: %d (%s)", addr, errno, strerror(errno));
        return NULL;
    }

    struct addrinfo* res = NULL;
    long port;
    char* endptr;

    int i;
    for (i = addr_len - 1; i >= 0; i--)
    {
        if (addr_buf[i] == ':') break;
    }

    if (addr_buf[i] != ':')
    {
        NODE_ERROR("Malformed address:port string: '%s'", addr);
        goto end;
    }

    addr_buf[i] = '\0';
    port = strtol(addr_buf + i + 1, &endptr, 10);

    if (port <= 0 || port > USHRT_MAX || errno ||
        (*endptr != '\0' && !isspace(*endptr)))
    {
        NODE_ERROR("Malformed/invalid port: '%s'. Errno: %d (%s)",
                   addr_buf + i + 1, errno, strerror(errno));
        goto end;
    }

    res = socket_get_addrinfo2(strlen(addr_buf) > 0 ? addr_buf : NULL,
                               (uint16_t)port);
end:
    free(addr_buf);
    return res;
}

static struct node_socket*
socket_create(int const fd)
{
    assert(fd > 0);

    struct node_socket* res = calloc(1, sizeof(struct node_socket));
    if (res)
    {
        res->fd = fd;
    }
    else
    {
        NODE_ERROR("Failed to allocate struct node_socket: %d (%s)",
                   errno, strerror(errno));
        close(fd);
    }

    return res;
}

/**
 * Definition of function type with the signature of bind() and connect()
 */
typedef int (*socket_act_fun_t) (int                    sfd,
                                 const struct sockaddr* addr,
                                 socklen_t              addrlen);

static int
socket_bind_and_listen(int                    const sfd,
                       const struct sockaddr* const addr,
                       socklen_t              const addrlen)
{
    int ret = bind(sfd, addr, addrlen);

    if (!ret)
        ret = listen(sfd, SOMAXCONN);

    return ret;
}

/**
 * A "template" method to do the "right thing" with the addrinfo and create a
 * socket from it. The "right thing" would normally be bind and listen for
 * a server socket OR connect for a client socket.
 *
 * @param[in] info       addrinfo list, swallowed and deallocated
 * @param[in] action_fun the "right thing" to do on socket and struct sockaddr
 * @param[in] action_str action description to be printed in the error message
 * @param[in] orig_host  host address to be pronted in the error message
 * @param[in] orig_port  port to be printed in the error message, if orig_host
 *                       string contains the port, this parameter should be 0
 *
 * The last three parameters are for diagnostic puposes only. orig_host and
 * orig_port are supposed to be what were used to obtain addrinfo.
 *
 * @return new struct node_socket.
 */
static struct node_socket*
socket_from_addrinfo(struct addrinfo* const info,
                     socket_act_fun_t const action_fun,
                     const char*      const action_str,
                     const char*      const orig_host,
                     uint16_t         const orig_port)
{
    int sfd;
    int err = 0;

    /* Iterate over addrinfo list and try to apply action_fun on the resulting
     * socket. Once successful, break loop. */
    struct addrinfo* addr;
    for (addr = info; addr != NULL; addr = addr->ai_next)
    {
        sfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (sfd == -1)
        {
            err = errno;
            continue;
        }

        if (action_fun(sfd, addr->ai_addr, addr->ai_addrlen) == 0) break;

        err = errno;
        close(sfd);
    }

    freeaddrinfo(info); /* no longer needed */

    if (!addr)
    {
        NODE_ERROR("Failed to %s to '%s%s%.0hu': %d (%s)",
                   action_str,
                   orig_host ? orig_host : "", orig_port > 0 ? ":" : "",
                   orig_port > 0 ? orig_port : 0, /* won't be printed if 0 */
                   err, strerror(err));
        return NULL;
    }

    assert(sfd > 0);
    return socket_create(sfd);
}

struct node_socket*
node_socket_listen(const char* const host, uint16_t const port)
{
    struct addrinfo* const info = socket_get_addrinfo2(host, port);
    if (!info) return NULL;

    return socket_from_addrinfo(info, socket_bind_and_listen,
                                "bind a listening socket", host, port);
}

struct node_socket*
node_socket_connect(const char* const addr_str)
{
    struct addrinfo* const info = socket_get_addrinfo1(addr_str);
    if (!info) return NULL;

    return socket_from_addrinfo(info, connect, "connect", addr_str, 0);
}

struct node_socket*
node_socket_accept(struct node_socket* socket)
{
    int sfd = accept(socket->fd, NULL, NULL);

    if (sfd < 0)
    {
        NODE_ERROR("Failed to accept connection: %d (%s)",
                   errno, strerror(errno));
        return NULL;
    }

    return socket_create(sfd);
}

int
node_socket_send_bytes(node_socket_t* socket, const void* buf, size_t len)
{
    ssize_t const ret = send(socket->fd, buf, len, MSG_NOSIGNAL);

    if (ret != (ssize_t)len)
    {
        NODE_ERROR("Failed to send %zu bytes: %d (%s)", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int
node_socket_recv_bytes(node_socket_t* socket, void* buf, size_t len)
{
    ssize_t const ret = recv(socket->fd, buf, len, MSG_WAITALL);

    if (ret != (ssize_t)len)
    {
        NODE_ERROR("Failed to recv %zu bytes: %d (%s)", errno, strerror(errno));
        return -1;
    }

    return 0;
}

void
node_socket_close(node_socket_t* socket)
{
    if (!socket) return;

    if (socket->fd > 0) close(socket->fd);

    free(socket);
}