gzcompress: new module to compress/decompress SIP message body using zlib
authorDaniel-Constantin Mierla <miconda@gmail.com>
Fri, 20 Sep 2013 22:10:43 +0000 (00:10 +0200)
committerDaniel-Constantin Mierla <miconda@gmail.com>
Fri, 20 Sep 2013 22:17:05 +0000 (00:17 +0200)
modules/gzcompress/Makefile [new file with mode: 0644]
modules/gzcompress/README [new file with mode: 0644]
modules/gzcompress/doc/Makefile [new file with mode: 0644]
modules/gzcompress/doc/gzcompress.xml [new file with mode: 0644]
modules/gzcompress/doc/gzcompress_admin.xml [new file with mode: 0644]
modules/gzcompress/gzcompress_mod.c [new file with mode: 0644]

diff --git a/modules/gzcompress/Makefile b/modules/gzcompress/Makefile
new file mode 100644 (file)
index 0000000..aa24a3f
--- /dev/null
@@ -0,0 +1,25 @@
+#
+# gzcompress module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=gzcompress.so
+
+ifeq ($(CROSS_COMPILE),)
+       BUILDER = $(shell which pkg-config)
+endif
+
+ifneq ($(BUILDER),)
+       DEFS += $(shell $(BUILDER) --cflags zlib)
+       LIBS += $(shell $(BUILDER) --libs zlib)
+else
+       DEFS += -I$(LOCALBASE)/include
+       LIBS += -L$(LOCALBASE)/lib -lz
+endif
+
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+
+include ../../Makefile.modules
diff --git a/modules/gzcompress/README b/modules/gzcompress/README
new file mode 100644 (file)
index 0000000..c1cc720
--- /dev/null
@@ -0,0 +1,162 @@
+GZCompress Module
+
+Daniel-Constantin Mierla
+
+   <miconda@gmail.com>
+
+Edited by
+
+Daniel-Constantin Mierla
+
+   <miconda@gmail.com>
+
+   Copyright © 2013 asipto.com
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. header_name (str)
+              3.2. header_value (str)
+              3.3. sanity_checks (integer)
+
+        4. Functions
+
+              4.1.
+
+   List of Examples
+
+   1.1. Set header_name parameter
+   1.2. Set header_value parameter
+   1.3. Set sanity_checks parameter
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. header_name (str)
+        3.2. header_value (str)
+        3.3. sanity_checks (integer)
+
+   4. Functions
+
+        4.1.
+
+1. Overview
+
+   This module is able to detect compressed body in received SIP message
+   and decompress it as well as compress the body for outgoing SIP
+   message.
+
+   The decision of whether to do compression or decompression is made by
+   detecting a special SIP header (default 'Content-Encoding') that
+   matches a given value - both header name and value can be set via
+   module parameters. If a SIP message is received with clear body and you
+   want to compress the body for outgoing, add the header in config file.
+   The header can be added to the local generated replies as well.
+
+   In other words, if the header is present in incoming SIP message, its
+   body is decompressed. If the header is present in outgoing SIP message,
+   its body is compressed. Therefore inside configuration file, the body
+   is in original format(e.g., plain text). In this way, the existing
+   functions to handle content of the body work as usual (e.g., to strip
+   codecs in sdp via sdpops or do substitutions via textops).
+
+   The functions used to compress and decompress are from zlib library
+   (http://zlib.net).
+
+   NOTE: for the moment the module cannot be used with topoh module,
+   overlapping in core event callbacks (will be fixed soon).
+
+   The immediate benefit of compressing the body is to reduce the size of
+   the SIP message, increasing the chances to stay under MTU for UDP
+   packts. From observation, the compressed body is in between 50% to 67%
+   smaller than the original size (e.g., a body of 431 bytes was
+   compressed to 230).
+
+   An use case can be when having peering traffic between two Kamailio
+   servers. Before relaying to the other Kamailio, use in config file:
+   append_hf("Content-Encoding: gzip\r\n").
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * none.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * zlib compression library (http://zlib.net).
+
+3. Parameters
+
+   3.1. header_name (str)
+   3.2. header_value (str)
+   3.3. sanity_checks (integer)
+
+3.1. header_name (str)
+
+   Name of the header that indicates compression or decompression has to
+   be done.
+
+   Default value is "Content-Encoding".
+
+   Example 1.1. Set header_name parameter
+...
+modparam("gzcompress", "header_name", "Encoded")
+...
+
+3.2. header_value (str)
+
+   Name of the header that indicates compression or decompression has to
+   be done.
+
+   Default value is "gzip".
+
+   Example 1.2. Set header_value parameter
+...
+modparam("gzcompress", "header_value", "tgz")
+...
+
+3.3. sanity_checks (integer)
+
+   If set to 1, gzcompress module will bind to sanity module in order to
+   perform sanity checks over received SIP request. Default sanity checks
+   are done. It is useful to check if received request is well formated
+   before proceeding to encoding/decoding.
+
+   Default value is 0 (do not bind to sanity module).
+
+   Example 1.3. Set sanity_checks parameter
+...
+modparam("gzcompress", "sanity_checks", 1)
+...
+
+4. Functions
+
+   4.1.
+
+   None.
diff --git a/modules/gzcompress/doc/Makefile b/modules/gzcompress/doc/Makefile
new file mode 100644 (file)
index 0000000..beb5851
--- /dev/null
@@ -0,0 +1,4 @@
+docs = gzcompress.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module
diff --git a/modules/gzcompress/doc/gzcompress.xml b/modules/gzcompress/doc/gzcompress.xml
new file mode 100644 (file)
index 0000000..156252a
--- /dev/null
@@ -0,0 +1,37 @@
+<?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 "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+       <title>GZCompress Module</title>
+       <productname class="trade">kamailio.org</productname>
+       <authorgroup>
+           <author>
+               <firstname>Daniel-Constantin</firstname>
+               <surname>Mierla</surname>
+               <email>miconda@gmail.com</email>
+           </author>
+           <editor>
+               <firstname>Daniel-Constantin</firstname>
+               <surname>Mierla</surname>
+               <email>miconda@gmail.com</email>
+           </editor>
+       </authorgroup>
+       <copyright>
+           <year>2013</year>
+           <holder>asipto.com</holder>
+       </copyright>
+    </bookinfo>
+    <toc></toc>
+    
+    <xi:include href="gzcompress_admin.xml"/>
+    
+    
+</book>
diff --git a/modules/gzcompress/doc/gzcompress_admin.xml b/modules/gzcompress/doc/gzcompress_admin.xml
new file mode 100644 (file)
index 0000000..61d3633
--- /dev/null
@@ -0,0 +1,165 @@
+<?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 "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+       
+       <title>&adminguide;</title>
+       
+       <section>
+       <title>Overview</title>
+       <para>
+               This module is able to detect compressed body in received SIP message
+               and decompress it as well as compress the body for outgoing SIP
+               message.
+       </para>
+       <para>
+               The decision of whether to do compression or decompression is made
+               by detecting a special SIP header (default 'Content-Encoding') that
+               matches a given value - both header name and value can be set via
+               module parameters. If a SIP message is received with clear body and
+               you want to compress the body for outgoing, add the header in config
+               file. The header can be added to the local generated replies as well.
+       </para>
+       <para>
+               In other words, if the header is present in incoming SIP message, its
+               body is decompressed. If the header is present in outgoing SIP message,
+               its body is compressed. Therefore inside configuration file, the body
+               is in original format(e.g., plain text). In this way, the existing
+               functions to handle content of the body work as usual (e.g., to strip
+               codecs in sdp via sdpops or do substitutions via textops).
+       </para>
+       <para>
+               The functions used to compress and decompress are from zlib
+               library (http://zlib.net).
+       </para>
+       <para>
+               NOTE: for the moment the module cannot be used with topoh module,
+               overlapping in core event callbacks (will be fixed soon).
+       </para>
+       <para>
+               The immediate benefit of compressing the body is to reduce the size
+               of the SIP message, increasing the chances to stay under MTU for UDP
+               packts. From observation, the compressed body is in between 50% to 67%
+               smaller than the original size (e.g., a body of 431 bytes was
+               compressed to 230).
+       </para>
+       <para>
+               An use case can be when having peering traffic between two Kamailio
+               servers. Before relaying to the other Kamailio,  use
+               in config file: append_hf("Content-Encoding: gzip\r\n").
+       </para>
+       </section>
+       <section>
+       <title>Dependencies</title>
+       <section>
+               <title>&kamailio; Modules</title>
+               <para>
+               The following modules must be loaded before this module:
+                       <itemizedlist>
+                       <listitem>
+                       <para>
+                               <emphasis>none</emphasis>.
+                       </para>
+                       </listitem>
+                       </itemizedlist>
+               </para>
+       </section>
+       <section>
+               <title>External Libraries or Applications</title>
+               <para>
+               The following libraries or applications must be installed before running
+               &kamailio; with this module loaded:
+                       <itemizedlist>
+                       <listitem>
+                       <para>
+                               <emphasis>zlib</emphasis> compression library (http://zlib.net).
+                       </para>
+                       </listitem>
+                       </itemizedlist>
+               </para>
+       </section>
+       </section>
+       <section>
+       <title>Parameters</title>
+       <section id="gzcompress.p.header_name">
+               <title><varname>header_name</varname> (str)</title>
+               <para>
+                       Name of the header that indicates compression or decompression has
+                       to be done.
+               </para>
+               <para>
+               <emphasis>
+                       Default value is "Content-Encoding".
+               </emphasis>
+               </para>
+               <example>
+               <title>Set <varname>header_name</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+modparam("gzcompress", "header_name", "Encoded")
+...
+</programlisting>
+               </example>
+       </section>
+       <section id="gzcompress.p.header_value">
+               <title><varname>header_value</varname> (str)</title>
+               <para>
+                       Name of the header that indicates compression or decompression has
+                       to be done.
+               </para>
+               <para>
+               <emphasis>
+                       Default value is "gzip".
+               </emphasis>
+               </para>
+               <example>
+               <title>Set <varname>header_value</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+modparam("gzcompress", "header_value", "tgz")
+...
+</programlisting>
+               </example>
+       </section>
+       <section id="gzcompress.p.sanity_checks">
+               <title><varname>sanity_checks</varname> (integer)</title>
+               <para>
+                       If set to 1, gzcompress module will bind to sanity module in order
+                       to perform sanity checks over received SIP request. Default
+                       sanity checks are done. It is useful to check if received request
+                       is well formated before proceeding to encoding/decoding.
+               </para>
+               <para>
+               <emphasis>
+                       Default value is 0 (do not bind to sanity module).
+               </emphasis>
+               </para>
+               <example>
+               <title>Set <varname>sanity_checks</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+modparam("gzcompress", "sanity_checks", 1)
+...
+</programlisting>
+               </example>
+       </section>
+
+       </section>
+       <section>
+       <title>Functions</title>
+       <section>
+               <para>
+                       None.
+               </para>
+       </section>
+       </section>
+</chapter>
+
diff --git a/modules/gzcompress/gzcompress_mod.c b/modules/gzcompress/gzcompress_mod.c
new file mode 100644 (file)
index 0000000..38c7687
--- /dev/null
@@ -0,0 +1,408 @@
+/**
+ * $Id$
+ *
+ * Copyright (C) 2013 Daniel-Constantin Mierla (asipto.com)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*!
+ * \file
+ * \brief Kamailio gzcompress :: Module interface
+ * \ingroup gzcompress
+ * Module: \ref gzcompress
+ */
+
+/*! \defgroup gzcompress Kamailio :: compress-decompress message body with zlib
+ *
+ * This module compresses/decompresses SIP message body using zlib.
+ * The script interpreter gets the SIP messages decoded, so all
+ * existing functionality is preserved.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <zlib.h>
+
+#include "../../sr_module.h"
+#include "../../events.h"
+#include "../../dprint.h"
+#include "../../tcp_options.h"
+#include "../../ut.h"
+#include "../../forward.h"
+#include "../../msg_translator.h"
+#include "../../data_lump.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/parse_to.h"
+#include "../../parser/parse_from.h"
+
+#include "../../modules/sanity/api.h"
+
+MODULE_VERSION
+
+/** local functions */
+int gzc_msg_received(void *data);
+int gzc_msg_sent(void *data);
+
+/** module parameters */
+static str _gzc_hdr_name = str_init("Content-Encoding");
+static str _gzc_hdr_value = str_init("gzip");
+
+static int _gzc_sanity_checks = 0;
+static sanity_api_t scb = {0};
+
+/** module functions */
+static int mod_init(void);
+
+static param_export_t params[]={
+       {"header_name",         PARAM_STR, &_gzc_hdr_name},
+       {"header_value",        PARAM_STR, &_gzc_hdr_value},
+       {"sanity_checks",       PARAM_INT, &_gzc_sanity_checks},
+       {0,0,0}
+};
+
+
+/** module exports */
+struct module_exports exports= {
+       "gzcompress",
+       DEFAULT_DLFLAGS, /* dlopen flags */
+       0,
+       params,
+       0,          /* exported statistics */
+       0,          /* exported MI functions */
+       0,          /* exported pseudo-variables */
+       0,          /* extra processes */
+       mod_init,   /* module initialization function */
+       0,
+       0,
+       0           /* per-child init function */
+};
+
+/**
+ * init module function
+ */
+static int mod_init(void)
+{
+       if(_gzc_sanity_checks!=0)
+       {
+               if(sanity_load_api(&scb)<0)
+               {
+                       LM_ERR("cannot bind to sanity module\n");
+                       goto error;
+               }
+       }
+       
+       sr_event_register_cb(SREV_NET_DATA_IN, gzc_msg_received);
+       sr_event_register_cb(SREV_NET_DATA_OUT, gzc_msg_sent);
+#ifdef USE_TCP
+       tcp_set_clone_rcvbuf(1);
+#endif
+       return 0;
+error:
+       return -1;
+}
+
+/**
+ *
+ */
+int gzc_prepare_msg(sip_msg_t *msg)
+{
+       if (parse_msg(msg->buf, msg->len, msg)!=0)
+       {
+               LM_DBG("outbuf buffer parsing failed!");
+               return 1;
+       }
+
+       if(msg->first_line.type==SIP_REQUEST)
+       {
+               if(!IS_SIP(msg))
+               {
+                       LM_DBG("non sip request message\n");
+                       return 1;
+               }
+       } else if(msg->first_line.type!=SIP_REPLY) {
+               LM_DBG("non sip message\n");
+               return 1;
+       }
+
+       if (parse_headers(msg, HDR_EOH_F, 0)==-1)
+       {
+               LM_DBG("parsing headers failed");
+               return 2;
+       }
+
+       if(parse_from_header(msg)<0)
+       {
+               LM_ERR("cannot parse FROM header\n");
+               return 3;
+       }
+
+       if(parse_to_header(msg)<0 || msg->to==NULL)
+       {
+               LM_ERR("cannot parse TO header\n");
+               return 3;
+       }
+
+       if(get_to(msg)==NULL)
+       {
+               LM_ERR("cannot get TO header\n");
+               return 3;
+       }
+
+       return 0;
+}
+
+/**
+ *
+ */
+int gzc_skip_msg(sip_msg_t *msg)
+{
+       hdr_field_t *h;
+       char *sp;
+
+       if(_gzc_hdr_name.len<=0 || _gzc_hdr_value.len<=0)
+               return -1;
+       h = get_hdr_by_name(msg, _gzc_hdr_name.s, _gzc_hdr_name.len);
+       if(h==NULL)
+               return 1;
+       
+       for (sp = h->body.s; sp <= h->body.s + h->body.len - _gzc_hdr_value.len;
+                       sp++)
+       {
+        if (*sp == *_gzc_hdr_value.s
+                       && memcmp(sp, _gzc_hdr_value.s, _gzc_hdr_value.len)==0) {
+               /* found */
+            return 0;
+        }
+    }
+
+       return 2;
+}
+
+/**
+ *
+ */
+char* gzc_msg_update(sip_msg_t *msg, unsigned int *olen)
+{
+       struct dest_info dst;
+
+       init_dest_info(&dst);
+       dst.proto = PROTO_UDP;
+       return build_req_buf_from_sip_req(msg,
+                       olen, &dst, BUILD_NO_LOCAL_VIA|BUILD_NO_VIA1_UPDATE);
+}
+
+/**
+ *
+ */
+int gzc_set_msg_body(sip_msg_t *msg, str *obody, str *nbody)
+{
+       struct lump *anchor;
+       char* buf;
+
+       /* none should be here - just for safety */
+       del_nonshm_lump( &(msg->body_lumps) );
+       msg->body_lumps = NULL;
+
+       if(del_lump(msg, obody->s - msg->buf, obody->len, 0) == 0)
+       {
+               LM_ERR("cannot delete existing body");
+               return -1;
+       }
+
+       anchor = anchor_lump(msg, obody->s - msg->buf, 0, 0);
+
+       if (anchor == 0)
+       {
+               LM_ERR("failed to get body anchor\n");
+               return -1;
+       } 
+
+       buf=pkg_malloc(nbody->len * sizeof(char));
+       if (buf==0)
+       {
+               LM_ERR("out of pkg memory\n");
+               return -1;
+       }
+       memcpy(buf, nbody->s, nbody->len);
+       if (insert_new_lump_after(anchor, buf, nbody->len, 0) == 0)
+       {
+               LM_ERR("failed to insert body lump\n");
+               pkg_free(buf);
+               return -1;
+       }
+       return 0;
+}
+
+/* local buffer to use for compressing/decompressing */
+static char _gzc_local_buffer[BUF_SIZE];
+
+/**
+ *
+ */
+int gzc_msg_received(void *data)
+{
+       sip_msg_t msg;
+       str *obuf;
+       char *nbuf = NULL;
+       str obody;
+       str nbody;
+       unsigned long olen;
+       unsigned long nlen;
+       int ret;
+
+       obuf = (str*)data;
+       memset(&msg, 0, sizeof(sip_msg_t));
+       msg.buf = obuf->s;
+       msg.len = obuf->len;
+
+       if(gzc_prepare_msg(&msg)!=0)
+       {
+               goto done;
+       }
+
+       if(gzc_skip_msg(&msg))
+       {
+               goto done;
+       }
+
+       if(msg.first_line.type==SIP_REQUEST)
+       {
+               if(_gzc_sanity_checks!=0)
+               {
+                       if(scb.check_defaults(&msg)<1)
+                       {
+                               LM_ERR("sanity checks failed\n");
+                               goto done;
+                       }
+               }
+       }
+
+       obody.s = get_body(&msg);
+       if (obody.s==NULL)
+       {
+               LM_DBG("no body for this SIP message\n");
+               goto done;
+       }
+       obody.len = msg.buf + msg.len - obody.s;
+
+       /* decompress the body */
+       nbody.s = _gzc_local_buffer;
+       nlen = BUF_SIZE;
+       olen = obody.len;
+       ret = uncompress((unsigned char*)nbody.s, &nlen,
+                       (unsigned char*)obody.s, olen);
+       if(ret!=Z_OK)
+       {
+               LM_ERR("error decompressing body (%d)\n", ret);
+               goto done;
+       }
+       nbody.len = (int)nlen;
+       LM_DBG("body decompressed - old size: %d - new size: %d\n",
+                       obody.len, nbody.len);
+
+       if(gzc_set_msg_body(&msg, &obody, &nbody)<0)
+       {
+               LM_ERR("error replacing body\n");
+               goto done;
+       }
+
+       nbuf = gzc_msg_update(&msg, (unsigned int*)&obuf->len);
+
+       if(obuf->len>=BUF_SIZE)
+       {
+               LM_ERR("new buffer overflow (%d)\n", obuf->len);
+               pkg_free(nbuf);
+               return -1;
+       }
+       memcpy(obuf->s, nbuf, obuf->len);
+       obuf->s[obuf->len] = '\0';
+
+done:
+       if(nbuf!=NULL)
+               pkg_free(nbuf);
+       free_sip_msg(&msg);
+       return 0;
+}
+
+/**
+ *
+ */
+int gzc_msg_sent(void *data)
+{
+       sip_msg_t msg;
+       str *obuf;
+       str obody;
+       str nbody;
+       unsigned long olen;
+       unsigned long nlen;
+       int ret;
+
+       obuf = (str*)data;
+       memset(&msg, 0, sizeof(sip_msg_t));
+       msg.buf = obuf->s;
+       msg.len = obuf->len;
+
+       if(gzc_prepare_msg(&msg)!=0)
+       {
+               goto done;
+       }
+
+       if(gzc_skip_msg(&msg))
+       {
+               goto done;
+       }
+
+       obody.s = get_body(&msg);
+       if (obody.s==NULL)
+       {
+               LM_DBG("no body for this SIP message\n");
+               goto done;
+       }
+       obody.len = msg.buf + msg.len - obody.s;
+
+       /* decompress the body */
+       nbody.s = _gzc_local_buffer;
+       nlen = BUF_SIZE;
+       olen = obody.len;
+       ret = compress((unsigned char*)nbody.s, &nlen,
+                       (unsigned char*)obody.s, olen);
+       if(ret!=Z_OK)
+       {
+               LM_ERR("error compressing body (%d)\n", ret);
+               goto done;
+       }
+       nbody.len = (int)nlen;
+       LM_DBG("body compressed - old size: %d - new size: %d\n",
+                       obody.len, nbody.len);
+
+       if(gzc_set_msg_body(&msg, &obody, &nbody)<0)
+       {
+               LM_ERR("error replacing body\n");
+               goto done;
+       }
+
+       obuf->s = gzc_msg_update(&msg, (unsigned int*)&obuf->len);
+
+done:
+       free_sip_msg(&msg);
+       return 0;
+}
+