Browse Source

geoip2: added distance function

Added distance function which allows to calculate the distance
between the geocoordinates of the the IP-address passed as
incoming parameter (coordinates for the IP are determined inside
the function) and the geocoordinates passed as parameters for the function.
Nikolay Ivanuschak 2 years ago
parent
commit
60abf76798

+ 23 - 0
src/modules/geoip2/doc/geoip2_admin.xml

@@ -149,6 +149,29 @@ if(geoip2_match("$si", "src"))
 </programlisting>
 	    </example>
 	</section>
+	<section>
+		<title>
+		<function moreinfo="none">distance(ip_addr, latitude, longitude)</function>
+		</title>
+		<para>
+			The function calculates the distance in miles between the geocoordinates of
+			the IP address passed as parameter (the coordinates are calculated inside the function)
+			and the geocoordinates <emphasis>latitude</emphasis> and <emphasis>longitude</emphasis>
+		</para>
+		<example>
+		<title><function>distance</function> usage</title>
+		<programlisting format="linespecific">
+...
+	$var(client_ip) = "109.184.18.64";
+	$var(lat_pos) = "53.200660";
+	$var(lon_pos) = "45.004640";
+	$var(dist) = distance($var(client_ip), $var(lat_pos), $var(lon_pos));
+
+	xlog("distance is $var(dist)\n");
+...
+</programlisting>
+		</example>
+	</section>
 	
     </section>
 

+ 162 - 0
src/modules/geoip2/geoip2_mod.c

@@ -19,6 +19,7 @@
  *
  */
 
+#include <math.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
@@ -36,6 +37,10 @@
 
 MODULE_VERSION
 
+#define MAX_GEO_STR_SIZE 512
+#define EARTH_RADIUS (6371.0072 * 0.6214)
+#define TORADS(degrees) (degrees * (M_PI / 180))
+
 static char *geoip2_path = NULL;
 
 static int mod_init(void);
@@ -46,6 +51,29 @@ static int w_geoip2_match(struct sip_msg *msg, char *str1, char *str2);
 static int geoip2_match(sip_msg_t *msg, str *tomatch, str *pvclass);
 static int geoip2_resid_param(modparam_t type, void *val);
 
+static int w_distance(struct sip_msg *msg, char *str1, char *str2, char *str3);
+static int distance(sip_msg_t *msg, str *_ip_addr, double lat, double lon);
+
+static int fixup_distance_param(void **param, int param_no)
+{
+	if(param_no >= 1 && param_no <= 3) {
+		return fixup_spve_null(param, 1);
+	}
+
+	LM_ERR("invalid parameter number <%d>\n", param_no);
+	return -1;
+}
+
+static int fixup_free_distance_param(void **param, int param_no)
+{
+	if(param_no >= 1 && param_no <= 3) {
+		return fixup_free_spve_null(param, 1);
+	}
+
+	LM_ERR("invalid parameter number <%d>\n", param_no);
+	return -1;
+}
+
 static pv_export_t mod_pvs[] = {
 		{{"gip2", sizeof("gip2") - 1}, PVT_OTHER, pv_get_geoip2, 0,
 				pv_parse_geoip2_name, 0, 0, 0},
@@ -53,6 +81,8 @@ static pv_export_t mod_pvs[] = {
 
 static cmd_export_t cmds[] = {{"geoip2_match", (cmd_function)w_geoip2_match, 2,
 									  fixup_spve_spve, 0, ANY_ROUTE},
+		{"distance", (cmd_function)w_distance, 3, fixup_distance_param,
+				fixup_free_distance_param, ANY_ROUTE},
 		{0, 0, 0, 0, 0, 0}};
 
 static param_export_t params[] = {{"path", PARAM_STRING, &geoip2_path},
@@ -147,6 +177,138 @@ static int w_geoip2_match(sip_msg_t *msg, char *target, char *pvname)
 	return geoip2_match(msg, &tomatch, &pvclass);
 }
 
+static int distance(sip_msg_t *msg, str *_ip_addr, double lat, double lon)
+{
+	char ip_addr[MAX_GEO_STR_SIZE] = {0};
+	double lat1, lon1, lat2, lon2, orig_lat2, orig_lon2;
+	double d_lat, d_lon, a, c;
+	int dist = 0;
+	int gai_error, mmdb_error;
+	MMDB_lookup_result_s record;
+	MMDB_entry_data_s entry_data;
+	gen_lock_t *lock = get_gen_lock();
+
+	if(lock == NULL) {
+		LM_ERR("error GeoIP mutex is not initialized\n");
+		return -1;
+	}
+
+	LM_DBG("ip_addr=%.*s lat=%f lon=%f\n", _ip_addr->len, _ip_addr->s, lat,
+			lon);
+
+	strncpy(ip_addr, _ip_addr->s, _ip_addr->len);
+
+	MMDB_s *geoip_handle = get_geoip_handle();
+	if(geoip_handle == NULL) {
+		LM_ERR("error GeoIP handle is not initialized\n");
+		return -1;
+	}
+
+	lock_get(lock);
+	record = MMDB_lookup_string(
+			geoip_handle, (const char *)ip_addr, &gai_error, &mmdb_error);
+
+	LM_DBG("attempt to match: %s\n", ip_addr);
+	if(gai_error || MMDB_SUCCESS != mmdb_error || !record.found_entry) {
+		LM_DBG("no match for: %s\n", ip_addr);
+		lock_release(lock);
+		return -2;
+	}
+
+	if(MMDB_get_value(&record.entry, &entry_data, "location", "latitude", NULL)
+			!= MMDB_SUCCESS) {
+		LM_ERR("no location/latitude for: %s\n", ip_addr);
+		lock_release(lock);
+		return -2;
+	}
+	if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE) {
+		orig_lat2 = entry_data.double_value;
+	} else {
+		LM_ERR("wrong format for location/latitude\n");
+		lock_release(lock);
+		return -3;
+	}
+
+	if(MMDB_get_value(&record.entry, &entry_data, "location", "longitude", NULL)
+			!= MMDB_SUCCESS) {
+		LM_ERR("no location/longitude for: %s\n", ip_addr);
+		lock_release(lock);
+		return -2;
+	}
+	lock_release(lock);
+	if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE) {
+		orig_lon2 = entry_data.double_value;
+	} else {
+		LM_ERR("wrong format for location/latitude\n");
+		return -3;
+	}
+
+	LM_INFO("for ip_addr=%s the following coordinates: lat=%f lon=%f\n",
+			ip_addr, orig_lat2, orig_lon2);
+
+	lat1 = TORADS(lat);
+	lon1 = TORADS(lon);
+	lat2 = TORADS(orig_lat2);
+	lon2 = TORADS(orig_lon2);
+
+	d_lat = lat2 - lat1;
+	d_lon = lon2 - lon1;
+
+	a = sin(d_lat / 2) * sin(d_lat / 2)
+		+ cos(lat1) * cos(lat2) * sin(d_lon / 2) * sin(d_lon / 2);
+	c = 2 * atan2(sqrt(a), sqrt(1 - a));
+
+	dist = (int)(EARTH_RADIUS * c);
+
+	LM_DBG("lat1=%f lon1=%f lat2=%f lon2=%f distance = %d\n", lat, lon,
+			orig_lat2, orig_lon2, dist);
+
+	return dist;
+}
+
+static int w_distance(struct sip_msg *msg, char *ip_addr_param, char *lat_param,
+		char *lon_param)
+{
+	str ip_addr_str = STR_NULL;
+	str lat_str = STR_NULL;
+	str lon_str = STR_NULL;
+	double lat = 0;
+	double lon = 0;
+	char buf[MAX_GEO_STR_SIZE] = {0};
+
+	if(fixup_get_svalue(msg, (gparam_t *)ip_addr_param, &ip_addr_str) < 0) {
+		LM_ERR("cannot get the IP address\n");
+		return -1;
+	}
+	if(fixup_get_svalue(msg, (gparam_t *)lat_param, &lat_str) < 0) {
+		LM_ERR("cannot get latitude string\n");
+		return -1;
+	}
+	if(fixup_get_svalue(msg, (gparam_t *)lon_param, &lon_str) < 0) {
+		LM_ERR("cannot get longitude string\n");
+		return -1;
+	}
+
+	strncpy(buf, lat_str.s, lat_str.len);
+	lat = atof(buf);
+	if(!lat && errno == ERANGE) {
+		LM_ERR("cannot convert string to double: %.*s\n", lat_str.len,
+				lat_str.s);
+		return -1;
+	}
+
+	memset(buf, 0, MAX_GEO_STR_SIZE);
+	strncpy(buf, lon_str.s, lon_str.len);
+	lon = atof(buf);
+	if(!lon && errno == ERANGE) {
+		LM_ERR("cannot convert string to double: %.*s\n", lon_str.len,
+				lon_str.s);
+		return -1;
+	}
+
+	return distance(msg, &ip_addr_str, lat, lon);
+}
+
 static void geoip2_rpc_reload(rpc_t *rpc, void *ctx)
 {
 	if(geoip2_reload_pv(geoip2_path) != 0) {

+ 10 - 0
src/modules/geoip2/geoip2_pv.c

@@ -70,6 +70,16 @@ static gen_lock_t *lock = NULL;
 
 static sr_geoip2_item_t *_sr_geoip2_list = NULL;
 
+MMDB_s *get_geoip_handle(void)
+{
+	return _handle_GeoIP;
+}
+
+gen_lock_t *get_gen_lock(void)
+{
+	return lock;
+}
+
 sr_geoip2_record_t *sr_geoip2_get_record(str *name)
 {
 	sr_geoip2_item_t *it = NULL;

+ 2 - 0
src/modules/geoip2/geoip2_pv.h

@@ -35,5 +35,7 @@ int geoip2_reload_pv(char *path);
 void geoip2_pv_reset(str *pvclass);
 int geoip2_update_pv(str *tomatch, str *pvclass);
 int sr_geoip2_add_resid(str *rname);
+MMDB_s *get_geoip_handle(void);
+gen_lock_t *get_gen_lock(void);
 
 #endif