Browse Source

geoip2: new module exporting geoip API to configuration file

- uses the latest version of maxmind API/library
- has support for IPv6
- can be used instead of geoip module
Sergey Okhapkin 10 năm trước cách đây
mục cha
commit
cdba71eac0

+ 15 - 0
modules/geoip2/Makefile

@@ -0,0 +1,15 @@
+# $Id$
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=geoip2.so
+LIBS= -lmaxminddb
+
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kmi/kmi
+
+include ../../Makefile.modules

+ 146 - 0
modules/geoip2/README

@@ -0,0 +1,146 @@
+geoip2 Module
+
+Sergey Okhapkin
+
+   callwithus.com
+
+Edited by
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+   Copyright © 2010 Daniel-Constantin Mierla (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. path (string)
+
+        4. Functions
+
+              4.1. geoip2_match(ipaddr, pvc)
+
+        5. Exported pseudo-variables
+
+   List of Examples
+
+   1.1. Set path parameter
+   1.2. geoip2_match usage
+
+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. path (string)
+
+   4. Functions
+
+        4.1. geoip2_match(ipaddr, pvc)
+
+   5. Exported pseudo-variables
+
+1. Overview
+
+   This module allows real-time queries against the Max Mind GeoIP2
+   database to be performed from the config script.
+
+   The Max Mind GeoIP2 database is a map of IP network address assignments
+   to geographical locales that can be useful -- though approximate -- in
+   identifying the physical location with which an IP host address is
+   associated on a relatively granular level.
+
+   This database itself can be obtained on a free or commercial basis from
+   http://dev.maxmind.com/geoip/. The library libmaxminddb that interfaces
+   with the Max Mind API, as well as scripts to automate downloading of
+   the on-disk version are available at
+   http://dev.maxmind.com/geoip/geoip2/downloadable/.
+
+   This module exports a new class of pseudo-variables - $gip2(pvc=>key) -
+   to enable access to the results of a query to the database.
+
+   Many queries can be done and store results in different containers to
+   be able to use in parallel. Database is loaded at startup in cache.
+
+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:
+     * libmaxminddb - the GeoIP2 library.
+
+3. Parameters
+
+   3.1. path (string)
+
+3.1. path (string)
+
+   Path to the GeoIP2 database file.
+
+   Default value is “null”.
+
+   Example 1.1. Set path parameter
+...
+modparam("geoip2", "path", "/usr/local/share/GeoIP/GeoLite2-City.mmdb")
+...
+
+4. Functions
+
+   4.1. geoip2_match(ipaddr, pvc)
+
+4.1.  geoip2_match(ipaddr, pvc)
+
+   Match ipaddr against the GeoIP database and set the pvc container. The
+   function has to be called before accessing a key via: $gip2(pvc=>key).
+
+   Example 1.2. geoip2_match usage
+...
+if(geoip2_match("$si", "src"))
+    xlog("SIP message from: $gip2(src=>cc)\n");
+...
+
+5. Exported pseudo-variables
+
+     * $gip2(pvc=>key) - pvc is an identifier for this query result; it is
+       designated by the second parameter of geoip2_match(). The key can
+       be one of the following:
+          + cc - country code
+          + tz - time zone
+          + zip - postal code
+          + lat - latitude
+          + lon - longitude
+          + nmask - network mask (CIDR format)
+          + city - city
+          + regc - region
+          + regn - region name
+          + metro - metro code
+
+   Exported pseudo-variables are documented at
+   http://www.kamailio.org/wiki/.

+ 4 - 0
modules/geoip2/doc/Makefile

@@ -0,0 +1,4 @@
+docs = geoip2.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 36 - 0
modules/geoip2/doc/geoip2.xml

@@ -0,0 +1,36 @@
+<?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>geoip2 Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Sergey</firstname>
+		<surname>Okhapkin</surname>
+		<affiliation><orgname>callwithus.com</orgname></affiliation>
+	    </author>
+	    <editor>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		    <email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2010</year>
+	    <holder>Daniel-Constantin Mierla (asipto.com)</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+    
+	<xi:include href="geoip2_admin.xml"/>
+    
+</book>

+ 175 - 0
modules/geoip2/doc/geoip2_admin.xml

@@ -0,0 +1,175 @@
+<?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 allows real-time queries against the Max Mind GeoIP2 
+		database to be performed from the config script.  
+	</para>
+	<para>
+		The Max Mind GeoIP2 database is a map of IP network address assignments 
+		to geographical locales that can be useful -- though approximate --
+		in identifying the physical location with which an IP host address
+		is associated on a relatively granular level.
+	</para>
+	<para>
+		This database itself can be obtained on a free or commercial basis 
+		from <ulink url="http://dev.maxmind.com/geoip/">
+		http://dev.maxmind.com/geoip/</ulink>. The 
+		library libmaxminddb
+		that interfaces with the Max Mind API, as well as scripts to
+		automate downloading of the on-disk version are available at
+		<ulink url="http://dev.maxmind.com/geoip/geoip2/downloadable/">
+		http://dev.maxmind.com/geoip/geoip2/downloadable/</ulink>.
+	</para>
+	<para>
+		This module exports a new class of pseudo-variables -
+		$gip2(pvc=&gt;key) - to enable access to the results of a query to the
+		database.
+	</para>
+	<para>
+		Many queries can be done and store results in different containers to
+		be able to use in parallel. Database is loaded at startup in cache.
+	</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>libmaxminddb</emphasis> - the GeoIP2 library.
+			</para>
+		    </listitem>
+	    	</itemizedlist>
+	    </para>
+	</section>
+    </section>
+    <section>
+	<title>Parameters</title>
+	<section>
+	    <title><varname>path</varname> (string)</title>
+	    <para>
+		Path to the GeoIP2 database file.
+	    </para>
+	    <para>
+		<emphasis>
+		    Default value is <quote>null</quote>.
+		</emphasis>
+	    </para>
+	    <example>
+		<title>Set <varname>path</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("geoip2", "path", "/usr/local/share/GeoIP/GeoLite2-City.mmdb")
+...
+</programlisting>
+	    </example>
+	</section>
+
+	</section>
+	
+    <section>
+	<title>Functions</title>
+ 	<section>
+	    <title>
+		<function moreinfo="none">geoip2_match(ipaddr, pvc)</function>
+	    </title>
+	    <para>
+			Match ipaddr against the GeoIP database and set the pvc container. The
+			function has to be called before accessing a key via: $gip2(pvc=&gt;key).
+	    </para>
+		<example>
+		<title><function>geoip2_match</function> usage</title>
+		<programlisting format="linespecific">
+...
+if(geoip2_match("$si", "src"))
+    xlog("SIP message from: $gip2(src=&gt;cc)\n");
+...
+</programlisting>
+	    </example>
+	</section>
+	
+    </section>
+	
+	<section>
+		<title>Exported pseudo-variables</title>
+		<itemizedlist>
+			<listitem><para>
+				<emphasis>$gip2(pvc=&gt;key)</emphasis> - <emphasis>pvc</emphasis> is an 
+				identifier for this query result;  it is designated by the second 
+				parameter of geoip2_match(). The <emphasis>key</emphasis> can be one of
+				the following:
+				</para>
+			<itemizedlist>
+				<listitem><para>
+					<emphasis>cc</emphasis> - country code
+				</para></listitem>
+				<listitem><para>
+					<emphasis>tz</emphasis> - time zone
+				</para></listitem>
+				<listitem><para>
+					<emphasis>zip</emphasis> - postal code
+				</para></listitem>
+				<listitem><para>
+					<emphasis>lat</emphasis> - latitude
+				</para></listitem>
+				<listitem><para>
+					<emphasis>lon</emphasis> - longitude
+				</para></listitem>
+				<listitem><para>
+					<emphasis>nmask</emphasis> - network mask (CIDR format)
+				</para></listitem>
+				<listitem><para>
+					<emphasis>city</emphasis> - city
+				</para></listitem>
+				<listitem><para>
+					<emphasis>regc</emphasis> - region
+				</para></listitem>
+				<listitem><para>
+					<emphasis>regn</emphasis> - region name
+				</para></listitem>
+				<listitem><para>
+					<emphasis>metro</emphasis> - metro code
+				</para></listitem>
+			</itemizedlist>
+			</listitem>
+		</itemizedlist>
+		<para>
+		Exported pseudo-variables are documented at &kamwikilink;.
+		</para>
+	</section>
+
+</chapter>
+

+ 139 - 0
modules/geoip2/geoip2_mod.c

@@ -0,0 +1,139 @@
+/**
+ * $Id$
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file 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
+ *
+ *
+ * This file 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+#include "../../pvar.h"
+#include "../../mod_fix.h"
+
+#include "geoip2_pv.h"
+
+MODULE_VERSION
+
+static char *geoip2_path = NULL;
+
+static int  mod_init(void);
+static void mod_destroy(void);
+
+static int w_geoip2_match(struct sip_msg* msg, char* str1, char* str2);
+static int geoip2_match(struct sip_msg *msg, gparam_t *target, gparam_t *pvname);
+
+static pv_export_t mod_pvs[] = {
+	{ {"gip2", sizeof("gip2")-1}, PVT_OTHER, pv_get_geoip2, 0,
+		pv_parse_geoip2_name, 0, 0, 0 },
+	{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+
+static cmd_export_t cmds[]={
+	{"geoip2_match", (cmd_function)w_geoip2_match, 2, fixup_spve_spve,
+		0, ANY_ROUTE},
+	{0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[]={
+	{"path",     PARAM_STRING, &geoip2_path},
+	{0, 0, 0}
+};
+
+struct module_exports exports = {
+	"geoip2",
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,
+	params,
+	0,
+	0,              /* exported MI functions */
+	mod_pvs,        /* exported pseudo-variables */
+	0,              /* extra processes */
+	mod_init,       /* module initialization function */
+	0,              /* response function */
+	mod_destroy,    /* destroy function */
+	0               /* per child init function */
+};
+
+
+
+/**
+ * init module function
+ */
+static int mod_init(void)
+{
+
+	if(geoip2_path==NULL || strlen(geoip2_path)==0)
+	{
+		LM_ERR("path to GeoIP database file not set\n");
+		return -1;
+	}
+
+	if(geoip2_init_pv(geoip2_path)!=0)
+	{
+		LM_ERR("cannot init for database file at: %s\n", geoip2_path);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * destroy module function
+ */
+static void mod_destroy(void)
+{
+	geoip2_destroy_pv();
+}
+
+static int w_geoip2_match(struct sip_msg* msg, char* str1, char* str2)
+{
+	return geoip2_match(msg, (gparam_t*)str1, (gparam_t*)str2);
+}
+
+static int geoip2_match(struct sip_msg *msg, gparam_t *target, gparam_t *pvname)
+{
+	str tomatch;
+	str pvclass;
+	
+	if(msg==NULL)
+	{
+		LM_ERR("received null msg\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, target, &tomatch)<0)
+	{
+		LM_ERR("cannot get the address\n");
+		return -1;
+	}
+	if(fixup_get_svalue(msg, pvname, &pvclass)<0)
+	{
+		LM_ERR("cannot get the pv class\n");
+		return -1;
+	}
+	geoip2_pv_reset(&pvclass);
+
+	return geoip2_update_pv(&tomatch, &pvclass);
+}
+

+ 476 - 0
modules/geoip2/geoip2_pv.c

@@ -0,0 +1,476 @@
+/**
+ * $Id$
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file 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
+ *
+ *
+ * This file 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 <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "../../dprint.h"
+#include "../../hashes.h"
+#include "../../pvar.h"
+
+#include "geoip2_pv.h"
+
+typedef struct _sr_geoip2_record {
+	MMDB_lookup_result_s record;
+	str time_zone;
+	str zip;
+	str city;
+	str region_code;
+	str region_name;
+	str country;
+	char latitude[16];
+	char longitude[16];
+	char metro[16];
+	char nmask[8];
+	char tomatch[256];
+	int flags;
+} sr_geoip2_record_t;
+
+typedef struct _sr_geoip2_item {
+	str pvclass;
+	unsigned int hashid;
+	sr_geoip2_record_t r;
+	struct _sr_geoip2_item *next;
+} sr_geoip2_item_t;
+
+typedef struct _geoip2_pv {
+	sr_geoip2_item_t *item;
+	int type;
+} geoip2_pv_t;
+
+static MMDB_s _handle_GeoIP;
+
+static sr_geoip2_item_t *_sr_geoip2_list = NULL;
+
+sr_geoip2_record_t *sr_geoip2_get_record(str *name)
+{
+	sr_geoip2_item_t *it = NULL;
+	unsigned int hashid = 0;
+
+	hashid =  get_hash1_raw(name->s, name->len);
+
+	it = _sr_geoip2_list;
+	while(it!=NULL)
+	{
+		if(it->hashid==hashid && it->pvclass.len == name->len
+				&& strncmp(it->pvclass.s, name->s, name->len)==0)
+			return &it->r;
+		it = it->next;
+	}
+	return NULL;
+}
+
+sr_geoip2_item_t *sr_geoip2_add_item(str *name)
+{
+	sr_geoip2_item_t *it = NULL;
+	unsigned int hashid = 0;
+
+	hashid =  get_hash1_raw(name->s, name->len);
+
+	it = _sr_geoip2_list;
+	while(it!=NULL)
+	{
+		if(it->hashid==hashid && it->pvclass.len == name->len
+				&& strncmp(it->pvclass.s, name->s, name->len)==0)
+			return it;
+		it = it->next;
+	}
+	/* add new */
+	it = (sr_geoip2_item_t*)pkg_malloc(sizeof(sr_geoip2_item_t));
+	if(it==NULL)
+	{
+		LM_ERR("no more pkg\n");
+		return NULL;
+	}
+	memset(it, 0, sizeof(sr_geoip2_item_t));
+	it->pvclass.s = (char*)pkg_malloc(name->len+1);
+	if(it->pvclass.s==NULL)
+	{
+		LM_ERR("no more pkg.\n");
+		pkg_free(it);
+		return NULL;
+	}
+	memcpy(it->pvclass.s, name->s, name->len);
+	it->pvclass.s[name->len] = '\0';
+	it->pvclass.len = name->len;
+	it->hashid = hashid;
+	it->next = _sr_geoip2_list;
+	_sr_geoip2_list = it;
+	return it;
+}
+
+
+int pv_parse_geoip2_name(pv_spec_p sp, str *in)
+{
+	geoip2_pv_t *gpv=NULL;
+	char *p;
+	str pvc;
+	str pvs;
+	if(sp==NULL || in==NULL || in->len<=0)
+		return -1;
+
+	gpv = (geoip2_pv_t*)pkg_malloc(sizeof(geoip2_pv_t));
+	if(gpv==NULL)
+		return -1;
+
+	memset(gpv, 0, sizeof(geoip2_pv_t));
+
+	p = in->s;
+
+	while(p<in->s+in->len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+		p++;
+	if(p>in->s+in->len || *p=='\0')
+		goto error;
+	pvc.s = p;
+	while(p < in->s + in->len)
+	{
+		if(*p=='=' || *p==' ' || *p=='\t' || *p=='\n' || *p=='\r')
+			break;
+		p++;
+	}
+	if(p>in->s+in->len || *p=='\0')
+		goto error;
+	pvc.len = p - pvc.s;
+	if(*p!='=')
+	{
+		while(p<in->s+in->len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+			p++;
+		if(p>in->s+in->len || *p=='\0' || *p!='=')
+			goto error;
+	}
+	p++;
+	if(*p!='>')
+		goto error;
+	p++;
+
+	pvs.len = in->len - (int)(p - in->s);
+	pvs.s = p;
+	LM_DBG("geoip2 [%.*s] - key [%.*s]\n", pvc.len, pvc.s,
+			pvs.len, pvs.s);
+
+	gpv->item = sr_geoip2_add_item(&pvc);
+	if(gpv->item==NULL)
+		goto error;
+
+	switch(pvs.len)
+	{
+		case 2: 
+			if(strncmp(pvs.s, "cc", 2)==0)
+				gpv->type = 0;
+			else if(strncmp(pvs.s, "tz", 2)==0)
+				gpv->type = 1;
+			else goto error;
+		break;
+		case 3: 
+			if(strncmp(pvs.s, "zip", 3)==0)
+				gpv->type = 2;
+			else if(strncmp(pvs.s, "lat", 3)==0)
+				gpv->type = 3;
+			else if(strncmp(pvs.s, "lon", 3)==0)
+				gpv->type = 4;
+			else goto error;
+		break;
+		case 4: 
+			if(strncmp(pvs.s, "city", 4)==0)
+				gpv->type = 8;
+			else if(strncmp(pvs.s, "regc", 4)==0)
+				gpv->type = 10;
+			else if(strncmp(pvs.s, "regn", 4)==0)
+				gpv->type = 11;
+			else goto error;
+		break;
+		case 5: 
+			if(strncmp(pvs.s, "metro", 5)==0)
+				gpv->type = 12;
+			else if(strncmp(pvs.s, "nmask", 5)==0)
+				gpv->type = 13;
+			else goto error;
+		break;
+		default:
+			goto error;
+	}
+	sp->pvp.pvn.u.dname = (void*)gpv;
+	sp->pvp.pvn.type = PV_NAME_OTHER;
+
+	return 0;
+
+error:
+	if(gpv!=NULL)
+		pkg_free(gpv);
+
+	LM_ERR("error at PV geoip2 name: %.*s\n", in->len, in->s);
+	return -1;
+}
+
+int pv_geoip2_get_strzval(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res, char *sval)
+{
+	str s;
+	if(sval==NULL)
+		return pv_get_null(msg, param, res);
+
+	s.s = sval;
+	s.len = strlen(s.s);
+	return pv_get_strval(msg, param, res, &s);
+}
+
+int pv_get_geoip2(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res)
+{
+	geoip2_pv_t *gpv;
+	MMDB_entry_data_s entry_data;
+
+	if(msg==NULL || param==NULL)
+		return -1;
+
+	gpv = (geoip2_pv_t*)param->pvn.u.dname;
+	if(gpv==NULL)
+		return -1;
+	if(gpv->item==NULL)
+		return pv_get_null(msg, param, res);
+
+	switch(gpv->type)
+	{
+		case 1: /* tz */
+			if(gpv->item->r.time_zone.s==NULL)
+			{
+				if(gpv->item->r.flags&1)
+					return pv_get_null(msg, param, res);
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"location","time_zone", NULL
+					) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+					gpv->item->r.time_zone.s = (char *)entry_data.utf8_string;
+					gpv->item->r.time_zone.len = entry_data.data_size;
+				}
+				gpv->item->r.flags |= 1;
+			}
+			return pv_get_strval(msg, param, res, &gpv->item->r.time_zone);
+		case 2: /* zip */
+			if(gpv->item->r.zip.s==NULL)
+			{
+				if(gpv->item->r.flags&32)
+					return pv_get_null(msg, param, res);
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"postal","code", NULL) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+					gpv->item->r.zip.s = (char *)entry_data.utf8_string;
+					gpv->item->r.zip.len = entry_data.data_size;
+				}
+				gpv->item->r.flags |= 32;
+			}
+			return pv_get_strval(msg, param, res, &gpv->item->r.zip);
+		case 3: /* lat */
+			if((gpv->item->r.flags&2)==0)
+			{
+				gpv->item->r.latitude[0] = '\0';
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"location","latitude", NULL) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE)
+					snprintf(gpv->item->r.latitude, 15, "%f", entry_data.double_value);
+				gpv->item->r.flags |= 2;
+			}
+			return pv_geoip2_get_strzval(msg, param, res,
+					gpv->item->r.latitude);
+		case 4: /* lon */
+			if((gpv->item->r.flags&4)==0)
+			{
+				gpv->item->r.latitude[0] = '\0';
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"location","longitude", NULL) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE)
+					snprintf(gpv->item->r.longitude, 15, "%f", entry_data.double_value);
+				gpv->item->r.flags |= 4;
+			}
+			return pv_geoip2_get_strzval(msg, param, res,
+					gpv->item->r.longitude);
+		case 8: /* city */
+			if(gpv->item->r.city.s==NULL)
+			{
+				if(gpv->item->r.flags&64)
+					return pv_get_null(msg, param, res);
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"city","names","en", NULL
+					) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+					gpv->item->r.city.s = (char *)entry_data.utf8_string;
+					gpv->item->r.city.len = entry_data.data_size;
+				}
+				gpv->item->r.flags |= 64;
+			}
+			return pv_get_strval(msg, param, res, &gpv->item->r.city);
+		case 10: /* regc */
+			if(gpv->item->r.region_code.s==NULL)
+			{
+				if(gpv->item->r.flags&128)
+					return pv_get_null(msg, param, res);
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"subdivisions","0","iso_code", NULL
+					) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+					gpv->item->r.region_code.s = (char *)entry_data.utf8_string;
+					gpv->item->r.region_code.len = entry_data.data_size;
+				}
+				gpv->item->r.flags |= 128;
+			}
+			return pv_get_strval(msg, param, res, &gpv->item->r.region_code);
+		case 11: /* regn */
+			if(gpv->item->r.region_name.s==NULL)
+			{
+				if(gpv->item->r.flags&16)
+					return pv_get_null(msg, param, res);
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"subdivisions","0","names","en", NULL
+					) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+					gpv->item->r.region_name.s = (char *)entry_data.utf8_string;
+					gpv->item->r.region_name.len = entry_data.data_size;
+				}
+				gpv->item->r.flags |= 16;
+			}
+			return pv_get_strval(msg, param, res, &gpv->item->r.region_name);
+		case 12: /* metro */
+			if((gpv->item->r.flags&256)==0)
+			{
+				gpv->item->r.metro[0] = '\0';
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"location","metro_code", NULL) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UINT16)
+					snprintf(gpv->item->r.metro, 15, "%hd", entry_data.uint16);
+				gpv->item->r.flags |= 256;
+			}
+			return pv_geoip2_get_strzval(msg, param, res,
+					gpv->item->r.metro);
+		case 13: /* nmask */
+			if((gpv->item->r.flags&1024)==0)
+			{
+				gpv->item->r.nmask[0] = '\0';
+				snprintf(gpv->item->r.nmask, 8, "%hd", gpv->item->r.record.netmask);
+				gpv->item->r.flags |= 1024;
+			}
+			return pv_geoip2_get_strzval(msg, param, res,
+					gpv->item->r.nmask);
+		default: /* cc */
+			if(gpv->item->r.country.s==NULL)
+			{
+				if(gpv->item->r.flags&512)
+					return pv_get_null(msg, param, res);
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"country","iso_code", NULL
+					) != MMDB_SUCCESS)
+					return pv_get_null(msg, param, res);
+				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+					gpv->item->r.country.s = (char *)entry_data.utf8_string;
+					gpv->item->r.country.len = entry_data.data_size;
+				}
+				if(MMDB_get_value(&gpv->item->r.record.entry, &entry_data,
+					"traits","is_anonymous_proxy", NULL) == MMDB_SUCCESS
+					&& entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_BOOLEAN
+					&& entry_data.boolean) {
+					gpv->item->r.country.s = "A1";
+					gpv->item->r.country.len = 2;
+				}
+				gpv->item->r.flags |= 512;
+			}
+			return pv_get_strval(msg, param, res, &gpv->item->r.country);
+	}
+}
+
+int geoip2_init_pv(char *path)
+{
+	int status = MMDB_open(path, MMDB_MODE_MMAP, &_handle_GeoIP);
+	
+	if(MMDB_SUCCESS != status)
+	{
+		LM_ERR("cannot open GeoIP database file at: %s\n", path);
+		return -1;
+	}
+	return 0;
+}
+
+void geoip2_destroy_list(void)
+{
+}
+
+void geoip2_destroy_pv(void)
+{
+	MMDB_close(&_handle_GeoIP);
+}
+
+void geoip2_pv_reset(str *name)
+{
+	sr_geoip2_record_t *gr = NULL;
+	
+	gr = sr_geoip2_get_record(name);
+
+	if(gr==NULL)
+		return;
+	memset(gr, 0, sizeof(struct _sr_geoip2_record));
+}
+
+int geoip2_update_pv(str *tomatch, str *name)
+{
+	sr_geoip2_record_t *gr = NULL;
+	int gai_error, mmdb_error;
+	
+	if(tomatch->len>255)
+	{
+		LM_DBG("target too long (max 255): %s\n", tomatch->s);
+		return -3;
+	}
+	
+	gr = sr_geoip2_get_record(name);
+	if(gr==NULL)
+	{
+		LM_DBG("container not found: %s\n", tomatch->s);
+		return - 4;
+	}
+
+	strncpy(gr->tomatch, tomatch->s, tomatch->len);
+	tomatch->s[tomatch->len] = '\0';
+	gr->record = MMDB_lookup_string(&_handle_GeoIP,
+			(const char*)gr->tomatch,
+			&gai_error, &mmdb_error
+			);
+	LM_DBG("attempt to match: %s\n", gr->tomatch);
+	if (gai_error || MMDB_SUCCESS != mmdb_error || !gr->record.found_entry)
+	{
+		LM_DBG("no match for: %s\n", gr->tomatch);
+		return -2;
+	}
+	LM_DBG("geoip2 PV updated for: %s\n", gr->tomatch);
+
+	return 1;
+}
+

+ 40 - 0
modules/geoip2/geoip2_pv.h

@@ -0,0 +1,40 @@
+/**
+ * $Id$
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file 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
+ *
+ *
+ * This file 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 _GEOIP_PV_H_
+#define _GEOIP_PV_H_
+
+#include <maxminddb.h>
+
+#include "../../pvar.h"
+
+int pv_parse_geoip2_name(pv_spec_p sp, str *in);
+int pv_get_geoip2(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res);
+
+int geoip2_init_pv(char *path);
+void geoip2_destroy_pv(void);
+void geoip2_pv_reset(str *pvclass);
+int geoip2_update_pv(str *tomatch, str *pvclass);
+
+#endif
+