Navigation


source: freeDiameter/freeDiameter/cnxctx.c @ 30:bca243c65b56

Last change on this file since 30:bca243c65b56 was 30:bca243c65b56, checked in by Sebastien Decugis <sdecugis@nict.go.jp>, 11 years ago

Fix some segfaults

File size: 34.1 KB
Line 
1/*********************************************************************************************************
2* Software License Agreement (BSD License)                                                               *
3* Author: Sebastien Decugis <sdecugis@nict.go.jp>                                                        *
4*                                                                                                        *
5* Copyright (c) 2009, WIDE Project and NICT                                                              *
6* All rights reserved.                                                                                   *
7*                                                                                                        *
8* Redistribution and use of this software in source and binary forms, with or without modification, are  *
9* permitted provided that the following conditions are met:                                              *
10*                                                                                                        *
11* * Redistributions of source code must retain the above                                                 *
12*   copyright notice, this list of conditions and the                                                    *
13*   following disclaimer.                                                                                *
14*                                                                                                        *
15* * Redistributions in binary form must reproduce the above                                              *
16*   copyright notice, this list of conditions and the                                                    *
17*   following disclaimer in the documentation and/or other                                               *
18*   materials provided with the distribution.                                                            *
19*                                                                                                        *
20* * Neither the name of the WIDE Project or NICT nor the                                                 *
21*   names of its contributors may be used to endorse or                                                  *
22*   promote products derived from this software without                                                  *
23*   specific prior written permission of WIDE Project and                                                *
24*   NICT.                                                                                                *
25*                                                                                                        *
26* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
27* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
28* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
29* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT     *
30* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS    *
31* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
32* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
33* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                                             *
34*********************************************************************************************************/
35
36#include "fD.h"
37#include "cnxctx.h"
38
39/* The maximum size of Diameter message we accept to receive (<= 2^24) to avoid too big mallocs in case of trashed headers */
40#ifndef DIAMETER_MSG_SIZE_MAX
41#define DIAMETER_MSG_SIZE_MAX   65535   /* in bytes */
42#endif /* DIAMETER_MSG_SIZE_MAX */
43
44/* Connections contexts (cnxctx) in freeDiameter are wrappers around the sockets and TLS operations .
45 * They are used to hide the details of the processing to the higher layers of the daemon.
46 * They are always oriented on connections (TCP or SCTP), connectionless modes (UDP or SCTP) are not supported.
47 */
48
49/* Note: this file could be moved to libfreeDiameter instead, but since it uses gnuTLS we prefer to keep it in the daemon */
50
51/* Lifetime of a cnxctx object:
52 * 1) Creation
53 *    a) a server socket:
54 *       - create the object with fd_cnx_serv_tcp or fd_cnx_serv_sctp
55 *       - start listening incoming connections: fd_cnx_serv_listen
56 *       - accept new clients with fd_cnx_serv_accept.
57 *    b) a client socket:
58 *       - connect to a remote server with fd_cnx_cli_connect
59 *
60 * 2) Initialization
61 *    - if TLS is started first, call fd_cnx_handshake
62 *    - otherwise to receive clear messages, call fd_cnx_start_clear. fd_cnx_handshake can be called later.
63 *
64 * 3) Usage
65 *    - fd_cnx_receive, fd_cnx_send : exchange messages on this connection (send is synchronous, receive is not, but blocking).
66 *    - fd_cnx_recv_setaltfifo : when a message is received, the event is sent to an external fifo list. fd_cnx_receive does not work when the alt_fifo is set.
67 *    - fd_cnx_getid : retrieve a descriptive string for the connection (for debug)
68 *    - fd_cnx_getremoteid : identification of the remote peer (IP address or fqdn)
69 *    - fd_cnx_getcred : get the remote peer TLS credentials, after handshake
70 *    - fd_cnx_getendpoints : get the endpoints (IP) of the connection
71 *
72 * 4) End
73 *    - fd_cnx_destroy
74 */
75
76
77/*******************************************/
78/*     Creation of a connection object     */
79/*******************************************/
80
81/* Initialize a context structure */
82static struct cnxctx * fd_cnx_init(int full)
83{
84        struct cnxctx * conn = NULL;
85
86        TRACE_ENTRY("%d", full);
87
88        CHECK_MALLOC_DO( conn = malloc(sizeof(struct cnxctx)), return NULL );
89        memset(conn, 0, sizeof(struct cnxctx));
90
91        if (full) {
92                CHECK_FCT_DO( fd_fifo_new ( &conn->cc_incoming ), return NULL );
93        }
94
95        return conn;
96}
97
98/* Create and bind a server socket to the given endpoint and port */
99struct cnxctx * fd_cnx_serv_tcp(uint16_t port, int family, struct fd_endpoint * ep)
100{
101        struct cnxctx * cnx = NULL;
102        sSS dummy;
103        sSA * sa = (sSA *) &dummy;
104
105        TRACE_ENTRY("%hu %d %p", port, family, ep);
106
107        CHECK_PARAMS_DO( port, return NULL );
108        CHECK_PARAMS_DO( ep || family, return NULL );
109        CHECK_PARAMS_DO( (! family) || (family == AF_INET) || (family == AF_INET6), return NULL );
110        CHECK_PARAMS_DO( (! ep) || (!family) || (ep->ss.ss_family == family), return NULL );
111
112        /* The connection object */
113        CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL );
114
115        /* Prepare the socket address information */
116        if (ep) {
117                memcpy(sa, &ep->ss, sizeof(sSS));
118        } else {
119                memset(&dummy, 0, sizeof(dummy));
120                sa->sa_family = family;
121        }
122        if (sa->sa_family == AF_INET) {
123                ((sSA4 *)sa)->sin_port = htons(port);
124        } else {
125                ((sSA6 *)sa)->sin6_port = htons(port);
126        }
127
128        /* Create the socket */
129        CHECK_FCT_DO( fd_tcp_create_bind_server( &cnx->cc_socket, sa, sizeof(sSS) ), goto error );
130
131        /* Generate the name for the connection object */
132        {
133                char addrbuf[INET6_ADDRSTRLEN];
134                int  rc;
135                rc = getnameinfo(sa, sizeof(sSS), addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
136                if (rc)
137                        snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
138                snprintf(cnx->cc_id, sizeof(cnx->cc_id), "Srv TCP [%s]:%hu (%d)", addrbuf, port, cnx->cc_socket);
139        }
140
141        cnx->cc_proto = IPPROTO_TCP;
142
143        return cnx;
144
145error:
146        fd_cnx_destroy(cnx);
147        return NULL;
148}
149
150/* Same function for SCTP, with a list of local endpoints to bind to */
151struct cnxctx * fd_cnx_serv_sctp(uint16_t port, struct fd_list * ep_list)
152{
153#ifdef DISABLE_SCTP
154        TRACE_DEBUG(INFO, "This function should never been called when SCTP is disabled...");
155        ASSERT(0);
156        CHECK_FCT_DO( ENOTSUP, return NULL);
157#else /* DISABLE_SCTP */
158        struct cnxctx * cnx = NULL;
159        sSS dummy;
160        sSA * sa = (sSA *) &dummy;
161
162        TRACE_ENTRY("%hu %p", port, ep_list);
163
164        CHECK_PARAMS_DO( port, return NULL );
165
166        /* The connection object */
167        CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL );
168
169        /* Create the socket */
170        CHECK_FCT_DO( fd_sctp_create_bind_server( &cnx->cc_socket, ep_list, port ), goto error );
171
172        /* Generate the name for the connection object */
173        snprintf(cnx->cc_id, sizeof(cnx->cc_id), "Srv SCTP :%hu (%d)", port, cnx->cc_socket);
174
175        cnx->cc_proto = IPPROTO_SCTP;
176
177        return cnx;
178
179error:
180        fd_cnx_destroy(cnx);
181        return NULL;
182#endif /* DISABLE_SCTP */
183}
184
185/* Allow clients to connect on the server socket */
186int fd_cnx_serv_listen(struct cnxctx * conn)
187{
188        CHECK_PARAMS( conn );
189
190        switch (conn->cc_proto) {
191                case IPPROTO_TCP:
192                        CHECK_FCT(fd_tcp_listen(conn->cc_socket));
193                        break;
194
195#ifndef DISABLE_SCTP
196                case IPPROTO_SCTP:
197                        CHECK_FCT(fd_sctp_listen(conn->cc_socket));
198                        break;
199#endif /* DISABLE_SCTP */
200
201                default:
202                        CHECK_PARAMS(0);
203        }
204
205        return 0;
206}
207
208/* Accept a client (blocking until a new client connects) -- cancelable */
209struct cnxctx * fd_cnx_serv_accept(struct cnxctx * serv)
210{
211        struct cnxctx * cli = NULL;
212        sSS ss;
213        socklen_t ss_len = sizeof(ss);
214        int cli_sock = 0;
215        struct fd_endpoint * ep;
216
217        TRACE_ENTRY("%p", serv);
218        CHECK_PARAMS_DO(serv, return NULL);
219       
220        /* Accept the new connection -- this is blocking until new client enters or cancellation */
221        CHECK_SYS_DO( cli_sock = accept(serv->cc_socket, (sSA *)&ss, &ss_len), return NULL );
222       
223        if (TRACE_BOOL(INFO)) {
224                fd_log_debug("%s : accepted new client [", fd_cnx_getid(serv));
225                sSA_DUMP_NODE( &ss, NI_NUMERICHOST );
226                fd_log_debug("].\n");
227        }
228       
229        CHECK_MALLOC_DO( cli = fd_cnx_init(1), { shutdown(cli_sock, SHUT_RDWR); return NULL; } );
230        cli->cc_socket = cli_sock;
231        cli->cc_proto = serv->cc_proto;
232       
233        /* Generate the name for the connection object */
234        {
235                char addrbuf[INET6_ADDRSTRLEN];
236                char portbuf[10];
237                int  rc;
238               
239                /* Numeric values for debug */
240                rc = getnameinfo((sSA *)&ss, sizeof(sSS), addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV);
241                if (rc) {
242                        snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
243                        portbuf[0] = '\0';
244                }
245               
246                snprintf(cli->cc_id, sizeof(cli->cc_id), "Incoming %s [%s]:%s (%d) @ serv (%d)", 
247                                IPPROTO_NAME(cli->cc_proto), 
248                                addrbuf, portbuf, 
249                                cli->cc_socket, serv->cc_socket);
250               
251                /* Name for log messages */
252                rc = getnameinfo((sSA *)&ss, sizeof(sSS), cli->cc_remid, sizeof(cli->cc_remid), NULL, 0, 0);
253                if (rc)
254                        snprintf(cli->cc_remid, sizeof(cli->cc_remid), "[err:%s]", gai_strerror(rc));
255        }
256
257#ifndef DISABLE_SCTP
258        /* SCTP-specific handlings */
259        if (cli->cc_proto == IPPROTO_SCTP) {
260                /* Retrieve the number of streams */
261                CHECK_FCT_DO( fd_sctp_get_str_info( cli->cc_socket, &cli->cc_sctp_para.str_in, &cli->cc_sctp_para.str_out, NULL ), goto error );
262                if (cli->cc_sctp_para.str_out > cli->cc_sctp_para.str_in)
263                        cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_out;
264                else
265                        cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_in;
266        }
267#endif /* DISABLE_SCTP */
268
269        return cli;
270error:
271        fd_cnx_destroy(cli);
272        return NULL;
273}
274
275/* Client side: connect to a remote server -- cancelable */
276struct cnxctx * fd_cnx_cli_connect_tcp(sSA * sa /* contains the port already */, socklen_t addrlen)
277{
278        int sock;
279        struct cnxctx * cnx = NULL;
280       
281        TRACE_ENTRY("%p %d", sa, addrlen);
282        CHECK_PARAMS_DO( sa && addrlen, return NULL );
283       
284        /* Create the socket and connect, which can take some time and/or fail */
285        CHECK_FCT_DO( fd_tcp_client( &sock, sa, addrlen ), return NULL );
286       
287        if (TRACE_BOOL(INFO)) {
288                fd_log_debug("Connection established to server '");
289                sSA_DUMP_NODE_SERV( sa, NI_NUMERICSERV);
290                fd_log_debug("' (TCP:%d).\n", sock);
291        }
292       
293        /* Once the socket is created successfuly, prepare the remaining of the cnx */
294        CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); return NULL; } );
295       
296        cnx->cc_socket = sock;
297        cnx->cc_proto  = IPPROTO_TCP;
298       
299        /* Generate the names for the object */
300        {
301                char addrbuf[INET6_ADDRSTRLEN];
302                char portbuf[10];
303                int  rc;
304               
305                /* Numeric values for debug */
306                rc = getnameinfo(sa, addrlen, addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV);
307                if (rc) {
308                        snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
309                        portbuf[0] = '\0';
310                }
311               
312                snprintf(cnx->cc_id, sizeof(cnx->cc_id), "Client of TCP server [%s]:%s (%d)", addrbuf, portbuf, cnx->cc_socket);
313               
314                /* Name for log messages */
315                rc = getnameinfo(sa, addrlen, cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0);
316                if (rc)
317                        snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc));
318        }
319       
320        return cnx;
321
322error:
323        fd_cnx_destroy(cnx);
324        return NULL;
325}
326
327/* Same for SCTP, accepts a list of remote addresses to connect to (see sctp_connectx for how they are used) */
328struct cnxctx * fd_cnx_cli_connect_sctp(int no_ip6, uint16_t port, struct fd_list * list)
329{
330#ifdef DISABLE_SCTP
331        TRACE_DEBUG(INFO, "This function should never been called when SCTP is disabled...");
332        ASSERT(0);
333        CHECK_FCT_DO( ENOTSUP, return NULL);
334#else /* DISABLE_SCTP */
335        int sock;
336        struct cnxctx * cnx = NULL;
337        sSS primary;
338       
339        TRACE_ENTRY("%p", list);
340        CHECK_PARAMS_DO( list && !FD_IS_LIST_EMPTY(list), return NULL );
341       
342        CHECK_FCT_DO( fd_sctp_client( &sock, no_ip6, port, list ), return NULL );
343       
344        /* Once the socket is created successfuly, prepare the remaining of the cnx */
345        CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); return NULL; } );
346       
347        cnx->cc_socket = sock;
348        cnx->cc_proto  = IPPROTO_SCTP;
349       
350        /* Retrieve the number of streams and primary address */
351        CHECK_FCT_DO( fd_sctp_get_str_info( sock, &cnx->cc_sctp_para.str_in, &cnx->cc_sctp_para.str_out, &primary ), goto error );
352        if (cnx->cc_sctp_para.str_out > cnx->cc_sctp_para.str_in)
353                cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_out;
354        else
355                cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_in;
356       
357        if (TRACE_BOOL(INFO)) {
358                fd_log_debug("Connection established to server '");
359                sSA_DUMP_NODE_SERV( &primary, NI_NUMERICSERV);
360                fd_log_debug("' (SCTP:%d, %d/%d streams).\n", sock, cnx->cc_sctp_para.str_in, cnx->cc_sctp_para.str_out);
361        }
362       
363        /* Generate the names for the object */
364        {
365                char addrbuf[INET6_ADDRSTRLEN];
366                char portbuf[10];
367                int  rc;
368               
369                /* Numeric values for debug */
370                rc = getnameinfo((sSA *)&primary, sizeof(sSS), addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV);
371                if (rc) {
372                        snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc));
373                        portbuf[0] = '\0';
374                }
375               
376                snprintf(cnx->cc_id, sizeof(cnx->cc_id), "Client of SCTP server [%s]:%s (%d)", addrbuf, portbuf, cnx->cc_socket);
377               
378                /* Name for log messages */
379                rc = getnameinfo((sSA *)&primary, sizeof(sSS), cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0);
380                if (rc)
381                        snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc));
382        }
383       
384        return cnx;
385
386error:
387        fd_cnx_destroy(cnx);
388        return NULL;
389#endif /* DISABLE_SCTP */
390}
391
392/* Return a string describing the connection, for debug */
393char * fd_cnx_getid(struct cnxctx * conn)
394{
395        CHECK_PARAMS_DO( conn, return "" );
396        return conn->cc_id;
397}
398
399/* Return the protocol of a connection */
400int fd_cnx_getproto(struct cnxctx * conn)
401{
402        CHECK_PARAMS_DO( conn, return 0 );
403        return conn->cc_proto;
404}
405
406/* Return the TLS state of a connection */
407int fd_cnx_getTLS(struct cnxctx * conn)
408{
409        CHECK_PARAMS_DO( conn, return 0 );
410        return conn->cc_tls;
411}
412
413/* Get the list of endpoints (IP addresses) of the local and remote peers on this connection */
414int fd_cnx_getendpoints(struct cnxctx * conn, struct fd_list * local, struct fd_list * remote)
415{
416        TRACE_ENTRY("%p %p %p", conn, local, remote);
417        CHECK_PARAMS(conn);
418       
419        if (local) {
420                /* Retrieve the local endpoint(s) of the connection */
421                switch (conn->cc_proto) {
422                        case IPPROTO_TCP: {
423                                sSS ss;
424                                socklen_t sl;
425                                CHECK_FCT(fd_tcp_get_local_ep(conn->cc_socket, &ss, &sl));
426                                CHECK_FCT(fd_ep_add_merge( local, (sSA *)&ss, sl, EP_FL_LL | EP_FL_PRIMARY));
427                        }
428                        break;
429
430                        #ifndef DISABLE_SCTP
431                        case IPPROTO_SCTP: {
432                                CHECK_FCT(fd_sctp_get_local_ep(conn->cc_socket, local));
433                        }
434                        break;
435                        #endif /* DISABLE_SCTP */
436
437                        default:
438                                CHECK_PARAMS(0);
439                }
440        }
441       
442        if (remote) {
443                /* Check we have a full connection object, not a listening socket (with no remote) */
444                CHECK_PARAMS( conn->cc_incoming );
445               
446                /* Retrieve the peer endpoint(s) of the connection */
447                switch (conn->cc_proto) {
448                        case IPPROTO_TCP: {
449                                sSS ss;
450                                socklen_t sl;
451                                CHECK_FCT(fd_tcp_get_remote_ep(conn->cc_socket, &ss, &sl));
452                                CHECK_FCT(fd_ep_add_merge( remote, (sSA *)&ss, sl, EP_FL_LL | EP_FL_PRIMARY ));
453                        }
454                        break;
455
456                        #ifndef DISABLE_SCTP
457                        case IPPROTO_SCTP: {
458                                CHECK_FCT(fd_sctp_get_remote_ep(conn->cc_socket, remote));
459                        }
460                        break;
461                        #endif /* DISABLE_SCTP */
462
463                        default:
464                                CHECK_PARAMS(0);
465                }
466        }
467
468        return 0;
469}
470
471
472/* Get a string describing the remote peer address (ip address or fqdn) */
473char * fd_cnx_getremoteid(struct cnxctx * conn)
474{
475        CHECK_PARAMS_DO( conn, return "" );
476        return conn->cc_remid;
477}
478
479
480/**************************************/
481/*     Use of a connection object     */
482/**************************************/
483
484/* Receiver thread (TCP & noTLS) : incoming message is directly saved into the target queue */
485static void * rcvthr_notls_tcp(void * arg)
486{
487        struct cnxctx * conn = arg;
488       
489        TRACE_ENTRY("%p", arg);
490        CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto out);
491       
492        /* Set the thread name */
493        {
494                char buf[48];
495                snprintf(buf, sizeof(buf), "Receiver (%d) TCP/noTLS)", conn->cc_socket);
496                fd_log_threadname ( buf );
497        }
498       
499        ASSERT( conn->cc_proto == IPPROTO_TCP );
500        ASSERT( conn->cc_tls == 0 );
501        ASSERT( Target_Queue(conn) );
502       
503        /* Receive from a TCP connection: we have to rebuild the message boundaries */
504        do {
505                uint8_t header[4];
506                uint8_t * newmsg;
507                size_t  length;
508                ssize_t ret = 0;
509                size_t  received = 0;
510
511                do {
512                        ret = recv(conn->cc_socket, &header[received], sizeof(header) - received, 0);
513                        if (ret <= 0) {
514                                CHECK_SYS_DO(ret, /* continue */);
515                                goto error; /* Stop the thread, the recipient of the event will cleanup */
516                        }
517
518                        received += ret;
519                } while (received < sizeof(header));
520
521                length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3];
522
523                /* Check the received word is a valid begining of a Diameter message */
524                if ((header[0] != DIAMETER_VERSION)     /* defined in <libfreeDiameter.h> */
525                   || (length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */
526                        /* The message is suspect */
527                        TRACE_DEBUG(INFO, "Received suspect header [ver: %d, size: %zd], assume disconnection", (int)header[0], length);
528                        goto error; /* Stop the thread, the recipient of the event will cleanup */
529                }
530
531                /* Ok, now we can really receive the data */
532                CHECK_MALLOC_DO(  newmsg = malloc( length ), goto error );
533                memcpy(newmsg, header, sizeof(header));
534
535                while (received < length) {
536                        pthread_cleanup_push(free, newmsg); /* In case we are canceled, clean the partialy built buffer */
537                        ret = recv(conn->cc_socket, newmsg + received, length - received, 0);
538                        pthread_cleanup_pop(0);
539
540                        if (ret <= 0) {
541                                CHECK_SYS_DO(ret, /* continue */);
542                                free(newmsg);
543                                goto error; /* Stop the thread, the recipient of the event will cleanup */
544                        }
545                        received += ret;
546                }
547               
548                /* We have received a complete message, send it */
549                CHECK_FCT_DO( fd_event_send( Target_Queue(conn), FDEVP_CNX_MSG_RECV, length, newmsg), /* continue or destroy everything? */);
550               
551        } while (conn->cc_loop);
552       
553out:
554        TRACE_DEBUG(FULL, "Thread terminated"); 
555        return NULL;
556error:
557        CHECK_FCT_DO( fd_event_send( Target_Queue(conn), FDEVP_CNX_ERROR, 0, NULL), /* continue or destroy everything? */);
558        goto out;
559}
560
561#ifndef DISABLE_SCTP
562/* Receiver thread (SCTP & noTLS) : incoming message is directly saved into cc_incoming, no need to care for the stream ID */
563static void * rcvthr_notls_sctp(void * arg)
564{
565        struct cnxctx * conn = arg;
566        uint8_t * buf;
567        size_t    bufsz;
568        int       event;
569       
570        TRACE_ENTRY("%p", arg);
571        CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto out);
572       
573        /* Set the thread name */
574        {
575                char buf[48];
576                snprintf(buf, sizeof(buf), "Receiver (%d) SCTP/noTLS)", conn->cc_socket);
577                fd_log_threadname ( buf );
578        }
579       
580        ASSERT( conn->cc_proto == IPPROTO_SCTP );
581        ASSERT( conn->cc_tls == 0 );
582        ASSERT( Target_Queue(conn) );
583       
584        do {
585                CHECK_FCT_DO( fd_sctp_recvmeta(conn->cc_socket, NULL, &buf, &bufsz, &event), goto error );
586                if (event == FDEVP_CNX_ERROR) {
587                        goto error;
588                }
589               
590                CHECK_FCT_DO( fd_event_send( Target_Queue(conn), event, bufsz, buf), goto error );
591               
592        } while (conn->cc_loop);
593       
594out:
595        TRACE_DEBUG(FULL, "Thread terminated"); 
596        return NULL;
597error:
598        CHECK_FCT_DO( fd_event_send( Target_Queue(conn), FDEVP_CNX_ERROR, 0, NULL), /* continue or destroy everything? */);
599        goto out;
600}
601#endif /* DISABLE_SCTP */
602
603/* Returns 0 on error, received data size otherwise (always >= 0) */
604static ssize_t fd_tls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz)
605{
606        ssize_t ret;
607again: 
608        CHECK_GNUTLS_DO( ret = gnutls_record_recv(session, data, sz),
609                {
610                        switch (ret) {
611                                case GNUTLS_E_REHANDSHAKE: 
612                                        CHECK_GNUTLS_DO( ret = gnutls_handshake(session),
613                                                {
614                                                        if (TRACE_BOOL(INFO)) {
615                                                                fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s\n", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
616                                                        }
617                                                        ret = 0;
618                                                        goto end;
619                                                } );
620
621                                case GNUTLS_E_AGAIN:
622                                case GNUTLS_E_INTERRUPTED:
623                                        goto again;
624
625                                default:
626                                        TRACE_DEBUG(INFO, "This TLS error is not handled, assume unrecoverable error");
627                                        ret = 0;
628                        }
629                } );
630end:   
631        return ret;
632}
633
634/* The function that receives TLS data and re-builds a Diameter message -- it exits only on error or cancelation */
635int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session)
636{
637        /* No guarantee that GnuTLS preserves the message boundaries, so we re-build it as in TCP */
638        do {
639                uint8_t header[4];
640                uint8_t * newmsg;
641                size_t  length;
642                ssize_t ret = 0;
643                size_t  received = 0;
644
645                do {
646                        ret = fd_tls_recv_handle_error(conn, session, &header[received], sizeof(header) - received);
647                        if (ret == 0) {
648                                /* The connection is closed */
649                                goto out;
650                        }
651                        received += ret;
652                } while (received < sizeof(header));
653
654                length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3];
655
656                /* Check the received word is a valid beginning of a Diameter message */
657                if ((header[0] != DIAMETER_VERSION)     /* defined in <libfreeDiameter.h> */
658                   || (length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */
659                        /* The message is suspect */
660                        TRACE_DEBUG(INFO, "Received suspect header [ver: %d, size: %zd], assume disconnection", (int)header[0], length);
661                        goto out;
662                }
663
664                /* Ok, now we can really receive the data */
665                CHECK_MALLOC(  newmsg = malloc( length ) );
666                memcpy(newmsg, header, sizeof(header));
667
668                while (received < length) {
669                        pthread_cleanup_push(free, newmsg); /* In case we are canceled, clean the partialy built buffer */
670                        ret = fd_tls_recv_handle_error(conn, session, newmsg + received, length - received);
671                        pthread_cleanup_pop(0);
672
673                        if (ret == 0) {
674                                free(newmsg);
675                                goto out; /* Stop the thread, the recipient of the event will cleanup */
676                        }
677                        received += ret;
678                }
679               
680                /* We have received a complete message, send it */
681                CHECK_FCT_DO( fd_event_send( Target_Queue(conn), FDEVP_CNX_MSG_RECV, length, newmsg), /* continue or destroy everything? */);
682               
683        } while (1);
684out:
685        return ENOTCONN;
686}
687
688/* Receiver thread (TLS & 1 stream SCTP or TCP) : gnutls directly handles the socket, save records into the target queue */
689static void * rcvthr_tls_single(void * arg)
690{
691        struct cnxctx * conn = arg;
692       
693        TRACE_ENTRY("%p", arg);
694        CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto error);
695       
696        /* Set the thread name */
697        {
698                char buf[48];
699                snprintf(buf, sizeof(buf), "Receiver (%d) TLS/ single stream)", conn->cc_socket);
700                fd_log_threadname ( buf );
701        }
702       
703        ASSERT( conn->cc_tls == 1 );
704        ASSERT( Target_Queue(conn) );
705       
706        CHECK_FCT_DO(fd_tls_rcvthr_core(conn, conn->cc_tls_para.session), /* continue */);
707error:
708        CHECK_FCT_DO( fd_event_send( Target_Queue(conn), FDEVP_CNX_ERROR, 0, NULL), /* continue or destroy everything? */);
709        TRACE_DEBUG(FULL, "Thread terminated"); 
710        return NULL;
711}
712
713/* Start receving messages in clear (no TLS) on the connection */
714int fd_cnx_start_clear(struct cnxctx * conn, int loop)
715{
716        TRACE_ENTRY("%p %i", conn, loop);
717       
718        CHECK_PARAMS( conn && Target_Queue(conn) && (!conn->cc_tls) && (!conn->cc_loop));
719       
720        /* Save the loop request */
721        conn->cc_loop = loop;
722       
723        /* Release resources in case of a previous call was already made */
724        CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */);
725       
726        switch (conn->cc_proto) {
727                case IPPROTO_TCP:
728                        /* Start the tcp_notls thread */
729                        CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_tcp, conn ) );
730                        break;
731#ifndef DISABLE_SCTP
732                case IPPROTO_SCTP:
733                        /* Start the tcp_notls thread */
734                        CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_sctp, conn ) );
735                        break;
736#endif /* DISABLE_SCTP */
737                default:
738                        TRACE_DEBUG(INFO, "Unknown protocol: %d", conn->cc_proto);
739                        return ENOTSUP;
740        }
741                       
742        return 0;
743}
744
745/* Prepare a gnutls session object for handshake */
746int fd_tls_prepare(gnutls_session_t * session, int mode, char * priority, void * alt_creds)
747{
748        /* Create the master session context */
749        CHECK_GNUTLS_DO( gnutls_init (session, mode), return ENOMEM );
750
751        /* Set the algorithm suite */
752        if (priority) {
753                const char * errorpos;
754                CHECK_GNUTLS_DO( gnutls_priority_set_direct( *session, priority, &errorpos ), 
755                        { TRACE_DEBUG(INFO, "Error in priority string '%s' at position: '%s'\n", priority, errorpos); return EINVAL; } );
756        } else {
757                CHECK_GNUTLS_DO( gnutls_priority_set( *session, fd_g_config->cnf_sec_data.prio_cache ), return EINVAL );
758        }
759
760        /* Set the credentials of this side of the connection */
761        CHECK_GNUTLS_DO( gnutls_credentials_set (*session, GNUTLS_CRD_CERTIFICATE, alt_creds ?: fd_g_config->cnf_sec_data.credentials), return EINVAL );
762
763        /* Request the remote credentials as well */
764        if (mode == GNUTLS_SERVER) {
765                gnutls_certificate_server_set_request (*session, GNUTLS_CERT_REQUIRE);
766        }
767       
768        return 0;
769}
770
771/* TLS handshake a connection; no need to have called start_clear before. Reception is active if handhsake is successful */
772int fd_cnx_handshake(struct cnxctx * conn, int mode, char * priority, void * alt_creds)
773{
774        TRACE_ENTRY( "%p %d", conn, mode);
775        CHECK_PARAMS( conn && (!conn->cc_tls) && ( (mode == GNUTLS_CLIENT) || (mode == GNUTLS_SERVER) ) && (!conn->cc_loop) );
776
777        /* Save the mode */
778        conn->cc_tls_para.mode = mode;
779       
780        /* Cancel receiving thread if any -- it should already be terminated anyway, we just release the resources */
781        CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */);
782       
783        /* Once TLS handshake is done, we don't stop after the first message */
784        conn->cc_loop = 1;
785       
786        /* Prepare the master session credentials and priority */
787        CHECK_FCT( fd_tls_prepare(&conn->cc_tls_para.session, mode, priority, alt_creds) );
788
789        /* Special case: multi-stream TLS is not natively managed in GNU TLS, we use a wrapper library */
790        if (conn->cc_sctp_para.pairs > 1) {
791#ifdef DISABLE_SCTP
792                ASSERT(0);
793                CHECK_FCT( ENOTSUP );
794#else /* DISABLE_SCTP */
795                /* Initialize the wrapper, start the demux thread */
796                CHECK_FCT( fd_sctps_init(conn) );
797#endif /* DISABLE_SCTP */
798        } else {
799                /* Set the socket info in the session */
800                gnutls_transport_set_ptr (conn->cc_tls_para.session, (gnutls_transport_ptr_t) conn->cc_socket);
801        }
802
803        /* Handshake master session */
804        {
805                int ret;
806                CHECK_GNUTLS_DO( ret = gnutls_handshake(conn->cc_tls_para.session),
807                        {
808                                if (TRACE_BOOL(INFO)) {
809                                        fd_log_debug("TLS Handshake failed on socket %d (%s) : %s\n", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
810                                }
811                                return EINVAL;
812                        } );
813
814                /* Now verify the remote credentials are valid -- only simple test here */
815                CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (conn->cc_tls_para.session, &ret), return EINVAL );
816                if (ret) {
817                        if (TRACE_BOOL(INFO)) {
818                                fd_log_debug("TLS: Remote certificate invalid on socket %d (%s) :\n", conn->cc_socket, conn->cc_id);
819                                if (ret & GNUTLS_CERT_INVALID)
820                                        fd_log_debug(" - The certificate is not trusted (unknown CA?)\n");
821                                if (ret & GNUTLS_CERT_REVOKED)
822                                        fd_log_debug(" - The certificate has been revoked.\n");
823                                if (ret & GNUTLS_CERT_SIGNER_NOT_FOUND)
824                                        fd_log_debug(" - The certificate hasn't got a known issuer.\n");
825                                if (ret & GNUTLS_CERT_SIGNER_NOT_CA)
826                                        fd_log_debug(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.\n");
827                                if (ret & GNUTLS_CERT_INSECURE_ALGORITHM)
828                                        fd_log_debug(" - The certificate signature uses a weak algorithm.\n");
829                        }
830                        return EINVAL;
831                }
832        }
833
834        /* Multi-stream TLS: handshake other streams as well */
835        if (conn->cc_sctp_para.pairs > 1) {
836#ifndef DISABLE_SCTP
837                /* Resume all additional sessions from the master one. */
838                CHECK_FCT(fd_sctps_handshake_others(conn, priority, alt_creds));
839               
840                /* Mark the connection as protected from here */
841                conn->cc_tls = 1;
842
843                /* Start decrypting the messages from all threads and queuing them in target queue */
844                CHECK_FCT(fd_sctps_startthreads(conn));
845#endif /* DISABLE_SCTP */
846        } else {
847                /* Mark the connection as protected from here */
848                conn->cc_tls = 1;
849
850                /* Start decrypting the data */
851                CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_tls_single, conn ) );
852        }
853       
854        return 0;
855}
856
857/* Retrieve TLS credentials of the remote peer, after handshake */
858int fd_cnx_getcred(struct cnxctx * conn, const gnutls_datum_t **cert_list, unsigned int *cert_list_size)
859{
860        TRACE_ENTRY("%p %p %p", conn, cert_list, cert_list_size);
861        CHECK_PARAMS( conn && (conn->cc_tls) && cert_list && cert_list_size );
862       
863        /* This function only works for X.509 certificates. */
864        CHECK_PARAMS( gnutls_certificate_type_get (conn->cc_tls_para.session) == GNUTLS_CRT_X509 );
865       
866        *cert_list = gnutls_certificate_get_peers (conn->cc_tls_para.session, cert_list_size);
867        if (*cert_list == NULL) {
868                TRACE_DEBUG(INFO, "No certificate was provided by remote peer / an error occurred.");
869                return EINVAL;
870        }
871
872        TRACE_DEBUG( FULL, "Remote peer provided %d certificates.\n", *cert_list_size);
873       
874        return 0;
875}
876
877/* Receive next message. if timeout is not NULL, wait only until timeout. This function only pulls from a queue, mgr thread is filling that queue aynchrounously. */
878int fd_cnx_receive(struct cnxctx * conn, struct timespec * timeout, unsigned char **buf, size_t * len)
879{
880        int    ev;
881        size_t ev_sz;
882        void * ev_data;
883       
884        TRACE_ENTRY("%p %p %p %p", conn, timeout, buf, len);
885        CHECK_PARAMS(conn && (conn->cc_socket > 0) && buf && len);
886        CHECK_PARAMS(conn->cc_rcvthr != (pthread_t)NULL);
887        CHECK_PARAMS(conn->cc_alt == NULL);
888
889        /* Now, pull the first event */
890get_next:
891        if (timeout) {
892                CHECK_FCT( fd_event_timedget(conn->cc_incoming, timeout, FDEVP_PSM_TIMEOUT, &ev, &ev_sz, &ev_data) );
893        } else {
894                CHECK_FCT( fd_event_get(conn->cc_incoming, &ev, &ev_sz, &ev_data) );
895        }
896       
897        switch (ev) {
898                case FDEVP_CNX_MSG_RECV:
899                        /* We got one */
900                        *len = ev_sz;
901                        *buf = ev_data;
902                        return 0;
903                       
904                case FDEVP_PSM_TIMEOUT:
905                        TRACE_DEBUG(FULL, "Timeout event received");
906                        return ETIMEDOUT;
907                       
908                case FDEVP_CNX_EP_CHANGE:
909                        /* We ignore this event */
910                        goto get_next;
911                       
912                case FDEVP_CNX_ERROR:
913                        TRACE_DEBUG(FULL, "Received ERROR event on the connection");
914                        return ENOTCONN;
915        }
916       
917        TRACE_DEBUG(INFO, "Received unexpected event %d (%s)", ev, fd_pev_str(ev));
918        return EINVAL;
919}
920
921/* Set an alternate FIFO list to send FDEVP_CNX_* events to */
922int fd_cnx_recv_setaltfifo(struct cnxctx * conn, struct fifo * alt_fifo)
923{
924        TRACE_ENTRY( "%p %p", conn, alt_fifo );
925        CHECK_PARAMS( conn && alt_fifo && conn->cc_incoming );
926       
927        /* The magic function does it all */
928        CHECK_FCT( fd_fifo_move( &conn->cc_incoming, alt_fifo, &conn->cc_alt ) );
929       
930        return 0;
931}
932
933/* Wrapper around gnutls_record_recv to handle some error codes */
934static ssize_t fd_tls_send_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz)
935{
936        ssize_t ret;
937again: 
938        CHECK_GNUTLS_DO( ret = gnutls_record_send(session, data, sz),
939                {
940                        switch (ret) {
941                                case GNUTLS_E_REHANDSHAKE: 
942                                        CHECK_GNUTLS_DO( ret = gnutls_handshake(session),
943                                                {
944                                                        if (TRACE_BOOL(INFO)) {
945                                                                fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s\n", conn->cc_socket, conn->cc_id, gnutls_strerror(ret));
946                                                        }
947                                                        goto end;
948                                                } );
949
950                                case GNUTLS_E_AGAIN:
951                                case GNUTLS_E_INTERRUPTED:
952                                        goto again;
953
954                                default:
955                                        TRACE_DEBUG(INFO, "This TLS error is not handled, assume unrecoverable error");
956                        }
957                } );
958end:   
959        return ret;
960}
961
962
963
964/* Send function when no multi-stream is involved, or sending on stream #0 (send() always use stream 0)*/
965static int send_simple(struct cnxctx * conn, unsigned char * buf, size_t len)
966{
967        ssize_t ret;
968        size_t sent = 0;
969        TRACE_ENTRY("%p %p %zd", conn, buf, len);
970        do {
971                if (conn->cc_tls) {
972                        CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_tls_para.session, buf + sent, len - sent), return ENOTCONN );
973                } else {
974                        CHECK_SYS( ret = send(conn->cc_socket, buf + sent, len - sent, 0) ); /* better to replace with sendmsg for atomic sending? */
975                }
976                sent += ret;
977        } while ( sent < len );
978        return 0;
979}
980
981/* Send a message -- this is synchronous -- and we assume it's never called by several threads at the same time, so we don't protect. */
982int fd_cnx_send(struct cnxctx * conn, unsigned char * buf, size_t len)
983{
984        TRACE_ENTRY("%p %p %zd", conn, buf, len);
985       
986        CHECK_PARAMS(conn && (conn->cc_socket > 0) && buf && len);
987
988        TRACE_DEBUG(FULL, "Sending %zdb %sdata on connection %s", len, conn->cc_tls ? "TLS-protected ":"", conn->cc_id);
989       
990        switch (conn->cc_proto) {
991                case IPPROTO_TCP:
992                        CHECK_FCT( send_simple(conn, buf, len) );
993                        break;
994               
995#ifndef DISABLE_SCTP
996                case IPPROTO_SCTP: {
997                        int multistr = 0;
998                       
999                        if ((conn->cc_sctp_para.str_out > 1) && ((! conn->cc_tls) || (conn->cc_sctp_para.pairs > 1)))  {
1000                                /* Update the id of the stream we will send this message on */
1001                                conn->cc_sctp_para.next += 1;
1002                                conn->cc_sctp_para.next %= (conn->cc_tls ? conn->cc_sctp_para.pairs : conn->cc_sctp_para.str_out);
1003                                multistr = 1;
1004                        }
1005                       
1006                        if ((!multistr) || (conn->cc_sctp_para.next == 0)) {
1007                                CHECK_FCT( send_simple(conn, buf, len) );
1008                        } else {
1009                                if (!conn->cc_tls) {
1010                                        CHECK_FCT( fd_sctp_sendstr(conn->cc_socket, conn->cc_sctp_para.next, buf, len) );
1011                                } else {
1012                                        /* push the record to the appropriate session */
1013                                        ssize_t ret;
1014                                        size_t sent = 0;
1015                                        ASSERT(conn->cc_sctps_data.array != NULL);
1016                                        do {
1017                                                CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_sctps_data.array[conn->cc_sctp_para.next].session, buf + sent, len - sent), return ENOTCONN );
1018                                                sent += ret;
1019                                        } while ( sent < len );
1020                                }
1021                        }
1022                }
1023                break;
1024#endif /* DISABLE_SCTP */
1025       
1026                default:
1027                        TRACE_DEBUG(INFO, "Unknwon protocol: %d", conn->cc_proto);
1028                        return ENOTSUP; /* or EINVAL... */
1029        }
1030       
1031        return 0;
1032}
1033
1034
1035/**************************************/
1036/*     Destruction of connection      */
1037/**************************************/
1038
1039/* Destroy a conn structure, and shutdown the socket */
1040void fd_cnx_destroy(struct cnxctx * conn)
1041{
1042        TRACE_ENTRY("%p", conn);
1043       
1044        CHECK_PARAMS_DO(conn, return);
1045
1046        /* In case of TLS, stop receiver thread, then close properly the gnutls session */
1047        if ((conn->cc_tls) && (conn->cc_sctp_para.pairs > 1)) {
1048#ifndef DISABLE_SCTP
1049                /* Multi-stream TLS: Stop all decipher threads, but not the demux thread */
1050                fd_sctps_stopthreads(conn);
1051#endif /* DISABLE_SCTP */
1052        } else {
1053                /* Stop the decoding thread */
1054                CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ );
1055        }
1056       
1057        /* Terminate properly the TLS session(s) */
1058        if (conn->cc_tls) {
1059                /* Master session */
1060                CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_RDWR), /* Continue */ );
1061                gnutls_deinit(conn->cc_tls_para.session);
1062               
1063#ifndef DISABLE_SCTP
1064                if (conn->cc_sctp_para.pairs > 1) {
1065                        /* Multi-stream TLS: destroy the wrapper and stop the demux thread */
1066                        fd_sctps_destroy(conn);
1067                }
1068#endif /* DISABLE_SCTP */
1069               
1070        }
1071       
1072        /* Shut the connection down */
1073        if (conn->cc_socket > 0) {
1074                shutdown(conn->cc_socket, SHUT_RDWR);
1075        }
1076       
1077        /* Empty and destroy FIFO list */
1078        if (conn->cc_incoming) {
1079                fd_event_destroy( &conn->cc_incoming, free );
1080        }
1081       
1082        /* Free the object */
1083        free(conn);
1084       
1085        /* Done! */
1086        return;
1087}
Note: See TracBrowser for help on using the repository browser.