source: branches/profile/CORE/arb_cs.cxx

Last change on this file was 12885, checked in by westram, 10 years ago
  • get rid of TEST_open_socket failures under jenkins
File size: 13.2 KB
Line 
1// ============================================================= //
2//                                                               //
3//   File      : arb_cs.cxx                                      //
4//   Purpose   : Basics for client/server communication          //
5//                                                               //
6//   Coded by Ralf Westram (coder@reallysoft.de) in March 2011   //
7//   Institute of Microbiology (Technical University Munich)     //
8//   http://www.arb-home.de/                                     //
9//                                                               //
10// ============================================================= //
11
12#include "arb_cs.h"
13#include "arb_msg.h"
14#include "arb_pattern.h"
15#include <smartptr.h>
16
17#include <unistd.h>
18#include <netdb.h>
19#include <sys/types.h>
20#include <sys/socket.h>
21#include <sys/un.h>
22#include <sys/stat.h>
23#include <netinet/tcp.h>
24
25// We need one of the below to prevent SIGPIPE on writes to
26// closed socket. For systems that have neither (Solaris),
27// we'd need to implement ignoring the signal in the write
28// loop (not done).
29#ifndef SO_NOSIGPIPE
30#ifndef MSG_NOSIGNAL
31#error Neither SO_NOSIGPIPE nor MSG_NOSIGNAL available!
32#endif
33#endif
34
35void arb_gethostbyname(const char *name, struct hostent *& he, GB_ERROR& err) {
36    he = gethostbyname(name);
37    // Note: gethostbyname is marked obsolete.
38    // replacement getnameinfo seems less portable atm.
39
40    if (he) {
41        err = NULL;
42    }
43    else {
44        err = GBS_global_string("Cannot resolve hostname: '%s' (h_errno=%i='%s')",
45                                name, h_errno, hstrerror(h_errno));
46    }
47}
48
49const char *arb_gethostname() {
50    static SmartCharPtr hostname;
51    if (hostname.isNull()) {
52        char buffer[4096];
53        gethostname(buffer, 4095);
54        hostname = strdup(buffer);
55    }
56    return &*hostname;
57}
58
59size_t arb_socket_read(int socket, char* ptr, size_t size) {
60    size_t to_read = size;
61    while(to_read) {
62        ssize_t read_len = read(socket, ptr, to_read);
63        if (read_len <= 0) { // read failed
64            // FIXME: GB_export_error!
65            return 0;
66        }
67        ptr += read_len;
68        to_read -= read_len;
69    }
70    return size;
71}
72
73ssize_t arb_socket_write(int socket, const char* ptr, size_t size) {
74    size_t to_write = size;
75
76    while (to_write) {
77#ifdef MSG_NOSIGNAL
78        // Linux has MSG_NOSIGNAL, but not SO_NOSIGPIPE
79        // prevent SIGPIPE here
80        ssize_t write_len = send(socket, ptr, to_write, MSG_NOSIGNAL);
81#else
82        ssize_t write_len = write(socket, ptr, to_write);
83#endif
84        if (write_len <= 0) { // write failed
85            if (errno == EPIPE) {
86                fputs("pipe broken\n", stderr);
87            }
88
89            // FIXME: GB_export_error!
90            return -1;
91        }
92        ptr += write_len;
93        to_write -= write_len;
94    }
95    return 0;
96}
97
98static GB_ERROR arb_open_unix_socket(char* name, bool do_connect, int *fd);
99static GB_ERROR arb_open_tcp_socket(char* name, bool do_connect, int *fd);
100
101/**
102 * Opens and prepares a socket
103 *
104 * If @param name begins with ":", the remainder is shell expanded and
105 * a unix socket is created. If @param contains no ":" it must be numeric,
106 * giving the TCPport number to open. If @param contains a ":" in the middle,
107 * the first part is considered the hostname and the latter part the port.
108 *
109 * @param  name          name of port   {[<host>:]<port>|:<filename>}
110 * @param  do_connect    connect if true (client), otherwise bind (server)
111 * @param  fd            file descriptor of opened socket (out)
112 * @param  filename_out  filename of unix socket (out)
113 *                       must be NULL or allocated (will be freed)
114 *
115 * @result NULL if all went fine
116 *         "" if could not connect
117 *         otherwise error message
118 */
119GB_ERROR arb_open_socket(const char* name, bool do_connect, int *fd, char** filename_out) {
120    if (!name || strlen(name) == 0) {
121        return "Error opening socket: empty name";
122    }
123
124    GB_ERROR error;
125    if (name[0] == ':') {
126        // expand variables in path
127        char *filename = arb_shell_expand(name+1);
128        if (GB_have_error()) {
129            return GB_await_error();
130        } 
131
132        error = arb_open_unix_socket(filename, do_connect, fd);
133        if (error) {
134            free(filename);
135        } else {
136            reassign(*filename_out, filename);
137        }
138    } 
139    else {
140        char *socket_name = strdup(name);
141        error = arb_open_tcp_socket(socket_name, do_connect, fd);
142        free(socket_name);
143        freenull(*filename_out);
144    }
145           
146    return error;
147}
148
149static GB_ERROR arb_open_unix_socket(char* filename, bool do_connect, int *fd) {   
150    // create structure for connect/bind
151    sockaddr_un unix_socket;
152    unix_socket.sun_family = AF_UNIX;
153    if (strlen(filename)+1 > sizeof(unix_socket.sun_path)) {
154        return GBS_global_string("Failed to create unix socket: "
155                                 "\"%s\" is longer than the allowed %li characters",
156                                 filename, sizeof(unix_socket.sun_path));
157    }
158    strncpy(unix_socket.sun_path, filename, sizeof(unix_socket.sun_path));
159   
160    // create socket
161    *fd = socket(PF_UNIX, SOCK_STREAM, 0);
162    if (*fd < 0) {
163        return GBS_global_string("Failed to create unix socket: %s", strerror(errno));
164    }
165   
166    // connect or bind socket
167    if (do_connect) {
168        if (connect(*fd, (sockaddr*)&unix_socket, sizeof(sockaddr_un))) {
169            if (errno == ECONNREFUSED || errno == ENOENT) {
170                return "";
171            } else {
172                return GBS_global_string("Failed to connect unix socket \"%s\": %s (%i)",
173                                         filename, strerror(errno), errno);
174            }
175        }
176    }
177    else {
178        struct stat stt;
179        if (!stat(filename, &stt)) {
180            if (!S_ISSOCK(stt.st_mode)) {
181                return GBS_global_string("Failed to create unix socket at \"%s\": file exists"
182                                         " and is not a socket", filename);
183            }
184            if (unlink(filename)) {
185                return GBS_global_string("Failed to create unix socket at \"%s\": cannot remove"
186                                         " existing socket", filename);
187            }
188        }
189        if (bind(*fd, (sockaddr*)&unix_socket, sizeof(sockaddr_un))) {
190            return  GBS_global_string("Failed to bind unix socket \"%s\": %s",
191                                      filename, strerror(errno));
192        }
193    }
194
195#ifdef SO_NOSIGPIPE
196    // OSX has SO_NOSIGPIPE but not MSG_NOSIGNAL
197    // prevent SIGPIPE here:
198    int one = 1;
199    if (setsockopt(*fd, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&one, sizeof(one))){
200        fprintf(stderr, "Warning: setsockopt(...NOSIGPIPE...) failed: %s", strerror(errno));
201    }
202#endif
203   
204
205    return NULL;
206}
207
208static GB_ERROR arb_open_tcp_socket(char* name, bool do_connect, int *fd) {
209    GB_ERROR error = NULL;
210
211    // create socket
212    *fd = socket(PF_INET, SOCK_STREAM, 0);
213    if (*fd < 0) {
214        return GBS_global_string("Failed to create tcp socket: %s", strerror(errno));
215    }
216
217    // create sockaddr struct
218    sockaddr_in tcp_socket;
219    tcp_socket.sin_family = AF_INET;
220
221    struct hostent *he;   
222    // get port and host
223    char *p = strchr(name, ':');
224    if (!p) { // <port>
225        tcp_socket.sin_port = htons(atoi(name));
226        arb_gethostbyname(arb_gethostname(), he, error);
227    }
228    else { // <host>:<port>
229        tcp_socket.sin_port = htons(atoi(p+1));
230        p[0]='\0';
231        arb_gethostbyname(name, he, error);
232        p[0]=':';
233    }
234    if (tcp_socket.sin_port == 0) {
235        return "Cannot open tcp socket on port 0. Is the port name malformed?";
236    }
237    if (error) {
238        return error;
239    }
240    memcpy(&tcp_socket.sin_addr, he->h_addr_list[0], he->h_length);
241
242    int one = 1;
243    if (do_connect) {
244        if (connect(*fd, (sockaddr*)&tcp_socket, sizeof(tcp_socket))) {
245            if (errno == ECONNREFUSED) {
246                return "";
247            } else {
248                return GBS_global_string("Failed to connect TCP socket \"%s\": %s",
249                                     name, strerror(errno));
250            }
251        }
252    }
253    else { // no connect (bind)
254        if (setsockopt(*fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
255            fprintf(stderr, "Warning: setsockopt(...REUSEADDR...) failed: %s", strerror(errno));
256        }
257        if (bind(*fd, (sockaddr*)&tcp_socket, sizeof(tcp_socket))) {
258            return GBS_global_string("Failed to bind TCP socket \"%s\": %s",
259                                     name, strerror(errno));
260        }
261    }
262   
263    if (setsockopt(*fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one))) {
264        fprintf(stderr, "Warning: setsockopt(...TCP_NODELAY...) failed: %s", strerror(errno));
265    }
266
267    return NULL;
268}
269
270////////// UNIT TESTS ///////////
271
272#ifdef UNIT_TESTS
273#ifndef TEST_UNIT_H
274#include <test_unit.h>
275#include <sys/wait.h>
276#endif
277
278int echo_server(const char* portname) {
279    int mypid = fork();
280    if (mypid) return mypid;
281   
282    int fd;
283    char *filename = NULL;
284    GB_ERROR error = arb_open_socket(portname, false, &fd, &filename);
285    if (error) {
286        exit(1);
287    }
288
289    if (listen(fd, 1)) {
290        exit(2);
291    }
292
293    int cli_fd = accept(fd, NULL, NULL);
294    if (cli_fd < 0) {
295        exit(3);
296    }
297
298
299    char buf[500];
300    ssize_t n;
301    while(1) {
302        n = sizeof(buf);
303        n = arb_socket_read(cli_fd, buf, n);
304        if (n == 0) break;
305        n = arb_socket_write(cli_fd, buf, n);
306        if (n == -1) break;;
307        if (strcmp(buf, "exit") == 0) break;
308    }
309             
310    close(fd);
311    if (filename) { 
312        unlink(filename);
313        free(filename);
314    }
315   
316    exit(0);
317}
318
319#if !defined(DEVEL_JENKINS)
320// this test fails randomly (disabled in jenkins)
321void TEST_open_socket() {
322    int fd;
323    char *filename = NULL;
324    int server_pid, server_status;
325
326    // set up port names
327    char *unix_socket = arb_shell_expand(":$ARBHOME/UNIT_TESTER/sockets/test.socket");
328    char tcp_socket[sizeof("65536")], tcp_socket2[sizeof("localhost:65536")];
329    int port = 32039;
330    for (; port < 32139; port++) {
331        snprintf(tcp_socket, sizeof(tcp_socket), "%i", port);
332        const char *err = arb_open_socket(tcp_socket, true, &fd, &filename);
333        if (!err) { // connected
334            close(fd);
335        } 
336        else if (err[0] == '\0') { // could not connect
337            // found a free socket
338            break;
339        } 
340        else { // other error
341            TEST_EXPECT_NULL(err);
342        }
343    }
344    snprintf(tcp_socket2, sizeof(tcp_socket2), "localhost:%i", port);
345
346   
347    // Test opening server sockets
348    TEST_EXPECT_NULL(arb_open_socket(tcp_socket, false, &fd, &filename));
349    TEST_EXPECT(fd>0);
350    TEST_EXPECT_NULL(filename);
351    TEST_EXPECT_EQUAL(close(fd), 0);
352
353    TEST_EXPECT_NULL(arb_open_socket(tcp_socket2, false, &fd, &filename));
354    TEST_EXPECT(fd>0);
355    TEST_EXPECT_NULL(filename);
356    TEST_EXPECT_EQUAL(close(fd), 0);
357
358    TEST_EXPECT_NULL(arb_open_socket(unix_socket, false, &fd, &filename));
359    TEST_EXPECT(fd>0);
360    TEST_REJECT_NULL(filename);
361    TEST_EXPECT_EQUAL(close(fd), 0);
362    TEST_EXPECT_EQUAL(unlink(filename), 0);
363    freenull(filename);
364
365    // Test connecting to existing tcp socket
366    server_pid = echo_server(tcp_socket);
367    TEST_REJECT_NULL(server_pid);
368    usleep(10000);
369    TEST_EXPECT_NULL(arb_open_socket(tcp_socket, true, &fd, &filename)); // @@@ randomly fails on waltz (11/Aug/14, 12/Aug/14)
370    TEST_EXPECT(fd>0);
371    TEST_EXPECT_NULL(filename);
372    TEST_EXPECT_EQUAL(close(fd), 0);
373    TEST_EXPECT_EQUAL(server_pid, waitpid(server_pid, &server_status, 0));
374
375    // Test connecting to closed socket
376    TEST_EXPECT_EQUAL("", arb_open_socket(tcp_socket, true, &fd, &filename));
377    TEST_EXPECT_EQUAL("", arb_open_socket(unix_socket, true, &fd, &filename));
378   
379    // Test connecting to existing unix socket
380    server_pid = echo_server(unix_socket);
381    usleep(20000);
382    TEST_EXPECT_NULL(arb_open_socket(unix_socket, true, &fd, &filename)); // @@@ randomly fails in jenkins (build820/u1304/DEBUG, build817/cent5/DEBUG+cent6/NDEBUG)
383    TEST_EXPECT(fd>0);
384
385    // Test read/write
386    char send_buf[500], recv_buf[500];
387    for (unsigned int i=0; i < sizeof(send_buf); i++) {
388        send_buf[i]=i % 64 + '0';
389    }
390    send_buf[sizeof(send_buf)-1]='\0';
391
392    TEST_EXPECT_NULL(arb_socket_write(fd, send_buf, sizeof(send_buf)));
393    TEST_EXPECT_EQUAL(sizeof(recv_buf), arb_socket_read(fd, recv_buf, sizeof(recv_buf)));
394    TEST_EXPECT_EQUAL(send_buf, recv_buf);
395    TEST_EXPECT_NULL(arb_socket_write(fd, send_buf, sizeof(send_buf)));
396    TEST_EXPECT_EQUAL(sizeof(recv_buf), arb_socket_read(fd, recv_buf, sizeof(recv_buf)));
397    TEST_EXPECT_EQUAL(send_buf, recv_buf);
398
399    // Test sigpipe (writing to closed socket)
400    // tell server to die:
401    strcpy(send_buf, "exit"); 
402    TEST_EXPECT_NULL(arb_socket_write(fd, send_buf, sizeof(send_buf)));
403    TEST_EXPECT_EQUAL(sizeof(recv_buf), arb_socket_read(fd, recv_buf, sizeof(recv_buf)));
404    // wait for server to die
405    TEST_EXPECT_EQUAL(server_pid, waitpid(server_pid, &server_status, 0));
406    // try writing to closed pipe
407    TEST_EXPECT_EQUAL(-1, arb_socket_write(fd, send_buf, sizeof(send_buf)));
408
409    TEST_EXPECT_EQUAL(close(fd), 0);
410    freenull(filename);
411   
412    free(unix_socket);
413}
414TEST_PUBLISH(TEST_open_socket);
415#endif
416
417#endif // UNIT_TESTS
418
419
Note: See TracBrowser for help on using the repository browser.