Mercurial > hg > freeDiameter
diff freeDiameter/cnxctx.c @ 22:0b3b46da2c12
Progress on server code
author | Sebastien Decugis <sdecugis@nict.go.jp> |
---|---|
date | Mon, 19 Oct 2009 18:43:09 +0900 |
parents | bef197f6826f |
children | db6c40b8b307 |
line wrap: on
line diff
--- a/freeDiameter/cnxctx.c Thu Oct 08 20:05:16 2009 +0900 +++ b/freeDiameter/cnxctx.c Mon Oct 19 18:43:09 2009 +0900 @@ -35,21 +35,53 @@ #include "fD.h" +/* Connections contexts (cnxctx) in freeDiameter are wrappers around the sockets and TLS operations . + * They are used to hide the details of the processing to the higher layers of the daemon. + * They are always oriented on connections (TCP or SCTP), connectionless modes (UDP or SCTP) are not supported. + */ + +/* Note: this file could be moved to libfreeDiameter instead, but since it uses gnuTLS we prefer to keep it in the daemon */ + +/* Lifetime of a cnxctx object: + * 1) Creation + * a) a server socket: + * - create the object with fd_cnx_serv_tcp or fd_cnx_serv_sctp + * - start listening incoming connections: fd_cnx_serv_listen + * - accept new clients with fd_cnx_serv_accept. + * b) a client socket: + * - connect to a remote server with fd_cnx_cli_connect + * + * 2) Initialization + * - if TLS is started first, call fd_cnx_handshake + * - otherwise to receive clear messages, call fd_cnx_start_clear. fd_cnx_handshake can be called later. + * + * 3) Usage + * - fd_cnx_receive, fd_cnx_send : exchange messages on this connection (send is synchronous, receive is not). + * - 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. + * - fd_cnx_getid : retrieve a descriptive string for the connection (for debug) + * - fd_cnx_getremoteid : identification of the remote peer (IP address or fqdn) + * - fd_cnx_getcred : get the remote peer TLS credentials, after handshake + * - fd_cnx_getendpoints : get the endpoints (IP) of the connection + * + * 4) End + * - fd_cnx_destroy + */ + /* The connection context structure */ struct cnxctx { + char cc_id[60]; /* The name of this connection */ + char cc_remid[60]; /* Id of remote peer */ + int cc_socket; /* The socket object of the connection -- <=0 if no socket is created */ - + int cc_proto; /* IPPROTO_TCP or IPPROTO_SCTP */ int cc_tls; /* Is TLS already started ? */ - + struct fifo * cc_events; /* Events occuring on the connection */ pthread_t cc_mgr; /* manager thread for the connection */ struct fifo * cc_incoming; /* FIFO queue of messages received on the connection */ - - uint16_t cc_port; /* Remote port of the connection, when we are client */ - struct fd_list cc_ep_remote; /* The remote address(es) of the connection */ - struct fd_list cc_ep_local; /* The local address(es) of the connection */ - + struct fifo * cc_alt; /* alternate fifo to send FDEVP_CNX_MSG_RECV events to. */ + /* If cc_proto == SCTP */ struct { int str_out;/* Out streams */ @@ -57,13 +89,13 @@ int pairs; /* max number of pairs ( = min(in, out)) */ int next; /* # of stream the next message will be sent to */ } cc_sctp_para; - + /* If cc_tls == true */ struct { int mode; /* GNUTLS_CLIENT / GNUTLS_SERVER */ gnutls_session_t session; /* Session object (stream #0 in case of SCTP) */ } cc_tls_para; - + /* If both conditions */ struct { gnutls_session_t *res_sessions; /* Sessions of other pairs of streams, resumed from the first */ @@ -72,62 +104,232 @@ }; -/* Initialize a context structure from a socket */ -struct cnxctx * fd_cnx_init(int sock, int proto) +/* Initialize a context structure */ +static struct cnxctx * fd_cnx_init(int full) { struct cnxctx * conn = NULL; - - TRACE_ENTRY("%d %d", sock, proto); - CHECK_PARAMS_DO( (proto == IPPROTO_TCP) || (proto == IPPROTO_SCTP), return NULL); - + + TRACE_ENTRY("%d", full); + CHECK_MALLOC_DO( conn = malloc(sizeof(struct cnxctx)), return NULL ); memset(conn, 0, sizeof(struct cnxctx)); - - conn->cc_socket = sock; - conn->cc_proto = proto; - - fd_list_init(&conn->cc_ep_remote, conn); - fd_list_init(&conn->cc_ep_local, conn); + + if (full) { + CHECK_FCT_DO( fd_fifo_new ( &conn->cc_events ), return NULL ); + CHECK_FCT_DO( fd_fifo_new ( &conn->cc_incoming ), return NULL ); + } + + return conn; +} + +/* Create and bind a server socket to the given endpoint and port */ +struct cnxctx * fd_cnx_serv_tcp(uint16_t port, int family, struct fd_endpoint * ep) +{ + struct cnxctx * cnx = NULL; + sSS dummy; + sSA * sa = (sSA *) &dummy; + + TRACE_ENTRY("%hu %d %p", port, family, ep); + + CHECK_PARAMS_DO( port, return NULL ); + CHECK_PARAMS_DO( ep || family, return NULL ); + CHECK_PARAMS_DO( (! family) || (family == AF_INET) || (family == AF_INET6), return NULL ); + CHECK_PARAMS_DO( (! ep) || (!family) || (ep->ss.ss_family == family), return NULL ); + + /* The connection object */ + CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL ); + + /* Prepare the socket address information */ + if (ep) { + memcpy(sa, &ep->ss, sizeof(sSS)); + } else { + memset(&dummy, 0, sizeof(dummy)); + sa->sa_family = family; + } + if (sa->sa_family == AF_INET) { + ((sSA4 *)sa)->sin_port = htons(port); + } else { + ((sSA6 *)sa)->sin6_port = htons(port); + } + + /* Create the socket */ + CHECK_FCT_DO( fd_tcp_create_bind_server( &cnx->cc_socket, sa, sizeof(sSS) ), goto error ); + + /* Generate the name for the connection object */ + { + char addrbuf[INET6_ADDRSTRLEN]; + int rc; + rc = getnameinfo(sa, sizeof(sSS), addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); + if (rc) + snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc)); + snprintf(cnx->cc_id, sizeof(cnx->cc_id), "Srv TCP [%s]:%hu (%d)", addrbuf, port, cnx->cc_socket); + } + + cnx->cc_proto = IPPROTO_TCP; + + return cnx; + +error: + fd_cnx_destroy(cnx); + return NULL; +} +#ifndef DISABLE_SCTP +struct cnxctx * fd_cnx_serv_sctp(uint16_t port, struct fd_list * ep_list) +{ + struct cnxctx * cnx = NULL; + sSS dummy; + sSA * sa = (sSA *) &dummy; + + TRACE_ENTRY("%hu %p", port, ep_list); + + CHECK_PARAMS_DO( port, return NULL ); + + /* The connection object */ + CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL ); + + /* Create the socket */ + CHECK_FCT_DO( fd_sctp_create_bind_server( &cnx->cc_socket, ep_list, port ), goto error ); + + /* Generate the name for the connection object */ + snprintf(cnx->cc_id, sizeof(cnx->cc_id), "Srv SCTP :%hu (%d)", port, cnx->cc_socket); + + cnx->cc_proto = IPPROTO_SCTP; + + return cnx; + +error: + fd_cnx_destroy(cnx); + return NULL; +} +#endif /* DISABLE_SCTP */ + +/* Allow clients to connect on the server socket */ +int fd_cnx_serv_listen(struct cnxctx * conn) +{ + CHECK_PARAMS( conn ); + + switch (conn->cc_proto) { + case IPPROTO_TCP: + CHECK_FCT(fd_tcp_listen(conn->cc_socket)); + break; + + case IPPROTO_SCTP: + CHECK_FCT(fd_sctp_listen(conn->cc_socket)); + break; + + default: + CHECK_PARAMS(0); + } + + return 0; +} + +/* Accept a client (blocking until a new client connects) -- cancelable */ +struct cnxctx * fd_cnx_serv_accept(struct cnxctx * serv) +{ + struct cnxctx * cli = NULL; + sSS ss; + socklen_t ss_len = sizeof(ss); + int cli_sock = 0; + struct fd_endpoint * ep; + + TRACE_ENTRY("%p", serv); + CHECK_PARAMS_DO(serv, return NULL); - if (proto == IPPROTO_SCTP) { -#ifndef DISABLE_SCTP - CHECK_FCT_DO( fd_sctp_get_str_info( sock, &conn->cc_sctp_para.str_in, &conn->cc_sctp_para.str_out ), - { free(conn); return NULL; } ); - conn->cc_sctp_para.pairs = (conn->cc_sctp_para.str_out < conn->cc_sctp_para.str_in) ? conn->cc_sctp_para.str_out : conn->cc_sctp_para.str_in; -#else /* DISABLE_SCTP */ - ASSERT(0); -#endif /* DISABLE_SCTP */ + CHECK_SYS_DO( cli_sock = accept(serv->cc_socket, (sSA *)&ss, &ss_len), return NULL ); + + if (TRACE_BOOL(INFO)) { + fd_log_debug("%s - new client [", fd_cnx_getid(serv)); + sSA_DUMP_NODE( &ss, AI_NUMERICHOST ); + fd_log_debug("] connected.\n"); } - return conn; + CHECK_MALLOC_DO( cli = fd_cnx_init(1), { shutdown(cli_sock, SHUT_RDWR); return NULL; } ); + cli->cc_socket = cli_sock; + cli->cc_proto = serv->cc_proto; + + /* Generate the name for the connection object */ + { + char addrbuf[INET6_ADDRSTRLEN]; + char portbuf[10]; + int rc; + + /* Numeric values for debug */ + rc = getnameinfo((sSA *)&ss, sizeof(sSS), addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) + snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc)); + + snprintf(cli->cc_id, sizeof(cli->cc_id), "Client %s [%s]:%s (%d) / serv (%d)", + IPPROTO_NAME(cli->cc_proto), + addrbuf, portbuf, + cli->cc_socket, serv->cc_socket); + + /* Textual value for log messages */ + rc = getnameinfo((sSA *)&ss, sizeof(sSS), cli->cc_remid, sizeof(cli->cc_remid), NULL, 0, NI_NUMERICHOST); + if (rc) + snprintf(cli->cc_remid, sizeof(cli->cc_remid), "[err:%s]", gai_strerror(rc)); + } + + /* SCTP-specific handlings */ +#ifndef DISABLE_SCTP + if (cli->cc_proto == IPPROTO_SCTP) { + /* Retrieve the number of streams */ + CHECK_FCT_DO( fd_sctp_get_str_info( cli->cc_socket, &cli->cc_sctp_para.str_in, &cli->cc_sctp_para.str_out ), goto error ); + if (cli->cc_sctp_para.str_out > cli->cc_sctp_para.str_in) + cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_out; + else + cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_in; + } +#endif /* DISABLE_SCTP */ + + return cli; +error: + fd_cnx_destroy(cli); + return NULL; +} + +/* Client side: connect to a remote server */ +struct cnxctx * fd_cnx_cli_connect(int proto, const sSA * sa, socklen_t addrlen) +{ + + TODO("..."); + return NULL; +} + +/* Return a string describing the connection, for debug */ +char * fd_cnx_getid(struct cnxctx * conn) +{ + CHECK_PARAMS_DO( conn, return "" ); + return conn->cc_id; } /* Start receving messages in clear (no TLS) on the connection */ int fd_cnx_start_clear(struct cnxctx * conn) { - + TODO("..."); return ENOTSUP; } /* TLS handshake a connection; no need to have called start_clear before. Reception is active if handhsake is successful */ -int fd_cnx_handshake(struct cnxctx * conn, int mode) +int fd_cnx_handshake(struct cnxctx * conn, int mode, char * priority) { TRACE_ENTRY( "%p %d", conn, mode); CHECK_PARAMS( conn && ( (mode == GNUTLS_CLIENT) || (mode == GNUTLS_SERVER) ) ); - + /* Save the mode */ conn->cc_tls_para.mode = mode; - + /* Create the master session context */ CHECK_GNUTLS_DO( gnutls_init (&conn->cc_tls_para.session, mode), return ENOMEM ); - + /* Set the algorithm suite */ + TODO("Use overwrite priority if non NULL"); CHECK_GNUTLS_DO( gnutls_priority_set( conn->cc_tls_para.session, fd_g_config->cnf_sec_data.prio_cache ), return EINVAL ); - + /* Set the credentials of this side of the connection */ CHECK_GNUTLS_DO( gnutls_credentials_set (conn->cc_tls_para.session, GNUTLS_CRD_CERTIFICATE, fd_g_config->cnf_sec_data.credentials), return EINVAL ); - + /* Request the remote credentials as well */ if (mode == GNUTLS_SERVER) { gnutls_certificate_server_set_request (conn->cc_tls_para.session, GNUTLS_CERT_REQUIRE); @@ -145,23 +347,23 @@ ASSERT(0); #endif /* DISABLE_SCTP */ } - + /* Handshake master session */ { int ret; CHECK_GNUTLS_DO( ret = gnutls_handshake(conn->cc_tls_para.session), { if (TRACE_BOOL(INFO)) { - fd_log_debug("TLS Handshake failed on socket %d : %s\n", conn->cc_socket, gnutls_strerror(ret)); + fd_log_debug("TLS Handshake failed on socket %d (%s) : %s\n", conn->cc_socket, conn->cc_id, gnutls_strerror(ret)); } return EINVAL; } ); - + /* Now verify the remote credentials are valid -- only simple test here */ CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (conn->cc_tls_para.session, &ret), return EINVAL ); if (ret) { if (TRACE_BOOL(INFO)) { - fd_log_debug("TLS: Remote certificate invalid on socket %d :\n", conn->cc_socket); + fd_log_debug("TLS: Remote certificate invalid on socket %d (%s) :\n", conn->cc_socket, conn->cc_id); if (ret & GNUTLS_CERT_INVALID) fd_log_debug(" - The certificate is not trusted (unknown CA?)\n"); if (ret & GNUTLS_CERT_REVOKED) @@ -176,30 +378,46 @@ return EINVAL; } } - + /* Other sessions in case of multi-stream SCTP are resumed from the master */ if ((conn->cc_proto == IPPROTO_SCTP) && (conn->cc_sctp_para.pairs > 0)) { #ifndef DISABLE_SCTP TODO("Init and resume all additional sessions from the master one."); #endif /* DISABLE_SCTP */ } - + + TODO("Start the connection state machine thread"); + return 0; } /* Retrieve TLS credentials of the remote peer, after handshake */ int fd_cnx_getcred(struct cnxctx * conn, const gnutls_datum_t **cert_list, unsigned int *cert_list_size) { - + TODO("..."); return ENOTSUP; } -/* Get the list of endpoints (IP addresses) of the remote peer on this object */ -int fd_cnx_getendpoints(struct cnxctx * conn, struct fd_list * senti) +/* Get the list of endpoints (IP addresses) of the local and remote peers on this conenction */ +int fd_cnx_getendpoints(struct cnxctx * conn, struct fd_list * local, struct fd_list * remote) { + TRACE_ENTRY("%p %p %p", conn, local, remote); + CHECK_PARAMS(conn); - TODO("..."); + if (local) { + /* Retrieve the local endpoint(s) of the connection */ + TODO("TCP : getsockname"); + TODO("SCTP: sctp_getladdrs / _sctp_getboundaddrs (waaad)"); + } + + if (remote) { + /* Retrieve the peer endpoint(s) of the connection */ + TODO("TCP : getpeername"); + TODO("SCTP: sctp_getpaddrs"); + + } + return ENOTSUP; } @@ -207,25 +425,35 @@ /* Get a string describing the remote peer address (ip address or fqdn) */ char * fd_cnx_getremoteid(struct cnxctx * conn) { - - TODO("..."); - return NULL; + CHECK_PARAMS_DO( conn, return "" ); + return conn->cc_remid; } /* Receive next message. if timeout is not NULL, wait only until timeout */ int fd_cnx_receive(struct cnxctx * conn, struct timespec * timeout, unsigned char **buf, size_t * len) { - + TODO("..."); return ENOTSUP; } +/* Set / reset alternate FIFO list to send FDEVP_CNX_MSG_RECV to when message is received */ +int fd_cnx_recv_setaltfifo(struct cnxctx * conn, struct fifo * alt_fifo) +{ + TRACE_ENTRY( "%p %p", conn, alt_fifo ); + CHECK_PARAMS( conn ); + + /* Let's cross fingers that there is no race condition here... */ + conn->cc_alt = alt_fifo; + + return 0; +} /* Send a message */ int fd_cnx_send(struct cnxctx * conn, unsigned char * buf, size_t len) { - + TODO("..."); return ENOTSUP; } @@ -234,8 +462,31 @@ /* Destroy a conn structure, and shutdown the socket */ void fd_cnx_destroy(struct cnxctx * conn) { + TRACE_ENTRY("%p", conn); - TODO("..."); + CHECK_PARAMS_DO(conn, return); + + TODO("End TLS session(s) if started"); + + TODO("Stop manager thread if running"); + + /* Shut the connection down */ + if (conn->cc_socket > 0) { + shutdown(conn->cc_socket, SHUT_RDWR); + } + + TODO("Empty FIFO queues"); + + /* Destroy FIFO lists */ + if (conn->cc_events) + CHECK_FCT_DO( fd_fifo_del ( &conn->cc_events ), /* continue */ ); + if (conn->cc_incoming) + CHECK_FCT_DO( fd_fifo_del ( &conn->cc_incoming ), /* continue */ ); + + /* Free the object */ + free(conn); + + /* Done! */ return; }