Initial commit
Change-Id: I6a4444e3c193dae437cd7929f4c39aba7b749efa
diff --git a/extensions/app_radgw/rgw_servers.c b/extensions/app_radgw/rgw_servers.c
new file mode 100644
index 0000000..d524f20
--- /dev/null
+++ b/extensions/app_radgw/rgw_servers.c
@@ -0,0 +1,304 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Manage the RADIUS server(s): opening sockets, receiving messages, ... */
+
+#include "rgw.h"
+
+#define RADIUS_MAX_MSG_LEN 3000
+#define RADIUS_AUTH_PORT 1812
+#define RADIUS_ACCT_PORT 1813
+
+/* Declare the rgw_servers */
+struct rgw_servs rgw_servers;
+
+void rgw_servers_dump(void)
+{
+ char ipstr[INET6_ADDRSTRLEN];
+
+ LOG_D(" auth server:");
+ LOG_D(" disabled..... : %s", rgw_servers.auth_serv.disabled ? "TRUE":"false");
+ LOG_D(" IP disabled.. : %s", rgw_servers.auth_serv.ip_disabled ? "TRUE":"false");
+ LOG_D(" IPv6 disabled : %s", rgw_servers.auth_serv.ip6_disabled ? "TRUE":"false");
+ LOG_D(" port......... : %hu", ntohs(rgw_servers.auth_serv.port));
+ inet_ntop(AF_INET, &rgw_servers.auth_serv.ip_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IP bind...... : %s", ipstr);
+ inet_ntop(AF_INET6, &rgw_servers.auth_serv.ip6_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IPv6 bind.... : %s", ipstr);
+
+ LOG_D(" acct server:");
+ LOG_D(" disabled..... : %s", rgw_servers.acct_serv.disabled ? "TRUE":"false");
+ LOG_D(" IP disabled.. : %s", rgw_servers.acct_serv.ip_disabled ? "TRUE":"false");
+ LOG_D(" IPv6 disabled : %s", rgw_servers.acct_serv.ip6_disabled ? "TRUE":"false");
+ LOG_D(" port......... : %hu", ntohs(rgw_servers.acct_serv.port));
+ inet_ntop(AF_INET, &rgw_servers.acct_serv.ip_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IP bind...... : %s", ipstr);
+ inet_ntop(AF_INET6, &rgw_servers.acct_serv.ip6_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IPv6 bind.... : %s", ipstr);
+
+}
+
+static struct servers_data {
+ int type; /* auth or acct */
+ int family; /* AF_INET or AF_INET6 */
+ int sock; /* the socket number */
+ pthread_t th; /* the running server thread, or NULL */
+ char name[10];
+} SERVERS[4];
+
+int rgw_servers_init(void)
+{
+ memset(&rgw_servers, 0, sizeof(rgw_servers));
+ memset(&SERVERS[0], 0, sizeof(SERVERS));
+
+ rgw_servers.auth_serv.port = htons(RADIUS_AUTH_PORT);
+ rgw_servers.acct_serv.port = htons(RADIUS_ACCT_PORT);
+
+ return 0;
+}
+
+static void * server_thread(void * param)
+{
+ struct servers_data * me = (struct servers_data *)param;
+
+ TRACE_ENTRY("%p", param);
+
+ CHECK_PARAMS_DO(param, return NULL);
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "radgw/%s serv", me->name);
+ fd_log_threadname ( buf );
+ }
+
+ /* Now loop on this socket, parse and queue each message received, until thread is cancelled. */
+ while (1) {
+ struct sockaddr_storage from;
+ char sa_buf[sSA_DUMP_STRLEN];
+ socklen_t fromlen = sizeof(from);
+ int len;
+ struct rgw_client * nas_info = NULL;
+ uint16_t port = 0;
+ unsigned char buf[RADIUS_MAX_MSG_LEN];
+ struct rgw_radius_msg_meta *msg = NULL;
+
+ pthread_testcancel();
+
+ /* receive the next message */
+ CHECK_SYS_DO( len = recvfrom( me->sock, &buf[0], sizeof(buf), 0, (struct sockaddr *) &from, &fromlen), break );
+
+ /* Get the port */
+ port = sSAport(&from);
+ if (!port) {
+ LOG_E("Invalid port (family: %d), discarding received %d bytes...", from.ss_family, len);
+ continue;
+ }
+
+ fd_sa_sdump_numeric(sa_buf, (sSA*)&from);
+ LOG_D("RADIUS: RCV %dB from %s", len, sa_buf);
+
+ /* Search the associated client definition, if any */
+ CHECK_FCT_DO( rgw_clients_search((struct sockaddr *) &from, &nas_info),
+ {
+ LOG_E("Discarding %d bytes received from unknown IP: %s", len, sa_buf);
+ continue;
+ } );
+
+
+ /* parse the message, loop if message is invalid */
+ CHECK_FCT_DO( rgw_msg_parse(&buf[0], len, &msg),
+ {
+ DiamId_t cliname = NULL;
+ size_t clisz;
+ CHECK_FCT_DO( rgw_clients_get_origin(nas_info, &cliname, &clisz, NULL, NULL), );
+ LOG_E( "Discarding invalid RADIUS message from '%s'", cliname);
+ rgw_clients_dispose(&nas_info);
+ continue;
+ } );
+
+ msg->serv_type = me->type;
+ msg->port = port;
+
+ rgw_msg_dump(msg, 1);
+
+ /* queue the message for a worker thread */
+ CHECK_FCT_DO( rgw_work_add(msg, nas_info), break );
+
+ /* Then wait for next incoming message */
+ }
+
+ TRACE_DEBUG(INFO, "Server thread terminated.");
+ return NULL;
+}
+
+/* Set the socket options for UDP sockets, before bind is called */
+static int _udp_setsockopt(int family, int sk)
+{
+ int ret = 0;
+ int opt;
+
+ /* In case of v6 address, force the v6only option, we use a different socket for v4 */
+ #ifdef IPV6_V6ONLY
+ if (family == AF_INET6) {
+ opt = 1;
+ ret = setsockopt(sk, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
+ if (ret != 0) {
+ ret = errno;
+ TRACE_DEBUG(INFO, "Unable to set the socket IPV6_V6ONLY option: %s", strerror(ret));
+ return ret;
+ }
+ }
+ #endif /* IPV6_V6ONLY */
+
+ return 0;
+}
+
+/* We reuse the same logic for all 4 possible servers (IP / IPv6, Auth / Acct ports) */
+#define UDPSERV( _type_, _portval_, _family_ ) { \
+ /* Check that this type / family are not disabled by configuration */ \
+ if ( (! rgw_servers. _type_ ## _serv.disabled) \
+ && ( ! rgw_servers. _type_ ## _serv.ip ## _family_ ## _disabled ) ) { \
+ struct sockaddr_in ## _family_ sin ## _family_ ; \
+ /* Create the socket */ \
+ CHECK_SYS( SERVERS[idx].sock = socket(AF_INET ## _family_, SOCK_DGRAM, 0) ); \
+ /* Set the parameters for bind into "sin" or "sin6" */ \
+ memset(& sin ## _family_, 0, sizeof(struct sockaddr_in ## _family_)); \
+ sin ## _family_ . sin ## _family_ ## _family = AF_INET ## _family_; \
+ sin ## _family_ . sin ## _family_ ## _port = rgw_servers. _type_ ## _serv . port; \
+ memcpy( &sin ## _family_ .sin ## _family_ ## _addr, \
+ &rgw_servers. _type_ ## _serv . ip ## _family_ ## _endpoint, \
+ sizeof(struct in ## _family_ ## _addr) ); \
+ /* This sockopt must be set before binding */ \
+ TRACE_DEBUG(ANNOYING, "Setting socket options..."); \
+ CHECK_FCT( _udp_setsockopt(AF_INET ## _family_, SERVERS[idx].sock) ); \
+ /* OK, now, bind */ \
+ TRACE_DEBUG(ANNOYING, "Binding " #_type_ " ip" #_family_ " server..."); \
+ CHECK_SYS( bind( SERVERS[idx].sock, \
+ (struct sockaddr *)&sin ## _family_, \
+ sizeof(struct sockaddr_in ## _family_) ) ); \
+ /* Save the server information in SERVERS structure */ \
+ SERVERS[idx].type = _portval_; \
+ SERVERS[idx].family = AF_INET ## _family_; \
+ snprintf(&SERVERS[idx].name[0], sizeof(SERVERS[idx].name), # _type_ "/ip" #_family_); \
+ /* Create the server thread */ \
+ CHECK_POSIX( pthread_create(&SERVERS[idx].th, NULL, server_thread, &SERVERS[idx]) ); \
+ idx++; \
+ } \
+}
+
+int rgw_servers_start(void)
+{
+ int idx = 0;
+
+ TRACE_ENTRY();
+
+ UDPSERV( auth, RGW_PLG_TYPE_AUTH, );
+ UDPSERV( auth, RGW_PLG_TYPE_AUTH, 6 );
+ UDPSERV( acct, RGW_PLG_TYPE_ACCT, );
+ UDPSERV( acct, RGW_PLG_TYPE_ACCT, 6 );
+
+ TRACE_DEBUG(FULL, "%d UDP servers started succesfully.", idx);
+ return 0;
+}
+
+/* Send a RADIUS message */
+int rgw_servers_send(int type, unsigned char *buf, size_t buflen, struct sockaddr *to, uint16_t to_port)
+{
+ int idx = 0;
+ int ret = 0;
+ struct sockaddr_storage sto;
+ char sa_buf[sSA_DUMP_STRLEN];
+
+ /* Find the appropriate socket to use (not sure if it is important) */
+ for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) {
+ if ( SERVERS[idx].sock && (type == SERVERS[idx].type) && (to->sa_family == SERVERS[idx].family) ) {
+ ret = 1;
+ break;
+ }
+ }
+
+ if (!ret) {
+ LOG_E( "Trying to send a message from a disabled server: %s / %s",
+ (type == RGW_PLG_TYPE_AUTH) ? "Auth" : "Acct",
+ (to->sa_family == AF_INET) ? "IP (v4)" : "IPv6");
+ return EINVAL;
+ }
+
+ /* Prepare the destination info */
+ memset(&sto, 0, sizeof(sto));
+ if (to->sa_family == AF_INET) {
+ memcpy(&sto, to, sizeof(struct sockaddr_in));
+ ((struct sockaddr_in *)&sto)->sin_port = to_port;
+ } else {
+ memcpy(&sto, to, sizeof(struct sockaddr_in6));
+ ((struct sockaddr_in6 *)&sto)->sin6_port = to_port;
+ }
+
+ fd_sa_sdump_numeric(sa_buf, (sSA*)&sto);
+ LOG_D("RADIUS: SND %zdB to %s", buflen, sa_buf);
+
+ /* Send */
+ ret = sendto(SERVERS[idx].sock, buf, buflen, 0, (struct sockaddr *)&sto, sSAlen(&sto));
+ if (ret < 0) {
+ ret = errno;
+ TRACE_DEBUG(INFO, "An error prevented sending of a RADIUS message: %s", strerror(ret));
+ return ret;
+ }
+ if (ret != buflen) {
+ TRACE_DEBUG(INFO, "Incomplete send: %d bytes / %zd", ret, buflen);
+ return EAGAIN;
+ }
+
+ /* Done :) */
+ return 0;
+}
+
+void rgw_servers_fini(void)
+{
+ int idx = 0;
+
+ for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) {
+ if (SERVERS[idx].sock == 0)
+ break;
+
+ CHECK_FCT_DO( fd_thr_term(&SERVERS[idx].th), /* continue */ );
+ close(SERVERS[idx].sock);
+ SERVERS[idx].sock = 0;
+ }
+
+}
+
+