ims_ipsec_pcscf: sec-agree implementation for IMS 1605/head
authorTsvetomir Dimitrov <tsv.dimitrov@gmail.com>
Mon, 30 Jul 2018 07:59:16 +0000 (10:59 +0300)
committerTsvetomir Dimitrov <tsv.dimitrov@gmail.com>
Mon, 30 Jul 2018 07:59:16 +0000 (10:59 +0300)
15 files changed:
src/modules/ims_ipsec_pcscf/Makefile [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/cmd.c [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/cmd.h [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/doc/Makefile [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/doc/ims_ipsec_pcscf.xml [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/doc/ims_ipsec_pcscf_admin.xml [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/ims_ipsec_pcscf_mod.c [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/ipsec.c [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/ipsec.h [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/run_spi_list_tests.sh [new file with mode: 0755]
src/modules/ims_ipsec_pcscf/spi_gen.c [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/spi_gen.h [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/spi_list.c [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/spi_list.h [new file with mode: 0644]
src/modules/ims_ipsec_pcscf/spi_list_tests.c [new file with mode: 0644]

diff --git a/src/modules/ims_ipsec_pcscf/Makefile b/src/modules/ims_ipsec_pcscf/Makefile
new file mode 100644 (file)
index 0000000..f674898
--- /dev/null
@@ -0,0 +1,20 @@
+#
+# registrar module makefile
+#
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=ims_ipsec_pcscf.so
+
+ifneq ($(OS),darwin)
+       LIBS += -lpthread
+       LIBS += -lmnl
+endif
+
+DEFS+=-DOPENSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/ims/kamailio_ims
+include ../../Makefile.modules
diff --git a/src/modules/ims_ipsec_pcscf/cmd.c b/src/modules/ims_ipsec_pcscf/cmd.c
new file mode 100644 (file)
index 0000000..d94e622
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
+ * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
+ *
+ * The initial version of this code was written by Dragos Vingarzan
+ * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
+ * Fruanhofer Institute. It was and still is maintained in a separate
+ * branch of the original SER. We are therefore migrating it to
+ * Kamailio/SR and look forward to maintaining it from here on out.
+ * 2011/2012 Smile Communications, Pty. Ltd.
+ * ported/maintained/improved by
+ * Jason Penton (jason(dot)penton(at)smilecoms.com and
+ * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
+ * effort to add full IMS support to Kamailio/SR using a new and
+ * improved architecture
+ *
+ * NB: Alot of this code was originally part of OpenIMSCore,
+ * FhG Fokus.
+ * Copyright (C) 2004-2006 FhG Fokus
+ * Thanks for great work! This is an effort to
+ * break apart the various CSCF functions into logically separate
+ * components. We hope this will drive wider use. We also feel
+ * that in this way the architecture is more complete and thereby easier
+ * to manage in the Kamailio/SR environment
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../core/parser/msg_parser.h"
+#include "../../core/socket_info.h"
+#include "../../lib/ims/ims_getters.h"
+#include "../../modules/tm/tm_load.h"
+#include "../ims_usrloc_pcscf/usrloc.h"
+
+#include "ipsec.h"
+#include "spi_gen.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+extern str ipsec_listen_addr;
+extern short ipsec_listen_port;
+extern short ipsec_server_port;
+extern short ipsec_client_port;
+
+// check http://www.asipto.com/pub/kamailio-devel-guide/#c16return_values
+const int IPSEC_CMD_FAIL = -1;
+const int IPSEC_CMD_SUCCESS = 1;
+
+extern usrloc_api_t ul;
+extern struct tm_binds tmb;
+
+
+static str get_www_auth_param(const char* param_name, str www_auth)
+{
+    str val = {0,0};
+    int i = 0;
+    int param_len = strlen(param_name);
+    int start = 0;
+    int end = 0;
+
+    for(i = 0; i < www_auth.len; i++) {
+        if (www_auth.s[i] == ' ') continue;
+
+        if(strncasecmp(www_auth.s+i, param_name, param_len) == 0) {
+            i += param_len;
+
+            //find first double quote
+            while(www_auth.s[i] != '"' && i < www_auth.len) i++;
+            i++; //and skip it
+
+            if (i == www_auth.len)
+                return val; //error
+            start = i;
+            i++;
+
+            //find second double quote
+            while(www_auth.s[i] != '"' && i < www_auth.len) i++;
+            if (i == www_auth.len)
+                return val; //error
+            end = i;
+            i++;
+
+            val.s = www_auth.s + start;
+            val.len = end - start;
+            break;
+        }
+
+        //parameter not relevant - fast forward
+        do { i++; } while (www_auth.s[i] != ',' && i < www_auth.len);
+    }
+
+    return val;
+}
+
+
+static int fill_contact(struct pcontact_info* ci, struct sip_msg* m)
+{
+    contact_body_t* cb = NULL;
+    struct via_body* vb = NULL;
+    unsigned short port, proto = 0;
+    struct sip_msg* req = NULL;
+
+
+    if(!ci) {
+        LM_ERR("fill_contact() called with null ptr\n");
+        return -1;
+    }
+
+    memset(ci, 0, sizeof(struct pcontact_info));
+
+
+    if(m->first_line.type == SIP_REQUEST) {
+        struct sip_uri uri;
+        memset(&uri, 0, sizeof(struct sip_uri));
+
+        if(parse_uri(m->first_line.u.request.uri.s, m->first_line.u.request.uri.len, &uri)) {
+            LM_ERR("Can't parse the request URI from first line\n");
+            return -1;
+        }
+
+        // populate host,port, aor in CI
+        ci->via_host = uri.host;
+        ci->via_port = uri.port_no ? uri.port_no : 5060;
+        ci->via_prot = proto;
+        ci->aor = m->first_line.u.request.uri;
+
+        req = m;
+    }
+    else if(m->first_line.type == SIP_REPLY) {
+
+        cb = cscf_parse_contacts(m);
+        vb = cscf_get_ue_via(m);
+        port = vb->port?vb->port:5060;
+        proto = vb->proto;
+
+        struct cell *t = tmb.t_gett();
+        if (!t || t == (void*) -1) {
+            LM_ERR("fill_contact(): Reply without transaction\n");
+            return -1;
+        }
+
+        req = t->uas.request;
+
+        cb = cscf_parse_contacts(req);
+        if (!cb || (!cb->contacts)) {
+            LM_ERR("fill_contact(): No contact headers\n");
+            return -3;
+        }
+
+        // populate CI with bare minimum
+        ci->via_host = vb->host;
+        ci->via_port = port;
+        ci->via_prot = proto;
+        ci->aor = cb->contacts->uri;
+    }
+
+
+    char* srcip;
+    srcip = pkg_malloc(50);
+
+    ci->received_host.len = ip_addr2sbuf(&req->rcv.src_ip, srcip, 50);
+    ci->received_host.s = srcip;
+    ci->received_port = req->rcv.src_port;
+    ci->received_proto = req->rcv.proto;
+
+    // Set to default, if not set:
+    if (ci->received_port == 0)
+        ci->received_port = 5060;
+
+
+    return 0;
+}
+
+// Get CK and IK from WWW-Authenticate
+static int get_ck_ik(const struct sip_msg* m, str* ck, str* ik)
+{
+    struct hdr_field *www_auth_hdr = NULL;
+    str www_auth;
+    memset(&www_auth, 0, sizeof(str));
+
+    www_auth = cscf_get_authenticate(m, &www_auth_hdr);
+
+    *ck = get_www_auth_param("ck", www_auth);
+    if (ck->len == 0) {
+        LM_ERR("Error getting CK\n");
+        return -1;
+    }
+
+    *ik = get_www_auth_param("ik", www_auth);
+    if (ck->len == 0) {
+        LM_ERR("Error getting IK\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int update_contact_ipsec_params(ipsec_t* s, const struct sip_msg* m)
+{
+    // Get CK and IK
+    str ck, ik;
+    if(get_ck_ik(m, &ck, &ik) != 0) {
+        return -1;
+    }
+
+    // Save CK and IK in the contact
+    s->ck.s = shm_malloc(ck.len);
+    if(s->ck.s == NULL) {
+        LM_ERR("Error allocating memory for CK\n");
+        return -1;
+    }
+    memcpy(s->ck.s, ck.s, ck.len);
+    s->ck.len = ck.len;
+
+    s->ik.s = shm_malloc(ik.len);
+    if(s->ik.s == NULL) {
+        LM_ERR("Error allocating memory for IK\n");
+        shm_free(s->ck.s);
+        s->ck.s = NULL; s->ck.len = 0;
+        s->ik.s = NULL; s->ik.len = 0;
+        return -1;
+    }
+    memcpy(s->ik.s, ik.s, ik.len);
+    s->ik.len = ik.len;
+
+    // Generate SPI
+    if((s->spi_pc = acquire_spi()) == 0) {
+        LM_ERR("Error generating client SPI for IPSEC tunnel creation\n");
+        shm_free(s->ck.s);
+        s->ck.s = NULL; s->ck.len = 0;
+        shm_free(s->ik.s);
+        s->ik.s = NULL; s->ik.len = 0;
+        return -1;
+    }
+
+    if((s->spi_ps = acquire_spi()) == 0) {
+        LM_ERR("Error generating server SPI for IPSEC tunnel creation\n");
+        shm_free(s->ck.s);
+        s->ck.s = NULL; s->ck.len = 0;
+        shm_free(s->ik.s);
+        s->ik.s = NULL; s->ik.len = 0;
+        return -1;
+    }
+
+    return 0;
+}
+
+static int create_ipsec_tunnel(const str remote_addr, ipsec_t* s)
+{
+    struct mnl_socket* sock = init_mnl_socket();
+    if (sock == NULL) {
+        return -1;
+    }
+
+    LM_DBG("Creating security associations: Local IP: %.*s client port: %d server port: %d; UE IP: %.*s; client port %d server port %d\n",
+            ipsec_listen_addr.len, ipsec_listen_addr.s, ipsec_client_port, ipsec_server_port,
+            remote_addr.len, remote_addr.s, s->port_uc, s->port_us);
+
+    // P-CSCF 'client' tunnel to UE 'server'
+    add_sa    (sock, ipsec_listen_addr, remote_addr, ipsec_client_port, s->port_us, s->spi_us, s->ck, s->ik);
+    add_policy(sock, ipsec_listen_addr, remote_addr, ipsec_client_port, s->port_us, s->spi_us, IPSEC_POLICY_DIRECTION_OUT);
+
+    // UE 'client' to P-CSCF 'server' tunnel
+    add_sa    (sock, remote_addr, ipsec_listen_addr, s->port_uc, ipsec_server_port, s->spi_ps, s->ck, s->ik);
+    add_policy(sock, remote_addr, ipsec_listen_addr, s->port_uc, ipsec_server_port, s->spi_ps, IPSEC_POLICY_DIRECTION_IN);
+
+    close_mnl_socket(sock);
+
+    return 0;
+}
+
+static int destroy_ipsec_tunnel(const str remote_addr, ipsec_t* s)
+{
+    struct mnl_socket* sock = init_mnl_socket();
+    if (sock == NULL) {
+        return -1;
+    }
+
+    LM_DBG("Destroying security associations: Local IP: %.*s client port: %d server port: %d; UE IP: %.*s; client port %d server port %d\n",
+            ipsec_listen_addr.len, ipsec_listen_addr.s, ipsec_client_port, ipsec_server_port,
+            remote_addr.len, remote_addr.s, s->port_uc, s->port_us);
+
+    // P-CSCF 'client' tunnel to UE 'server'
+    remove_sa    (sock, ipsec_listen_addr, remote_addr, ipsec_client_port, s->port_us, s->spi_us);
+    remove_policy(sock, ipsec_listen_addr, remote_addr, ipsec_client_port, s->port_us, s->spi_us, IPSEC_POLICY_DIRECTION_OUT);
+
+    // UE 'client' to P-CSCF 'server' tunnel
+    remove_sa    (sock, remote_addr, ipsec_listen_addr, s->port_uc, ipsec_server_port, s->spi_ps);
+    remove_policy(sock, remote_addr, ipsec_listen_addr, s->port_uc, ipsec_server_port, s->spi_ps, IPSEC_POLICY_DIRECTION_IN);
+
+    // Release SPIs
+    release_spi(s->spi_uc);
+    release_spi(s->spi_us);
+
+
+    close_mnl_socket(sock);
+    return 0;
+}
+
+static void on_expire(struct pcontact *c, int type, void *param)
+{
+    if(type != PCSCF_CONTACT_EXPIRE && type != PCSCF_CONTACT_DELETE) {
+        LM_ERR("Unexpected event type %d\n", type);
+        return;
+    }
+
+
+    if(c->security_temp == NULL) {
+        LM_ERR("No security parameters found in contact\n");
+        return;
+    }
+
+    //get security parameters
+    if(c->security_temp->type != SECURITY_IPSEC ) {
+        LM_ERR("Unsupported security type: %d\n", c->security_temp->type);
+        return;
+    }
+
+    destroy_ipsec_tunnel(c->received_host, c->security_temp->data.ipsec);
+}
+
+int add_supported_secagree_header(struct sip_msg* m)
+{
+    // Add sec-agree header in the reply
+    const char* supported_sec_agree = "Supported: sec-agree\r\n";
+    const int supported_sec_agree_len = 22;
+
+    str* supported = NULL;
+    if((supported = pkg_malloc(sizeof(str))) == NULL) {
+        LM_ERR("Error allocating pkg memory for supported header\n");
+        return -1;
+    }
+
+    if((supported->s = pkg_malloc(supported_sec_agree_len)) == NULL) {
+        LM_ERR("Error allcationg pkg memory for supported header str\n");
+        return -1;
+    }
+    memcpy(supported->s, supported_sec_agree, supported_sec_agree_len);
+    supported->len = supported_sec_agree_len;
+
+    if(cscf_add_header(m, supported, HDR_SUPPORTED_T) != 1) {
+        LM_ERR("Error adding security header to reply!\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+int add_security_server_header(struct sip_msg* m, ipsec_t* s)
+{
+    // allocate memory for the header itself
+    str* sec_header = NULL;
+    if((sec_header = pkg_malloc(sizeof(str))) == NULL) {
+        LM_ERR("Error allocating pkg memory for security header\n");
+        return -1;
+    }
+    memset(sec_header, 0, sizeof(str));
+
+    // create a temporary buffer and set the value in it
+    char sec_hdr_buf[1024];
+    memset(sec_hdr_buf, 0, sizeof(sec_hdr_buf));
+    sec_header->len = snprintf(sec_hdr_buf, sizeof(sec_hdr_buf) - 1,
+                                "Security-Server: ipsec-3gpp;prot=esp;mod=trans;spi-c=%d;spi-s=%d;port-c=%d;port-s=%d;alg=%.*s;ealg=%.*s\r\n",
+                                s->spi_pc, s->spi_ps, ipsec_client_port, ipsec_server_port,
+                                s->r_alg.len, s->r_alg.s,
+                                s->r_ealg.len, s->r_ealg.s
+                              );
+
+    // copy to the header and add
+    if((sec_header->s = pkg_malloc(sec_header->len)) == NULL) {
+        LM_ERR("Error allocating pkg memory for security header payload\n");
+        return -1;
+    }
+    memcpy(sec_header->s, sec_hdr_buf, sec_header->len);
+
+    // add security-server header in reply
+    if(cscf_add_header(m, sec_header, HDR_OTHER_T) != 1) {
+        LM_ERR("Error adding security header to reply!\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+int ipsec_create(struct sip_msg* m, udomain_t* d)
+{
+    pcontact_t* pcontact = NULL;
+    struct pcontact_info ci;
+    int ret = IPSEC_CMD_FAIL;   // FAIL by default
+
+    // Find the contact
+    if(fill_contact(&ci, m) != 0) {
+        LM_ERR("Error filling in contact data\n");
+        return ret;
+    }
+
+    ul.lock_udomain(d, &ci.via_host, ci.via_port, ci.via_prot);
+
+    if (ul.get_pcontact(d, &ci, &pcontact) != 0) {
+        LM_ERR("Contact doesn't exist\n");
+        goto cleanup;
+    }
+
+    // Get security parameters
+    if(pcontact->security_temp == NULL) {
+        LM_ERR("No security parameters found in contact\n");
+        goto cleanup;
+    }
+
+    if(pcontact->security_temp->type != SECURITY_IPSEC ) {
+        LM_ERR("Unsupported security type: %d\n", pcontact->security_temp->type);
+        goto cleanup;
+    }
+
+    ipsec_t* s = pcontact->security_temp->data.ipsec;
+
+    if(update_contact_ipsec_params(s, m) != 0) {
+        goto cleanup;
+    }
+
+    if(create_ipsec_tunnel(ci.received_host, s) != 0) {
+        goto cleanup;
+    }
+
+    // TODO: Save security_tmp to security!!!!!
+
+    if (ul.update_pcontact(d, &ci, pcontact) != 0) {
+        LM_ERR("Error updating contact\n");
+        goto cleanup;
+    }
+
+    // Destroy the tunnel, if the contact expires
+    if(ul.register_ulcb(pcontact, PCSCF_CONTACT_EXPIRE|PCSCF_CONTACT_DELETE, on_expire, NULL) != 1) {
+        LM_ERR("Error subscribing for contact\n");
+        goto cleanup;
+    }
+
+
+    if(add_supported_secagree_header(m) != 0) {
+        goto cleanup;
+    }
+
+    if(add_security_server_header(m, s) != 0) {
+        goto cleanup;
+    }
+
+    ret = IPSEC_CMD_SUCCESS;    // all good, set ret to SUCCESS, and exit
+
+cleanup:
+    // Do not free str* sec_header! It will be freed in data_lump.c -> free_lump()
+    ul.unlock_udomain(d, &ci.via_host, ci.via_port, ci.via_prot);
+    pkg_free(ci.received_host.s);
+    return ret;
+}
+
+
+int ipsec_forward(struct sip_msg* m, udomain_t* d)
+{
+    struct pcontact_info ci;
+    pcontact_t* pcontact = NULL;
+    int ret = IPSEC_CMD_FAIL; // FAIL by default
+
+    //
+    // Find the contact
+    //
+    if(fill_contact(&ci, m) != 0) {
+        LM_ERR("Error filling in contact data\n");
+        return ret;
+    }
+
+    ul.lock_udomain(d, &ci.via_host, ci.via_port, ci.via_prot);
+
+    if (ul.get_pcontact(d, &ci, &pcontact) != 0) {
+        LM_ERR("Contact doesn't exist\n");
+        goto cleanup;
+    }
+
+
+    if(pcontact->security_temp == NULL) {
+        LM_ERR("No security parameters found in contact\n");
+        goto cleanup;
+    }
+
+    //get security parameters
+    if(pcontact->security_temp->type != SECURITY_IPSEC ) {
+        LM_ERR("Unsupported security type: %d\n", pcontact->security_temp->type);
+        goto cleanup;
+    }
+
+    ipsec_t* s = pcontact->security_temp->data.ipsec;
+
+
+    // Update the destination
+    //
+    //       from sec-agree
+    //            v
+    // sip:host:port
+    //       ^
+    //    from URI
+    //int uri_len = 4 /* strlen("sip:") */ + ci.via_host.len + 5 /* max len of port number */ ;
+
+    if(m->dst_uri.s) {
+        pkg_free(m->dst_uri.s);
+        m->dst_uri.s = NULL;
+        m->dst_uri.len = 0;
+    }
+
+    char buf[1024];
+    int buf_len = snprintf(buf, sizeof(buf) - 1, "sip:%.*s:%d", ci.via_host.len, ci.via_host.s, s->port_us);
+
+    if((m->dst_uri.s = pkg_malloc(buf_len)) == NULL) {
+        LM_ERR("Error allocating memory for dst_uri\n");
+        goto cleanup;
+    }
+
+    memcpy(m->dst_uri.s, buf, buf_len);
+    m->dst_uri.len = buf_len;
+
+    // Set send socket
+    struct socket_info * client_sock = grep_sock_info(&ipsec_listen_addr, ipsec_client_port, PROTO_UDP);
+    if(!client_sock) {
+        LM_ERR("Error calling grep_sock_info() for ipsec client port in ipsec_forward\n");
+        return -1;
+    }
+    m->force_send_socket = client_sock;
+
+   // Set destination info
+    struct dest_info dst_info;
+    dst_info.send_sock = client_sock;
+#ifdef USE_DNS_FAILOVER
+    if (!uri2dst(NULL, &dst_info, m, &m->dst_uri, PROTO_UDP)) {
+#else
+    if (!uri2dst(&dst_info, m, &m->dst_uri, PROTO_UDP)) {
+#endif
+        LM_ERR("Error converting dst_uri (%.*s) to struct dst_info\n", m->dst_uri.len, m->dst_uri.s);
+        goto cleanup;
+    }
+
+    // Update dst_info in message
+    if(m->first_line.type == SIP_REPLY) {
+        struct cell *t = tmb.t_gett();
+        if (!t) {
+            LM_ERR("Error getting transaction\n");
+            goto cleanup;
+        }
+        t->uas.response.dst = dst_info;
+    }
+
+    LM_DBG("Destination changed to %.*s\n", m->dst_uri.len, m->dst_uri.s);
+
+    ret = IPSEC_CMD_SUCCESS; // all good, return SUCCESS
+
+    if(add_supported_secagree_header(m) != 0) {
+        goto cleanup;
+    }
+
+    if(add_security_server_header(m, s) != 0) {
+        goto cleanup;
+    }
+
+    ret = IPSEC_CMD_SUCCESS;    // all good, set ret to SUCCESS, and exit
+
+cleanup:
+    ul.unlock_udomain(d, &ci.via_host, ci.via_port, ci.via_prot);
+    pkg_free(ci.received_host.s);
+    return ret;
+}
+
+
+int ipsec_destroy(struct sip_msg* m, udomain_t* d)
+{
+    struct pcontact_info ci;
+    pcontact_t* pcontact = NULL;
+    int ret = IPSEC_CMD_FAIL; // FAIL by default
+
+    //
+    // Find the contact
+    //
+    if(fill_contact(&ci, m) != 0) {
+        LM_ERR("Error filling in contact data\n");
+        return ret;
+    }
+
+    ul.lock_udomain(d, &ci.via_host, ci.via_port, ci.via_prot);
+
+    if (ul.get_pcontact(d, &ci, &pcontact) != 0) {
+        LM_ERR("Contact doesn't exist\n");
+        goto cleanup;
+    }
+
+
+    if(pcontact->security_temp == NULL) {
+        LM_ERR("No security parameters found in contact\n");
+        goto cleanup;
+    }
+
+    //get security parameters
+    if(pcontact->security_temp->type != SECURITY_IPSEC ) {
+        LM_ERR("Unsupported security type: %d\n", pcontact->security_temp->type);
+        goto cleanup;
+    }
+
+    destroy_ipsec_tunnel(ci.received_host, pcontact->security_temp->data.ipsec);
+
+    ret = IPSEC_CMD_SUCCESS;    // all good, set ret to SUCCESS, and exit
+
+cleanup:
+    ul.unlock_udomain(d, &ci.via_host, ci.via_port, ci.via_prot);
+    pkg_free(ci.received_host.s);
+    return ret;
+}
diff --git a/src/modules/ims_ipsec_pcscf/cmd.h b/src/modules/ims_ipsec_pcscf/cmd.h
new file mode 100644 (file)
index 0000000..6102da2
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
+ * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
+ *
+ * The initial version of this code was written by Dragos Vingarzan
+ * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
+ * Fruanhofer Institute. It was and still is maintained in a separate
+ * branch of the original SER. We are therefore migrating it to
+ * Kamailio/SR and look forward to maintaining it from here on out.
+ * 2011/2012 Smile Communications, Pty. Ltd.
+ * ported/maintained/improved by
+ * Jason Penton (jason(dot)penton(at)smilecoms.com and
+ * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
+ * effort to add full IMS support to Kamailio/SR using a new and
+ * improved architecture
+ *
+ * NB: Alot of this code was originally part of OpenIMSCore,
+ * FhG Fokus.
+ * Copyright (C) 2004-2006 FhG Fokus
+ * Thanks for great work! This is an effort to
+ * break apart the various CSCF functions into logically separate
+ * components. We hope this will drive wider use. We also feel
+ * that in this way the architecture is more complete and thereby easier
+ * to manage in the Kamailio/SR environment
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef IPSEC_CMD_H
+#define IPSEC_CMD_H
+
+struct sip_msg;
+struct udomain_t;
+
+int ipsec_create(struct sip_msg* m, udomain_t* d);
+int ipsec_forward(struct sip_msg* m, udomain_t* d);
+int ipsec_destroy(struct sip_msg* m, udomain_t* d);
+
+#endif /* IPSEC_CMD_H */
\ No newline at end of file
diff --git a/src/modules/ims_ipsec_pcscf/doc/Makefile b/src/modules/ims_ipsec_pcscf/doc/Makefile
new file mode 100644 (file)
index 0000000..df681ab
--- /dev/null
@@ -0,0 +1,4 @@
+docs = ims_ipsec_pcscf.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module
diff --git a/src/modules/ims_ipsec_pcscf/doc/ims_ipsec_pcscf.xml b/src/modules/ims_ipsec_pcscf/doc/ims_ipsec_pcscf.xml
new file mode 100644 (file)
index 0000000..bcc96ae
--- /dev/null
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+]>
+<book>
+  <bookinfo>
+    <title>The IMS IPSec-Registrar Module</title>
+
+    <authorgroup>
+      <author>
+        <firstname>Dragos Vingarzan</firstname>
+
+        <surname/>
+
+        <affiliation>
+          <orgname>FhG Fokus</orgname>
+        </affiliation>
+
+        <email>Dragos.Vingarzan@fokus.fraunhofer.de</email>
+      </author>
+
+      <author>
+        <firstname>Jason</firstname>
+
+        <surname>Penton</surname>
+
+        <affiliation>
+          <orgname>Smile Communications</orgname>
+        </affiliation>
+
+        <email>jason.penton@smilecoms.com</email>
+      </author>
+
+      <author>
+        <firstname>Richard</firstname>
+
+        <surname>Good</surname>
+        <affiliation>
+          <orgname>Smile Communications</orgname>
+        </affiliation>
+
+        <email>richard.good@smilecoms.com</email>
+      </author>
+
+      <author>
+        <firstname>Carsten</firstname>
+
+        <surname>Bock</surname>
+        <affiliation>
+          <orgname>ng-voice GmbH</orgname>
+        </affiliation>
+
+        <email>carsten@ng-voice.com</email>
+      </author>
+
+      <author>
+        <firstname>Tsvetomir</firstname>
+
+        <surname>Dimitrov</surname>
+
+        <email>tsv.dimitrov@gmail.com</email>
+      </author>
+    </authorgroup>
+
+    <copyright>
+      <year>2007</year>
+      <holder>FhG FOKUS</holder>
+    </copyright>
+
+    <copyright>
+      <year>2012</year>
+
+      <holder>Smile Communications</holder>
+    </copyright>
+    <copyright>
+      <year>2015</year>
+      <holder>ng-voice GmbH</holder>
+    </copyright>
+  </bookinfo>
+
+  <toc/>
+
+  <xi:include href="ims_ipsec_pcscf_admin.xml"
+              xmlns:xi="http://www.w3.org/2001/XInclude"/>
+</book>
diff --git a/src/modules/ims_ipsec_pcscf/doc/ims_ipsec_pcscf_admin.xml b/src/modules/ims_ipsec_pcscf/doc/ims_ipsec_pcscf_admin.xml
new file mode 100644 (file)
index 0000000..0f1c08e
--- /dev/null
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+]>
+<chapter>
+  <title>&adminguide;</title>
+
+  <section>
+    <title>Overview</title>
+
+    <para>This module contains methods for IPSec initialisation/deinitialisation related for usage of Kamailio as a
+       Proxy-CSCF.</para>
+
+    <para>Important notice!!! Currently the module doesn't clear the IPSec tunnels( Policies and Security associations, in XFRM terms) on startup and shutdown.
+   This will be fixed in future releases. For the moment the following command can be used to clear the tunnels manually:</para>
+
+    <para>ip xfrm state deleteall</para>
+    <para>ip xfrm policy deleteall</para>
+
+    <para>Please note that this will clear ALL xfrm states and policies. This means that it will interfere with other IPSec applications on the machine.
+   If this is the case, Kamailio's states and policies should be cleared manually.</para>
+  </section>
+
+  <section>
+    <title>Dependencies</title>
+
+    <section>
+      <title>&kamailio; Modules</title>
+
+      <para>The Following modules must be loaded before this module:</para>
+
+      <itemizedlist>
+        <listitem>
+          <para>Usrloc PCSCF</para>
+        </listitem>
+
+        <listitem>
+          <para>TM</para>
+        </listitem>
+      </itemizedlist>
+    </section>
+
+    <section>
+      <title>External Libraries or Applications</title>
+
+      <para>This modules requires the internal IMS library and libmnl for operating with netlink sockets.</para>
+    </section>
+  </section>
+
+  <section>
+    <title>Parameters</title>
+
+    <section>
+      <title><varname>ipsec_listen_addr</varname> (string)</title>
+
+      <para>IP address which the Proxy-CSCF will use for incoming/outgoing SIP traffic over IPSec.</para>
+      <para><emphasis>Default value is "127.0.0.1"</emphasis></para>
+
+      <example>
+        <title><varname>ipsec_listen_addr</varname> parameter usage</title>
+
+        <programlisting format="linespecific">
+...
+modparam("ims_ipsec_pcscf", "ipsec_listen_addr", "127.0.0.1")
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section>
+      <title><varname>ipsec_client_port</varname> (int)</title>
+
+      <para>Port number which will be bound for incoming (server) IPSec traffic.</para>
+
+      <para><emphasis>Default value is 5963.</emphasis></para>
+
+      <example>
+        <title><varname>ipsec_client_port</varname> parameter usage</title>
+
+        <programlisting format="linespecific">
+...
+modparam("ims_ipsec_pcscf", "ipsec_client_port", 5062)
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section>
+      <title><varname>ipsec_server_port</varname> (int)</title>
+
+      <para>Port number which will be bound for incoming (server) IPSec traffic.</para>
+
+      <para><emphasis>Default value is 5063.</emphasis></para>
+
+      <example>
+        <title><varname>ipsec_server_port</varname> parameter usage</title>
+
+        <programlisting format="linespecific">
+...
+modparam("ims_ipsec_pcscf", "ipsec_server_port", 5063)
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section>
+      <title><varname>ipsec_spi_id_start</varname> (int)</title>
+
+      <para>Each IPSec tunnel has a unique system-wide identifier. This and the following option
+      allows to tune the SPIs used by Kamailio in order to avoid collisions with other IPSec useres.
+      If Kamailio is the only process on the system which uses IPSec,
+      don't bother with this option.</para>
+
+      <para><emphasis>Default value is 100.</emphasis></para>
+
+      <example>
+        <title><varname>ipsec_spi_id_start</varname> parameter usage</title>
+
+        <programlisting format="linespecific">
+...
+modparam("ims_ipsec_pcscf", "ipsec_spi_id_start", 100)
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section>
+      <title><varname>ipsec_spi_id_range</varname> (int)</title>
+
+      <para>How many SPIs to be allocated for the process. E.g. if ipsec_spi_id_start = 100
+      and ipsec_spi_id_range = 1000, SPIs between 100 and 1100 will be used.</para>
+
+      <para><emphasis>Default value is 1000.</emphasis></para>
+
+      <example>
+        <title><varname>ipsec_spi_id_range</varname> parameter usage</title>
+
+        <programlisting format="linespecific">
+...
+modparam("ims_ipsec_pcscf", "ipsec_spi_id_range", 1000)
+...
+        </programlisting>
+      </example>
+    </section>
+  </section>
+
+  <section>
+    <title>Functions</title>
+
+    <section>
+      <title><function moreinfo="none">ipsec_create(domain)</function></title>
+
+      <para>This function creates IPSec SA and Policy based on the parameters sent
+    in Security-Client header in the REGISTER message. It's called when OK
+    is received. The function also adds Security-Server header to the
+    REGISTER.</para>
+
+      <para>Meaning of the parameters is as follows:</para>
+      <itemizedlist>
+        <listitem>
+          <para>
+          <emphasis>domain</emphasis> - Logical domain within the registrar.
+          If a database is used then this must be name of the table which
+          stores the contacts.
+          </para>
+        </listitem>
+                 </itemizedlist>
+      <example>
+        <title>ipsec_create</title>
+
+        <programlisting format="linespecific">
+...
+ipsec_create("location");
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section>
+      <title><function moreinfo="none">ipsec_forward(domain)</function></title>
+      <para>The function processes redirects outgoing message via the IPSec tunnel
+      initiated with ipsec_create().</para>
+                 <para>Meaning of the parameters is as follows:</para>
+      <itemizedlist>
+        <listitem>
+          <para>
+          <emphasis>domain</emphasis> - Logical domain within the registrar.
+          If a database is used then this must be name of the table which
+          stores the contacts.
+          </para>
+        </listitem>
+      </itemizedlist>
+      <example>
+        <title>ipsec_forward</title>
+
+        <programlisting format="linespecific">
+...
+ipsec_forward("location");
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section>
+      <title><function moreinfo="none">ipsec_destroy(domain)</function></title>
+      <para>The function destroys IPSec tunnel, created with ipsec_create.</para>
+      <para>Meaning of the parameters is as follows:</para>
+      <itemizedlist>
+        <listitem>
+          <para>
+          <emphasis>domain</emphasis> - Logical domain within the registrar.
+          If a database is used then this must be name of the table which
+          stores the contacts.
+          </para>
+        </listitem>
+                 </itemizedlist>
+      <example>
+        <title>ipsec_forward</title>
+
+        <programlisting format="linespecific">
+...
+ipsec_destroy("location");
+...
+        </programlisting>
+      </example>
+    </section>
+  </section>
+</chapter>
diff --git a/src/modules/ims_ipsec_pcscf/ims_ipsec_pcscf_mod.c b/src/modules/ims_ipsec_pcscf/ims_ipsec_pcscf_mod.c
new file mode 100644 (file)
index 0000000..c265b87
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../core/sr_module.h"
+#include "../../modules/tm/tm_load.h"
+#include "../ims_usrloc_pcscf/usrloc.h"
+
+#include "cmd.h"
+#include "spi_gen.h"
+
+
+MODULE_VERSION
+
+usrloc_api_t ul;                                               /**!< Structure containing pointers to usrloc functions*/
+struct tm_binds tmb;                                   /**!< TM API structure */
+
+
+str ipsec_listen_addr = str_init("127.0.0.1");
+int ipsec_client_port =  5062;
+int ipsec_server_port =  5063;
+int spi_id_start = 100;
+int spi_id_range = 1000;
+
+/*! \brief Module init & destroy function */
+static int  mod_init(void);
+static int  child_init(int);
+static void mod_destroy(void);
+static int w_create(struct sip_msg* _m, char* _d, char* _cflags);
+static int w_forward(struct sip_msg* _m, char* _d, char* _cflags);
+static int w_destroy(struct sip_msg* _m, char* _d, char* _cflags);
+
+/*! \brief Fixup functions */
+static int domain_fixup(void** param, int param_no);
+static int save_fixup2(void** param, int param_no);
+
+/*! \brief
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+       {"ipsec_create",  (cmd_function)w_create,  1, save_fixup2, 0, ONREPLY_ROUTE },
+       {"ipsec_forward", (cmd_function)w_forward, 1, save_fixup2, 0, REQUEST_ROUTE | ONREPLY_ROUTE },
+       {"ipsec_destroy", (cmd_function)w_destroy, 1, save_fixup2, 0, REQUEST_ROUTE | ONREPLY_ROUTE },
+       {0, 0, 0, 0, 0, 0}
+};
+
+/*! \brief
+ * Exported parameters
+ */
+static param_export_t params[] = {
+       {"ipsec_listen_addr",   PARAM_STR, &ipsec_listen_addr   },
+       {"ipsec_client_port",   INT_PARAM, &ipsec_client_port   },
+       {"ipsec_server_port",   INT_PARAM, &ipsec_server_port   },
+       {"ipsec_spi_id_start",  INT_PARAM, &spi_id_start},
+       {"ipsec_spi_id_range",  INT_PARAM, &spi_id_range},
+       {0, 0, 0}
+};
+
+
+/*! \brief We expose internal variables via the statistic framework below.*/
+stat_export_t mod_stats[] = {
+       {0, 0, 0}
+};
+
+
+static pv_export_t mod_pvs[] = {
+    {{0, 0}, 0, 0, 0, 0, 0, 0, 0}
+};
+
+/*! \brief
+ * Module exports structure
+ */
+struct module_exports exports = {
+       "ims_ipsec_pcscf",
+       DEFAULT_DLFLAGS, /* dlopen flags */
+       cmds,           /* Exported functions */
+       params,         /* Exported parameters */
+       mod_stats,      /* exported statistics */
+       0,              /* exported MI functions */
+       mod_pvs,        /* exported pseudo-variables */
+       0,              /* extra processes */
+       mod_init,       /* module initialization function */
+       0,
+       mod_destroy,    /* destroy function */
+       child_init,     /* Per-child init function */
+};
+
+
+/*! \brief
+ * Initialize parent
+ */
+static int mod_init(void) {
+    struct socket_info * bind_addr = NULL;
+    char addr[128];
+    if(ipsec_listen_addr.len > sizeof(addr)-1) {
+        LM_ERR("Bad value for ipsec listen address: %.*s\n", ipsec_listen_addr.len, ipsec_listen_addr.s);
+        return -1;
+    }
+    memset(addr, 0, sizeof(addr));
+    memcpy(addr, ipsec_listen_addr.s, ipsec_listen_addr.len);
+
+       bind_usrloc_t bind_usrloc;
+
+       bind_usrloc = (bind_usrloc_t) find_export("ul_bind_ims_usrloc_pcscf", 1, 0);
+       if (!bind_usrloc) {
+               LM_ERR("can't bind ims_usrloc_pcscf\n");
+               return -1;
+       }
+
+       if (bind_usrloc(&ul) < 0) {
+               return -1;
+       }
+       LM_DBG("Successfully bound to PCSCF Usrloc module\n");
+
+       /* load the TM API */
+       if (load_tm_api(&tmb) != 0) {
+               LM_ERR("can't load TM API\n");
+               return -1;
+       }
+       LM_DBG("Successfully bound to TM module\n");
+
+
+    //add listen interfaces
+    if(add_listen_iface(addr, NULL, ipsec_client_port, PROTO_UDP, 0) != 0) {
+        LM_ERR("Error adding listen ipsec client interface\n");
+        return -1;
+    }
+
+    if(add_listen_iface(addr, NULL, ipsec_server_port, PROTO_UDP, 0) != 0) {
+        LM_ERR("Error adding listen ipsec server interface\n");
+        return -1;
+    }
+
+    if(fix_all_socket_lists() != 0) {
+        LM_ERR("Error calling fix_all_socket_lists() during module initialisation\n");
+        return -1;
+    }
+
+    //bind client port
+    bind_addr = grep_sock_info(&ipsec_listen_addr, ipsec_client_port, PROTO_UDP);
+    if(!bind_addr) {
+        LM_ERR("Error calling grep_sock_info() for ipsec client port during module initialisation\n");
+        return -1;
+    }
+
+    if(udp_init(bind_addr) != 0) {
+        LM_ERR("Error calling udp_init() during for ipsec client port module initialisation\n");
+        return -1;
+    }
+
+    //bind server port
+    bind_addr = grep_sock_info(&ipsec_listen_addr, ipsec_server_port, PROTO_UDP);
+    if(!bind_addr) {
+        LM_ERR("Error calling grep_sock_info() for ipsec server port during module initialisation\n");
+        return -1;
+    }
+
+    if(udp_init(bind_addr) != 0) {
+        LM_ERR("Error calling udp_init() during for ipsec server port module initialisation\n");
+        return -1;
+    }
+
+    //TODO: cleanup ipsec tunnels here
+
+    int res = 0;
+    if((res = init_spi_gen(spi_id_start, spi_id_start + spi_id_range)) != 0) {
+        LM_ERR("Error initialising spi generator. Error: %d\n", res);
+        return -1;
+    }
+
+       return 0;
+}
+
+static void mod_destroy(void)
+{
+    //TODO: cleanup ipsec tunnels here
+
+    if(destroy_spi_gen() != 0) {
+        LM_ERR("Error destroying spi generator\n");
+    }
+}
+
+static int child_init(int rank)
+{
+       return 0;
+}
+
+/* fixups */
+static int domain_fixup(void** param, int param_no)
+{
+       udomain_t* d;
+
+       if (param_no == 1) {
+               if (ul.register_udomain((char*)*param, &d) < 0) {
+                       LM_ERR("failed to register domain\n");
+                       return E_UNSPEC;
+               }
+               *param = (void*)d;
+       }
+       return 0;
+}
+
+/*! \brief
+ * Fixup for "save" function - both domain and flags
+ */
+static int save_fixup2(void** param, int param_no)
+{
+       if (param_no == 1) {
+               return domain_fixup(param,param_no);
+       }
+        return 0;
+}
+
+
+/*! \brief
+ * Wrapper to ipsec functions
+ */
+static int w_create(struct sip_msg* _m, char* _d, char* _cflags)
+{
+       return ipsec_create(_m, (udomain_t*)_d);
+}
+
+static int w_forward(struct sip_msg* _m, char* _d, char* _cflags)
+{
+       return ipsec_forward(_m, (udomain_t*)_d);
+}
+
+static int w_destroy(struct sip_msg* _m, char* _d, char* _cflags)
+{
+       return ipsec_destroy(_m, (udomain_t*)_d);
+}
diff --git a/src/modules/ims_ipsec_pcscf/ipsec.c b/src/modules/ims_ipsec_pcscf/ipsec.c
new file mode 100644 (file)
index 0000000..201106f
--- /dev/null
@@ -0,0 +1,410 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Alexander Yosifov
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "ipsec.h"
+
+#include "../../core/dprint.h"
+#include "../../core/mem/pkg.h"
+
+#include <errno.h>
+#include <arpa/inet.h>
+#include <libmnl/libmnl.h>
+#include <linux/xfrm.h>
+#include <time.h>
+
+#define XFRM_TMPLS_BUF_SIZE 1024
+
+//
+// This file contains all Linux specific IPSec code.
+//
+
+struct mnl_socket* init_mnl_socket()
+{
+    struct mnl_socket*  mnl_socket = mnl_socket_open(NETLINK_XFRM);
+    if(NULL == mnl_socket) {
+        LM_ERR("Error opening a MNL socket\n");
+        return NULL;
+    }
+
+    if(mnl_socket_bind(mnl_socket, 0, MNL_SOCKET_AUTOPID) < 0) {
+        LM_ERR("Error binding a MNL socket\n");
+        return NULL;
+    }
+
+    return mnl_socket;
+}
+
+void close_mnl_socket(struct mnl_socket* sock)
+{
+    if(mnl_socket_close(sock) != 0) {
+        LM_WARN("Error closing netlink socket\n");
+    }
+}
+
+static void string_to_key(char* dst, const str key_string)
+{
+    int i = 0;
+    char *pos = key_string.s;
+
+    for (i = 0; i < key_string.len/2; i++) {
+        sscanf(pos, "%2hhx", &dst[i]);
+        pos += 2;
+    }
+}
+
+int add_sa(struct mnl_socket* nl_sock, str src_addr_param, str dest_addr_param, int s_port, int d_port, int long id, str ck, str ik)
+{
+    char l_msg_buf[MNL_SOCKET_BUFFER_SIZE];
+    char l_auth_algo_buf[XFRM_TMPLS_BUF_SIZE];
+    char l_enc_algo_buf[XFRM_TMPLS_BUF_SIZE];
+    struct nlmsghdr* l_nlh = NULL;
+    struct xfrm_usersa_info* l_xsainfo = NULL;
+
+    struct xfrm_algo* l_auth_algo = NULL;
+    struct xfrm_algo* l_enc_algo  = NULL;
+
+    char* src_addr = NULL;
+    char* dest_addr = NULL;
+
+    memset(l_msg_buf, 0, sizeof(l_msg_buf));
+    memset(l_auth_algo_buf, 0, sizeof(l_auth_algo_buf));
+    memset(l_enc_algo_buf, 0, sizeof(l_enc_algo_buf));
+
+    // convert input IP addresses and keys to char*
+    if((src_addr = pkg_malloc(src_addr_param.len+1)) == NULL) {
+        LM_ERR("Error allocating memory for src addr during SA creation\n");
+        return -1;
+    }
+
+    if((dest_addr = pkg_malloc(dest_addr_param.len+1)) == NULL) {
+        pkg_free(src_addr);
+        LM_ERR("Error allocating memory for dest addr during SA creation\n");
+        return -2;
+    }
+
+    memset(src_addr, 0, src_addr_param.len+1);
+    memset(dest_addr, 0, dest_addr_param.len+1);
+
+    memcpy(src_addr, src_addr_param.s, src_addr_param.len);
+    memcpy(dest_addr, dest_addr_param.s, dest_addr_param.len);
+
+    // nlmsghdr initialization
+    l_nlh = mnl_nlmsg_put_header(l_msg_buf);
+    l_nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
+    l_nlh->nlmsg_type = XFRM_MSG_NEWSA;
+    l_nlh->nlmsg_seq = time(NULL);
+    l_nlh->nlmsg_pid = id;
+
+    // add Security association
+    l_xsainfo = (struct xfrm_usersa_info*)mnl_nlmsg_put_extra_header(l_nlh, sizeof(struct xfrm_usersa_info));
+    l_xsainfo->sel.family       = AF_INET;
+    l_xsainfo->sel.daddr.a4     = inet_addr(dest_addr);
+    l_xsainfo->sel.saddr.a4     = inet_addr(src_addr);
+    l_xsainfo->sel.dport        = htons(d_port);
+    l_xsainfo->sel.dport_mask   = 0xFFFF;
+    l_xsainfo->sel.prefixlen_d  = 32;
+    l_xsainfo->sel.sport        = htons(s_port);
+    l_xsainfo->sel.sport_mask   = 0xFFFF;
+    l_xsainfo->sel.prefixlen_s  = 32;
+    l_xsainfo->sel.proto        = IPPROTO_UDP;
+
+    l_xsainfo->saddr.a4         = inet_addr(src_addr);
+    l_xsainfo->id.daddr.a4      = inet_addr(dest_addr);
+    l_xsainfo->id.spi           = htonl(id);
+    l_xsainfo->id.proto         = IPPROTO_ESP;
+
+    l_xsainfo->lft.soft_byte_limit      = XFRM_INF;
+    l_xsainfo->lft.hard_byte_limit      = XFRM_INF;
+    l_xsainfo->lft.soft_packet_limit    = XFRM_INF;
+    l_xsainfo->lft.hard_packet_limit    = XFRM_INF;
+    l_xsainfo->reqid                    = id;
+    l_xsainfo->family                   = AF_INET;
+    l_xsainfo->mode                     = XFRM_MODE_TRANSPORT;
+    l_xsainfo->replay_window            = 32;
+    l_xsainfo->flags                    = XFRM_STATE_NOECN;
+
+    // char* ip addresses are no longer needed - free them
+    pkg_free(src_addr);
+    pkg_free(dest_addr);
+
+    // Add authentication algorithm for this SA
+
+    // The cast below is performed because alg_key from struct xfrm_algo is char[0]
+    // The point is to provide a continuous chunk of memory with the key in it
+    l_auth_algo = (struct xfrm_algo *)l_auth_algo_buf;
+
+    strcpy(l_auth_algo->alg_name,"md5");
+    l_auth_algo->alg_key_len = ik.len * 4;
+    string_to_key(l_auth_algo->alg_key, ik);
+
+    mnl_attr_put(l_nlh, XFRMA_ALG_AUTH, sizeof(struct xfrm_algo) + l_auth_algo->alg_key_len, l_auth_algo);
+
+    // add encription algorithm for this SA
+    l_enc_algo = (struct xfrm_algo *)l_enc_algo_buf;
+    strcpy(l_enc_algo->alg_name,"cipher_null");
+
+    mnl_attr_put(l_nlh, XFRMA_ALG_CRYPT, sizeof(struct xfrm_algo) + l_enc_algo->alg_key_len, l_enc_algo);
+
+    // send it to Netlink socket
+    if(mnl_socket_sendto(nl_sock, l_nlh, l_nlh->nlmsg_len) < 0)
+    {
+        LM_ERR("Failed to send Netlink message for SA creation, error: %s\n", strerror(errno));
+        return -3;
+    }
+
+    return 0;
+}
+
+
+int remove_sa(struct mnl_socket* nl_sock, str src_addr_param, str dest_addr_param, int s_port, int d_port, int long id)
+{
+    char* src_addr = NULL;
+    char* dest_addr = NULL;
+
+    // convert input IP addresses to char*
+    if((src_addr = pkg_malloc(src_addr_param.len+1)) == NULL) {
+        LM_ERR("Error allocating memory for src addr during SA removal\n");
+        return -1;
+    }
+
+    if((dest_addr = pkg_malloc(dest_addr_param.len+1)) == NULL) {
+        pkg_free(src_addr);
+        LM_ERR("Error allocating memory for dest addr during SA removal\n");
+        return -2;
+    }
+
+    memset(src_addr, 0, src_addr_param.len+1);
+    memset(dest_addr, 0, dest_addr_param.len+1);
+
+    memcpy(src_addr, src_addr_param.s, src_addr_param.len);
+    memcpy(dest_addr, dest_addr_param.s, dest_addr_param.len);
+
+
+    struct {
+        struct nlmsghdr n;
+        struct xfrm_usersa_id   xsid;
+        char buf[XFRM_TMPLS_BUF_SIZE];
+
+    } req = {
+        .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsid)),
+        .n.nlmsg_flags = NLM_F_REQUEST,
+        .n.nlmsg_type = XFRM_MSG_DELSA,
+        .xsid.spi = htonl(id),
+        .xsid.family = AF_INET,
+        .xsid.proto = IPPROTO_ESP,
+        .xsid.daddr.a4 = inet_addr(dest_addr)
+    };
+
+    // SADDR
+    xfrm_address_t saddr;
+    memset(&saddr, 0, sizeof(saddr));
+    saddr.a4 = inet_addr(src_addr);
+
+    mnl_attr_put(&req.n, XFRMA_SRCADDR, sizeof(saddr), (void *)&saddr);
+
+    if(mnl_socket_sendto(nl_sock, &req.n, req.n.nlmsg_len) < 0)
+    {
+        LM_ERR("Failed to send Netlink message, error: %s\n", strerror(errno));
+        pkg_free(src_addr);
+        pkg_free(dest_addr);
+        return -1;
+    }
+
+    pkg_free(src_addr);
+    pkg_free(dest_addr);
+
+    return 0;
+}
+
+
+int add_policy(struct mnl_socket* mnl_socket, str src_addr_param, str dest_addr_param, int src_port, int dst_port, int long p_id, enum ipsec_policy_direction dir)
+{
+    char                            l_msg_buf[MNL_SOCKET_BUFFER_SIZE];
+    char                            l_tmpls_buf[XFRM_TMPLS_BUF_SIZE];
+    struct nlmsghdr*                l_nlh;
+    struct xfrm_userpolicy_info*    l_xpinfo;
+
+    char* src_addr = NULL;
+    char* dest_addr = NULL;
+
+    //printf("Adding Policy\n");
+
+    memset(l_msg_buf, 0, sizeof(l_msg_buf));
+    memset(l_tmpls_buf, 0, sizeof(l_tmpls_buf));
+
+    // convert input IP addresses to char*
+    if((src_addr = pkg_malloc(src_addr_param.len+1)) == NULL) {
+        LM_ERR("Error allocating memory for src addr during Policy creation\n");
+        return -1;
+    }
+
+    if((dest_addr = pkg_malloc(dest_addr_param.len+1)) == NULL) {
+        pkg_free(src_addr);
+        LM_ERR("Error allocating memory for dest addr during Policy creation\n");
+        return -2;
+    }
+
+    memset(src_addr, 0, src_addr_param.len+1);
+    memset(dest_addr, 0, dest_addr_param.len+1);
+
+    memcpy(src_addr, src_addr_param.s, src_addr_param.len);
+    memcpy(dest_addr, dest_addr_param.s, dest_addr_param.len);
+
+    // nlmsghdr initialization
+    l_nlh = mnl_nlmsg_put_header(l_msg_buf);
+    l_nlh->nlmsg_flags  = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
+    l_nlh->nlmsg_type   = XFRM_MSG_NEWPOLICY;
+    l_nlh->nlmsg_seq    = time(NULL);
+    l_nlh->nlmsg_pid    = p_id;
+
+    // add OUT policy
+    l_xpinfo = (struct xfrm_userpolicy_info*)mnl_nlmsg_put_extra_header(l_nlh, sizeof(struct xfrm_userpolicy_info));
+    l_xpinfo->sel.family        = AF_INET;
+    l_xpinfo->sel.daddr.a4      = inet_addr(dest_addr);
+    l_xpinfo->sel.saddr.a4      = inet_addr(src_addr);
+    l_xpinfo->sel.dport         = htons(dst_port);
+    l_xpinfo->sel.dport_mask    = 0xFFFF;
+    l_xpinfo->sel.prefixlen_d   = 32;
+    l_xpinfo->sel.sport         = htons(src_port);
+    l_xpinfo->sel.sport_mask    = 0xFFFF;
+    l_xpinfo->sel.prefixlen_s   = 32;
+    l_xpinfo->sel.proto         = IPPROTO_UDP;
+
+    l_xpinfo->lft.soft_byte_limit   = XFRM_INF;
+    l_xpinfo->lft.hard_byte_limit   = XFRM_INF;
+    l_xpinfo->lft.soft_packet_limit = XFRM_INF;
+    l_xpinfo->lft.hard_packet_limit = XFRM_INF;
+    l_xpinfo->priority              = 2080;
+    l_xpinfo->action                = XFRM_POLICY_ALLOW;
+    l_xpinfo->share                 = XFRM_SHARE_ANY;
+
+    if (dir == IPSEC_POLICY_DIRECTION_IN) {
+        l_xpinfo->dir               = XFRM_POLICY_IN;
+    }
+    else if(dir == IPSEC_POLICY_DIRECTION_OUT) {
+        l_xpinfo->dir               = XFRM_POLICY_OUT;
+    }
+    else {
+        LM_ERR("Invalid direction parameter passed to add_policy: %d\n", dir);
+        pkg_free(src_addr);
+        pkg_free(dest_addr);
+
+        return -3;
+    }
+
+
+    // xfrm_user_tmpl initialization
+    struct xfrm_user_tmpl* l_tmpl = (struct xfrm_user_tmpl*)l_tmpls_buf;
+    l_tmpl->id.proto    = IPPROTO_ESP;
+    l_tmpl->family      = AF_INET;
+    l_tmpl->reqid       = p_id;
+    l_tmpl->mode        = XFRM_MODE_TRANSPORT;
+    l_tmpl->aalgos      = (~(__u32)0);
+    l_tmpl->ealgos      = (~(__u32)0);
+    l_tmpl->calgos      = (~(__u32)0);
+
+
+    mnl_attr_put(l_nlh, XFRMA_TMPL, sizeof(struct xfrm_user_tmpl), l_tmpl);
+
+    if(mnl_socket_sendto(mnl_socket, l_nlh, l_nlh->nlmsg_len) < 0)
+    {
+        pkg_free(src_addr);
+        pkg_free(dest_addr);
+        LM_ERR("Failed to send Netlink message, error: %s\n", strerror(errno));
+        return -4;
+    }
+
+    // char* ip addresses are no longer needed - free them
+    pkg_free(src_addr);
+    pkg_free(dest_addr);
+
+    return 0;
+}
+
+
+int remove_policy(struct mnl_socket* mnl_socket, str src_addr_param, str dest_addr_param, int src_port, int dst_port, int long p_id, enum ipsec_policy_direction dir)
+{
+    unsigned char policy_dir = 0;
+
+    if(dir == IPSEC_POLICY_DIRECTION_IN) {
+         policy_dir = XFRM_POLICY_IN;
+    }
+    else if(dir == IPSEC_POLICY_DIRECTION_OUT) {
+         policy_dir = XFRM_POLICY_OUT;
+    }
+    else {
+        LM_ERR("Invalid direction parameter passed to add_policy: %d\n", dir);
+        return -1;
+    }
+
+    char* src_addr = NULL;
+    char* dest_addr = NULL;
+
+    // convert input IP addresses to char*
+    if((src_addr = pkg_malloc(src_addr_param.len+1)) == NULL) {
+        LM_ERR("Error allocating memory for src addr during SA removal\n");
+        return -1;
+    }
+
+    if((dest_addr = pkg_malloc(dest_addr_param.len+1)) == NULL) {
+        pkg_free(src_addr);
+        LM_ERR("Error allocating memory for dest addr during SA removal\n");
+        return -2;
+    }
+
+    memset(src_addr, 0, src_addr_param.len+1);
+    memset(dest_addr, 0, dest_addr_param.len+1);
+
+    memcpy(src_addr, src_addr_param.s, src_addr_param.len);
+    memcpy(dest_addr, dest_addr_param.s, dest_addr_param.len);
+
+    struct {
+        struct nlmsghdr n;
+        struct xfrm_userpolicy_id xpid;
+        char buf[XFRM_TMPLS_BUF_SIZE];
+    } req = {
+        .n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xpid)),
+        .n.nlmsg_flags = NLM_F_REQUEST,
+        .n.nlmsg_type = XFRM_MSG_DELPOLICY,
+        .xpid.dir               = policy_dir,
+        .xpid.sel.family        = AF_INET,
+        .xpid.sel.daddr.a4      = inet_addr(dest_addr),
+        .xpid.sel.saddr.a4      = inet_addr(src_addr),
+        .xpid.sel.dport         = htons(dst_port),
+        .xpid.sel.dport_mask    = 0xFFFF,
+        .xpid.sel.prefixlen_d   = 32,
+        .xpid.sel.sport         = htons(src_port),
+        .xpid.sel.sport_mask    = 0xFFFF,
+        .xpid.sel.prefixlen_s   = 32,
+        .xpid.sel.proto         = IPPROTO_UDP
+    };
+
+    if(mnl_socket_sendto(mnl_socket, &req.n, req.n.nlmsg_len) < 0)
+    {
+        LM_ERR("Failed to send Netlink message, error: %s\n", strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/modules/ims_ipsec_pcscf/ipsec.h b/src/modules/ims_ipsec_pcscf/ipsec.h
new file mode 100644 (file)
index 0000000..19dbb73
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Alexander Yosifov
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef IMS_IPSEC_PCSCF_IPSEC
+#define IMS_IPSEC_PCSCF_IPSEC
+
+#include "../../core/str.h"
+
+
+struct mnl_socket;
+
+enum ipsec_policy_direction {
+    IPSEC_POLICY_DIRECTION_IN = 0,
+    IPSEC_POLICY_DIRECTION_OUT = 1
+};
+
+
+struct mnl_socket* init_mnl_socket();
+void close_mnl_socket(struct mnl_socket* sock);
+
+int add_sa(struct mnl_socket* nl_sock, str src_addr_param, str dest_addr_param, int s_port, int d_port, int long id, str ck, str ik);
+int remove_sa(struct mnl_socket* nl_sock, str src_addr_param, str dest_addr_param, int s_port, int d_port, int long id);
+
+int add_policy(struct mnl_socket* mnl_socket, str src_addr_param, str dest_addr_param, int src_port, int dst_port, int long p_id, enum ipsec_policy_direction dir);
+int remove_policy(struct mnl_socket* mnl_socket, str src_addr_param, str dest_addr_param, int src_port, int dst_port, int long p_id, enum ipsec_policy_direction dir);
+
+#endif /* IMS_IPSEC_PCSCF_IPSEC */
diff --git a/src/modules/ims_ipsec_pcscf/run_spi_list_tests.sh b/src/modules/ims_ipsec_pcscf/run_spi_list_tests.sh
new file mode 100755 (executable)
index 0000000..8e9efb6
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+gcc -D_IPSEC_SPI_LIST_TEST spi_list_tests.c spi_list.c -o spi_list_tests
+./spi_list_tests
diff --git a/src/modules/ims_ipsec_pcscf/spi_gen.c b/src/modules/ims_ipsec_pcscf/spi_gen.c
new file mode 100644 (file)
index 0000000..fa7a3e7
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "spi_gen.h"
+#include "spi_list.h"
+#include <pthread.h>
+
+pthread_mutex_t spis_mut;
+spi_list_t used_spis;
+uint32_t spi_val;
+uint32_t min_spi;
+uint32_t max_spi;
+
+int init_spi_gen(uint32_t start_val, uint32_t range)
+{
+    if(start_val < 1) {
+        return 1;
+    }
+
+    if(UINT32_MAX - range < start_val)
+        return 2;
+
+    if(pthread_mutex_init(&spis_mut, NULL))
+        return 3;
+
+    used_spis = create_list();
+
+    spi_val = start_val;
+    min_spi = start_val;
+    max_spi = start_val + range;
+
+    return 0;
+}
+
+uint32_t acquire_spi()
+{
+    //save the initial value for the highly unlikely case where there are no free SPIs
+    uint32_t initial_val = spi_val;
+    uint32_t ret = 0; // by default return invalid SPI
+
+    if(pthread_mutex_lock(&spis_mut) != 0) {
+        return ret;
+    }
+
+    while(1) {
+        if(spi_in_list(&used_spis, spi_val) == 0) {
+            ret = spi_val;
+            spi_val++;
+            break;
+        }
+
+        spi_val++; //the current SPI is not available - increment
+
+        if(spi_val >= max_spi) { //reached the top of the range - reset
+            spi_val = min_spi;
+        }
+
+        if(spi_val == initial_val) { //there are no free SPIs
+            break;
+        }
+
+    }
+
+    //found unused SPI - add it to the used list
+    if(spi_add(&used_spis, ret) != 0) {
+        ret = 0;
+    }
+
+    pthread_mutex_unlock(&spis_mut);
+
+    return ret;
+}
+
+int release_spi(uint32_t id)
+{
+    if(pthread_mutex_lock(&spis_mut) != 0) {
+        return 1;
+    }
+
+    spi_remove(&used_spis, id);
+
+    pthread_mutex_unlock(&spis_mut);
+
+    return 0;
+}
+
+int destroy_spi_gen()
+{
+    return pthread_mutex_destroy(&spis_mut);
+}
diff --git a/src/modules/ims_ipsec_pcscf/spi_gen.h b/src/modules/ims_ipsec_pcscf/spi_gen.h
new file mode 100644 (file)
index 0000000..dcb8475
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef _SPI_GEN_H_
+
+#include <stdint.h>
+
+//
+// SPI GEN is based on SPI list.
+// It is used as a unique ID generator for the SPIs. As the range of IDs is limited it
+// is important not to use generate ID which is still in use. For this reason there are
+// acquire_spi() and release_spi(uint32_t id) functions.
+
+int init_spi_gen(uint32_t start_val, uint32_t range);
+int destroy_spi_gen();
+uint32_t acquire_spi();
+int release_spi(uint32_t id);
+
+#endif /*  _SPI_GEN_H_ */
diff --git a/src/modules/ims_ipsec_pcscf/spi_list.c b/src/modules/ims_ipsec_pcscf/spi_list.c
new file mode 100644 (file)
index 0000000..f57a45b
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "spi_list.h"
+
+
+spi_list_t create_list()
+{
+    spi_list_t lst;
+    lst.head = NULL;
+    lst.tail = NULL;
+    return lst;
+}
+
+int spi_add(spi_list_t* list, uint32_t id)
+{
+    // create new node
+    spi_node_t* n = malloc(sizeof(spi_node_t));
+
+    if(!n)
+        return 1;
+
+    n->next = NULL;
+    n->id = id;
+
+    //when list is empty
+    if(!list->head) {
+        list->head = n;
+        list->tail = n;
+        return 0;
+    }
+
+    //all other cases - list should be sorted
+    spi_node_t* c = list->head;
+    spi_node_t* p = NULL;
+    while(c && (n->id > c->id)) {
+        p = c;
+        c = c->next;
+    }
+
+
+    if(c == NULL) { //first of all - at the end of the list?
+        list->tail->next = n;
+        list->tail = n;
+    }
+    else if(n->id == c->id) { //c is not NULL, so check for duplicates
+        free(n);
+        return 1;
+    }
+    else if(c == list->head) { //at the start of the list?
+        n->next = list->head;
+        list->head = n;
+    }
+    else {  //not a special case - just add it
+        p->next = n;
+        n->next = c;
+    }
+
+    return 0;
+}
+
+
+int spi_remove(spi_list_t* list, uint32_t id)
+{
+    //when list is empty
+    if(!list->head) {
+        return 0;
+    }
+
+    //when target is head
+    if(list->head->id == id) {
+        spi_node_t* t = list->head;
+        list->head = t->next;
+
+        //if head==tail, adjust tail too
+        if(t == list->tail) {
+            list->tail = list->head;
+        }
+
+        free(t);
+        return 0;
+    }
+
+
+    spi_node_t* prev = list->head;
+    spi_node_t* curr = list->head->next;
+
+    while(curr) {
+        if(curr->id == id) {
+            spi_node_t* t = curr;
+
+            //detach node
+            prev->next = curr->next;
+
+            //is it the last elemet
+            if(t == list->tail) {
+                list->tail = prev;
+            }
+
+            free(t);
+        }
+
+        prev = curr;
+        curr = curr->next;
+    }
+
+    return 0;
+}
+
+int spi_in_list(spi_list_t* list, uint32_t id)
+{
+    if(!list->head)
+        return 0;
+
+    if(id < list->head->id || id > list->tail->id)
+        return 0;
+
+    spi_node_t* n = list->head;
+    while(n) {
+        if (n->id == id)
+            return 1;
+    }
+
+    return 0;
+}
+
diff --git a/src/modules/ims_ipsec_pcscf/spi_list.h b/src/modules/ims_ipsec_pcscf/spi_list.h
new file mode 100644 (file)
index 0000000..05a555c
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef _SPI_LIST_H_
+
+#include <stdlib.h>
+#include <stdint.h>
+
+//
+// Single linked list implementation. The elements are kept sorted via insertion sort.
+//
+
+
+typedef struct _spi_node spi_node_t;
+
+struct _spi_node {
+    spi_node_t* next;
+    uint32_t id;
+};
+
+typedef struct _spi_list {
+    spi_node_t* head;
+    spi_node_t* tail;
+} spi_list_t;
+
+
+spi_list_t create_list();
+int spi_add(spi_list_t* list, uint32_t id);
+int spi_remove(spi_list_t* list, uint32_t id);
+int spi_in_list(spi_list_t* list, uint32_t id);
+
+#endif /* _SPI_LIST_H_ */
diff --git a/src/modules/ims_ipsec_pcscf/spi_list_tests.c b/src/modules/ims_ipsec_pcscf/spi_list_tests.c
new file mode 100644 (file)
index 0000000..876c474
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * IMS IPSEC PCSCF module
+ *
+ * Copyright (C) 2018 Tsvetomir Dimitrov
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef _IPSEC_SPI_LIST_TEST
+
+#include "spi_list.h"
+#include <stdio.h>
+
+void iterate(spi_list_t* list)
+{
+    spi_node_t* n = list->head;
+    printf("HEAD: %d TAIL: %d; [", list->head->id, list->tail->id);
+
+    while(n) {
+        printf("%d ", n->id);
+        n = n->next;
+    }
+    printf("]\n");
+}
+
+void check(spi_list_t* list, int* exp, int len, const char* func_name)
+{
+    //Special case for empty list
+    if(len == 0) {
+        if(list->head != NULL) {
+            printf("%s: Empty list but head is not NULL.\n", func_name);
+            return;
+        }
+
+        if(list->tail != NULL) {
+            printf("%s: Empty list, but tail is not NULL\n", func_name);
+            return;
+        }
+
+        goto success;
+    }
+
+    //Check head
+    if(exp[0] != list->head->id) {
+        printf("%s failed. Expected head: %d; Actual head: %d\n", func_name, exp[0], list->head->id);
+        return;
+    }
+
+    //Check list contents
+    spi_node_t* n = list->head;
+    int i;
+    for(i = 0; i < len; i++) {
+        if(exp[i] != n->id) {
+            printf("%s failed. list[%d] == %d; exp[%d] == %d\n", func_name, i, n->id, i, exp[i]);
+            return;
+        }
+        n = n->next;
+    }
+
+    //Check tail
+    if(exp[len-1] != list->tail->id) {
+        printf("%s failed. Expected tail: %d; Actual tail: %d\n", func_name, exp[len-1], list->tail->id);
+        return;
+    }
+
+success:
+    printf("%s: OK\n", func_name);
+}
+
+void case1() // One element list
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1};
+
+    spi_add(&list, 1);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case2() // Two element list
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1, 2};
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case3() // Three element list
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1, 2, 3};
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case4() // Delete head
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {2, 3};
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+
+    spi_remove(&list, 1);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+
+void case5() // Delete tail
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1, 2 };
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+
+    spi_remove(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case6() // Delete between
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1, 3 };
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+
+    spi_remove(&list, 2);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case7() // Out of order add
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1, 2, 3};
+
+    spi_add(&list, 2);
+    spi_add(&list, 1);
+    spi_add(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case8() //Random operations
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1, 4, 6};
+
+    spi_add(&list, 2);
+    spi_add(&list, 1);
+    spi_add(&list, 3);
+
+    spi_remove(&list, 2);
+    spi_add(&list, 4);
+    spi_add(&list, 6);
+    spi_remove(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case9() // Empty list
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {};
+
+    spi_add(&list, 2);
+    spi_add(&list, 1);
+    spi_add(&list, 3);
+
+    spi_remove(&list, 1);
+    spi_remove(&list, 2);
+    spi_remove(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+
+void case10() //No duplicates
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1,2,3};
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 2);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case11() //No duplicates
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1,2,3};
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+    spi_add(&list, 3);
+    spi_add(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case12() //No duplicates
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1,2,3};
+
+    spi_add(&list, 1);
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case13() //No duplicates
+{
+    spi_list_t list = create_list();
+
+    int exp[] = {1,2,3};
+
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+    spi_add(&list, 3);
+    spi_add(&list, 1);
+
+    check(&list, exp, sizeof(exp)/sizeof(int), __func__);
+}
+
+void case14()
+{
+    spi_list_t list = create_list();
+    spi_add(&list, 1);
+    spi_add(&list, 2);
+
+    if(spi_in_list(&list, 1) != 1) {
+        printf("%s: failed. 1 is in list, but spi_in_list() returns false.\n", __func__);
+        return;
+    }
+
+    if(spi_in_list(&list, 3) != 0) {
+        printf("%s: failed. 3 is not in list, but spi_in_list() returns true.\n", __func__);
+        return;
+    }
+
+    printf("%s: OK\n", __func__);
+}
+
+
+int main()
+{
+
+    case1();
+    case2();
+    case3();
+    case4();
+    case5();
+    case6();
+    case7();
+    case8();
+    case9();
+    case10();
+    case11();
+    case12();
+    case13();
+    case14();
+
+    return 0;
+}
+
+#endif