diff --git a/db/schema/fraud_detection.xml b/db/schema/fraud_detection.xml new file mode 100644 index 0000000000..f8e6a5df7f --- /dev/null +++ b/db/schema/fraud_detection.xml @@ -0,0 +1,141 @@ + + +%entities; + +]> + + + fraud_detection + 1 + &MYSQL_TABLE_TYPE; + + This table is used by the Fraud Detection module to store + information about fraud-profiles. + More information can be found at: &OPENSIPS_MOD_DOC;fraud_detection.html. + + + + + ruleid + unsigned int + &table_id_len; + + + + int,auto + Rule unique ID + + + + + profileid + unsigned int + The ID of the profile the current rule is part of + + + + + prefix + string + 64 + Numerical prefix to match this rule + + + + start_hour + string + 5 + Start of the interval in which the rule should be matched. + + + + + end_hour + string + 5 + End of the interval in which the rule should be matched. + + + + + daysoftheweek + string + 64 + List/interval of days in which the rule is available. + + + + + cpm_warning + unsigned int + 5 + Warning threshold for calls per minute. + + + + cpm_critical + unsigned int + 5 + Crtical threshold for calls per minute. + + + + call_duration_warning + unsigned int + 5 + Warning threshold for calls per minute. + + + + call_duration_critical + unsigned int + 5 + Crtical threshold for call duration. + + + + total_calls_warning + unsigned int + 5 + Warning threshold for total calls. + + + + total_calls_critical + unsigned int + 5 + Crtical threshold for total calls. + + + + concurrent_calls_warning + unsigned int + 5 + Warning threshold for concurrent calls. + + + + concurrent_calls_critical + unsigned int + 5 + Crtical threshold for concurrent calls. + + + + sequential_calls_warning + unsigned int + 5 + Warning threshold for sequential calls. + + + + sequential_calls_critical + unsigned int + 5 + Crtical threshold for sequential calls. + + +
diff --git a/db/schema/opensips-fraud_detection.xml b/db/schema/opensips-fraud_detection.xml new file mode 100644 index 0000000000..a8abef30b7 --- /dev/null +++ b/db/schema/opensips-fraud_detection.xml @@ -0,0 +1,13 @@ + + +%entities; + +]> + + + Fraud Detection + + diff --git a/modules/fraud_detection/Makefile b/modules/fraud_detection/Makefile new file mode 100644 index 0000000000..8f482eceea --- /dev/null +++ b/modules/fraud_detection/Makefile @@ -0,0 +1,11 @@ +# New module name +# +#- +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs +auto_gen= +NAME=fraud_detection.so +LIBS= + +include ../../Makefile.modules diff --git a/modules/fraud_detection/README b/modules/fraud_detection/README new file mode 100644 index 0000000000..e542849ec9 --- /dev/null +++ b/modules/fraud_detection/README @@ -0,0 +1,448 @@ +Fraud Detection Module + +Andrei Daniel Datcu + + + + Copyright © 2014 OpenSIPs Foundation + Revision History + Revision $Revision: 1 $ $Date$ + __________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1.1. Overview + + 1.1.1. Monitorized Stats + 1.1.2. Fraud rules + + 1.2. Dependencies + + 1.2.1. OpenSIPS modules + 1.2.2. External libraries or applications + + 1.3. Exported Parameters + + 1.3.1. db_url (string) + 1.3.2. table_name (string) + 1.3.3. rid_col (string) + 1.3.4. pid_col (string) + 1.3.5. prefix_col (string) + 1.3.6. start_h (string) + 1.3.7. end_h (string) + 1.3.8. days_col (string) + 1.3.9. cpm_thresh_warn_col (string) + 1.3.10. cpm_thresh_crit_col (string) + 1.3.11. calldur_thresh_warn_col (string) + 1.3.12. calldur_thresh_crit_col (string) + 1.3.13. totalc_thresh_warn_col (string) + 1.3.14. totalc_thresh_crit_col (string) + 1.3.15. concalls_thresh_warn_col (string) + 1.3.16. concalls_thresh_crit_col (string) + 1.3.17. seqcalls_thresh_warn_col (string) + 1.3.18. seqcalls_thresh_crit_col (string) + + 1.4. Exported Functions + + 1.4.1. check_fraud(user, number, profile_id) + + 1.5. Exported MI Functions + + 1.5.1. show_fraud_stats + 1.5.2. fraud_reload + + 1.6. Exported Events + + 1.6.1. E_FRD_WARNING + 1.6.2. E_FRD_CRITICAL + + List of Examples + + 1.1. Set the “db_url” parameter + 1.2. Set the “table_name” parameter + 1.3. Set “rid_col” parameter + 1.4. Set “pid_col” parameter + 1.5. Set “prefix_col” parameter + 1.6. Set “start_h” parameter + 1.7. Set “end_h” parameter + 1.8. Set “days_col” parameter + 1.9. Set “cpm_thresh_warn_col” parameter + 1.10. Set “cpm_thresh_crit_col” parameter + 1.11. Set “calldur_thresh_warn_col” parameter + 1.12. Set “calldur_thresh_crit_col” parameter + 1.13. Set “totalc_thresh_warn_col” parameter + 1.14. Set “totalc_thresh_crit_col” parameter + 1.15. Set “concalls_thresh_warn_col” parameter + 1.16. Set “concalls_thresh_crit_col” parameter + 1.17. Set “seqcalls_thresh_warn_col” parameter + 1.18. Set “seqcalls_thresh_crit_col” parameter + +Chapter 1. Admin Guide + +1.1. Overview + + This module provides a way to prevent some basic fraud attacks. + Alerts are provided through return codes and events. + +1.1.1. Monitorized Stats + + Basically, this module watches the following parameters: + * Total calls + * Calls per minute + * Concurrent calls + * Number of sequential calls + * Call duration + + Each of the above parameters is monitored for every user and + every called prefix separately. The stats are altered whenever + the check_fraud function is called. The function assumes a new + call is made, and checks the called number against all the + rules from the supplied profile. The rule's prefix is + considered to be the called prefix which along with the + provided user will be used to monitor values for the 5 + parameters. + +1.1.2. Fraud rules + + A rule is a set of two thresholds (warning and critical + thresholds) for each of the five parameters (as described + above) and is only available for a specified prefix. Further + more, a rule will only match between the indicated hours in the + indicated days of the week (similarly to a dr rule). A fraud + profile is simply a group of fraud rules and is used to only to + limit the list of rules to match when calling the check_fraud + function. + +1.2. Dependencies + +1.2.1. OpenSIPS modules + + The following modules must be loaded before this module: + * drouting + * dialog + +1.2.2. External libraries or applications + + The following libraries or applications must be installed + before running OpenSIPS with this module: + * none. + +1.3. Exported Parameters + +1.3.1. db_url (string) + + Database where to load the rules from. + + Default value is “NULL”. At least one db_url should be defined + for the fraud_detection module to work. + + Example 1.1. Set the “db_url” parameter +... +modparam("fraud_detection", "db_url", "mysql://user:passwb@localhost/dat +abase") +... + +1.3.2. table_name (string) + + If you want to load the rules from the database you must set + this parameter as the database name. + + The default value is “fraud_detection”. + + Example 1.2. Set the “table_name” parameter +... +modparam("fraud_detection", "table_name", "my_fraud") +... + +1.3.3. rid_col (string) + + The column's name in the database storing the fraud rule's id. + + Default value is “ruleid”. + + Example 1.3. Set “rid_col” parameter +... +modparam("dispatcher", "rid_col", "theruleid"") +... + +1.3.4. pid_col (string) + + The column's name in the database storing the fraud profile's + id. + + Please keep in mind that a profile is merely a set of rules. + + Default value is “profileid”. + + Example 1.4. Set “pid_col” parameter +... +modparam("dispatcher", "pid_col", "profile"") +... + +1.3.5. prefix_col (string) + + The column's name in the database storing the prefix for which + the fraud rule will match. + + Default value is “prefix”. + + Example 1.5. Set “prefix_col” parameter +... +modparam("dispatcher", "prefix_col", "myprefix") +... + +1.3.6. start_h (string) + + The column's name in the database storing the the start time of + the interval in which the rule will match. + + The time needs to be specified as string using the format: + “HH:MM” + + Default value is “start_hour”. + + Example 1.6. Set “start_h” parameter +... +modparam("dispatcher", "start_h", "the_start_time") +... + +1.3.7. end_h (string) + + The column's name in the database storing the the end time of + the interval in which the rule will match. + + The time needs to be specified as string using the format: + “HH:MM” + + Default value is “end_hour”. + + Example 1.7. Set “end_h” parameter +... +modparam("dispatcher", "end_h", "the_end_time") +... + +1.3.8. days_col (string) + + The column's name in the database storing the week days in + which the fraud rule's interval is available. + + The daysoftheweek needs to be specified as a string containing + a list of days or intervals. Each day must be specified using + the first three letters of its name. A valid string would be: + "Fri-Mon, Wed, Thu" + + Default value is “daysoftheweek”. + + Example 1.8. Set “days_col” parameter +... +modparam("dispatcher", "days_col", "days") +... + +1.3.9. cpm_thresh_warn_col (string) + + The column's name in the database storing the warning threshold + value for calls per minute. + + Default value is “cpm_warning”. + + Example 1.9. Set “cpm_thresh_warn_col” parameter +... +modparam("dispatcher", "cpm_thresh_warn_col", "cpm_warn_thresh") +... + +1.3.10. cpm_thresh_crit_col (string) + + The column's name in the database storing the critical + threshold value for calls per minute. + + Default value is “cpm_critical”. + + Example 1.10. Set “cpm_thresh_crit_col” parameter +... +modparam("dispatcher", "cpm_thresh_crit_col", "cpm_crit_thresh") +... + +1.3.11. calldur_thresh_warn_col (string) + + The column's name in the database storing the warning threshold + value for call duration. + + Default value is “call_duration_warning”. + + Example 1.11. Set “calldur_thresh_warn_col” parameter +... +modparam("dispatcher", "calldur_thresh_warn_col", "calldur_warn_thresh") +... + +1.3.12. calldur_thresh_crit_col (string) + + The column's name in the database storing the critical + threshold value for call duration. + + Default value is “call_duration_critical”. + + Example 1.12. Set “calldur_thresh_crit_col” parameter +... +modparam("dispatcher", "calldur_thresh_crit_col", "calldur_crit_thresh") +... + +1.3.13. totalc_thresh_warn_col (string) + + The column's name in the database storing the warning threshold + value for the number of total calls. + + Default value is “total_calls_warning”. + + Example 1.13. Set “totalc_thresh_warn_col” parameter +... +modparam("dispatcher", "totalc_thresh_warn_col", "totalc_warn_thresh") +... + +1.3.14. totalc_thresh_crit_col (string) + + The column's name in the database storing the critical + threshold value for the number of total calls. + + Default value is “total_calls_critical”. + + Example 1.14. Set “totalc_thresh_crit_col” parameter +... +modparam("dispatcher", "totalc_thresh_crit_col", "totalc_crit_thresh") +... + +1.3.15. concalls_thresh_warn_col (string) + + The column's name in the database storing the warning threshold + value for the number of concurrent calls. + + Default value is “concurrent_calls_warning”. + + Example 1.15. Set “concalls_thresh_warn_col” parameter +... +modparam("dispatcher", "concalls_thresh_warn_col", "concalls_warn_thresh +") +... + +1.3.16. concalls_thresh_crit_col (string) + + The column's name in the database storing the critical + threshold value for the number of concurrent calls. + + Default value is “concurrent_calls_critical”. + + Example 1.16. Set “concalls_thresh_crit_col” parameter +... +modparam("dispatcher", "concalls_thresh_crit_col", "concalls_crit_thresh +") +... + +1.3.17. seqcalls_thresh_warn_col (string) + + The column's name in the database storing the warning threshold + value for the number of sequential calls. + + Default value is “sequential_calls_warning”. + + Example 1.17. Set “seqcalls_thresh_warn_col” parameter +... +modparam("dispatcher", "seqcalls_thresh_warn_col", "seqcalls_warn_thresh +") +... + +1.3.18. seqcalls_thresh_crit_col (string) + + The column's name in the database storing the critical + threshold value for the number of sequential calls. + + Default value is “sequential_calls_critical”. + + Example 1.18. Set “seqcalls_thresh_crit_col” parameter +... +modparam("dispatcher", "seqcalls_thresh_crit_col", "seqcalls_crit_thresh +") +... + +1.4. Exported Functions + +1.4.1. check_fraud(user, number, profile_id) + + This method should be called each time a given user calls a + given number. It will try to match a fraud rule within de given + fraud profile and update the stats (see above). Furthermore, + the stats will be checked against the rule's thresholds. If any + of the stats is above it's threhsold value the appropriate + event will also be raised (see further details below). + + Meaning of the parameters is as follows: + * user - the user who is making the call. Please keep in mind + that the user doesn't have to be registered. This string is + only used do keep different stats for different registered + users. + * number - the number the user is calling to. + * profile_id - the fraud profile id (i.e. the subset of fraud + rules) in which to try and find a matching fraud rule. + + The meaning of the return code is as follows: + * 2 - no matching fraud rule was found + * 1 - a matching rule was found, but there is no parameter + above the rule's threshlod, i.e - everything is ok + * -1 - there is a parameter above the warning threhsold + value. Check the raised event for more info + * -2 - there is a parameter above the critical threhsold + value. Check the raised event for more info + * -3 - something went wrong (internal mechanism failed) + + This function can be used from REQUEST_ROUTE and ONREPLY_ROUTE. + +1.5. Exported MI Functions + +1.5.1. show_fraud_stats + + Show the current stats of a given user for a given prefix. + + Name: show_fraud_stats + + Parameters: + * user + * number + * prefix + +1.5.2. fraud_reload + + Reload the all the fraud rules. + + Name: fraud_reload + + Parameters: none + +1.6. Exported Events + +1.6.1. E_FRD_WARNING + + This event is raised whenever one of the 5 monitored parameters + is above the warning threhsold value + + Parameters: + * param - the name of the parameter. + * value - the current value of the parameter. + * threshold - the warning threshold value. + * user - the user who initiated the call. + * called_number - the number that was called. + * rule_id - the id of the fraud rule that matched when the + call was initiated + +1.6.2. E_FRD_CRITICAL + + This event is raised whenever one of the 5 monitored parameters + is above the warning threhsold value + + Parameters: + * param - the name of the parameter. + * value - the current value of the parameter. + * threshold - the warning threshold value. + * user - the user who initiated the call. + * called_number - the number that was called. + * rule_id - the id of the fraud rule that matched when the + call was initiated diff --git a/modules/fraud_detection/doc/fraud_detection.xml b/modules/fraud_detection/doc/fraud_detection.xml new file mode 100644 index 0000000000..16d6ecb03f --- /dev/null +++ b/modules/fraud_detection/doc/fraud_detection.xml @@ -0,0 +1,38 @@ + + + + + +%docentities; + +]> + + + + Fraud Detection Module + &osipsname; + + + Andrei Daniel + Datcu + datcuandrei@gmail.com + + + + 2014 + OpenSIPs Foundation + + + + $Revision: 1 $ + $Date$ + + + + + + &admin; + diff --git a/modules/fraud_detection/doc/fraud_detection_admin.xml b/modules/fraud_detection/doc/fraud_detection_admin.xml new file mode 100644 index 0000000000..f1167e629b --- /dev/null +++ b/modules/fraud_detection/doc/fraud_detection_admin.xml @@ -0,0 +1,688 @@ + + + + + &adminguide; + +
+ Overview + + This module provides a way to prevent some basic fraud attacks. + Alerts are provided through return codes and events. + +
+ Monitorized Stats + + Basically, this module watches the following parameters: + + + + Total calls + + + + + Calls per minute + + + + + Concurrent calls + + + + + Number of sequential calls + + + + + Call duration + + + + + + Each of the above parameters is monitored for every user and + every called prefix separately. The stats are altered whenever + the check_fraud function is called. The + function assumes a new call is made, and checks the called + number against all the rules from the supplied profile. The + rule's prefix is considered to be the called prefix which along with + the provided user will be used to monitor values for the 5 + parameters. + +
+ +
+ Fraud rules + + A rule is a set of two thresholds (warning and critical thresholds) for each of the + five parameters (as described above) and is only available for a specified prefix. + Further more, a rule will only match between the indicated hours in the indicated days + of the week (similarly to a dr rule). A fraud profile is simply a group of fraud rules + and is used to only to limit the list of rules to match when calling the check_fraud + function. + +
+
+
+ Dependencies +
+ &osips; modules + + The following modules must be loaded before this module: + + + + drouting + + + + + dialog + + + + +
+
+ External libraries or applications + + The following libraries or applications must be installed before + running &osips; with this module: + + + + none. + + + + +
+
+ +
+ Exported Parameters +
+ <varname>db_url</varname> (string) + + Database where to load the rules from. + + + + Default value is NULL. At least one db_url should + be defined for the fraud_detection module to work. + + + + Set the <quote>db_url</quote> parameter + +... +modparam("fraud_detection", "db_url", "mysql://user:passwb@localhost/database") +... + + +
+ +
+ <varname>table_name</varname> (string) + + If you want to load the rules from the database you must set + this parameter as the database name. + + + + The default value is fraud_detection. + + + + Set the <quote>table_name</quote> parameter + +... +modparam("fraud_detection", "table_name", "my_fraud") +... + + +
+ +
+ <varname>rid_col</varname> (string) + + The column's name in the database storing the + fraud rule's id. + + + + Default value is ruleid. + + + + Set <quote>rid_col</quote> parameter + +... +modparam("dispatcher", "rid_col", "theruleid"") +... + + +
+ +
+ <varname>pid_col</varname> (string) + + The column's name in the database storing the + fraud profile's id. + + + Please keep in mind that a profile is merely + a set of rules. + + + + Default value is profileid. + + + + Set <quote>pid_col</quote> parameter + +... +modparam("dispatcher", "pid_col", "profile"") +... + + +
+ +
+ <varname>prefix_col</varname> (string) + + The column's name in the database storing the + prefix for which the fraud rule will match. + + + + Default value is prefix. + + + + Set <quote>prefix_col</quote> parameter + +... +modparam("dispatcher", "prefix_col", "myprefix") +... + + +
+ +
+ <varname>start_h</varname> (string) + + The column's name in the database storing the + the start time of the interval in which the + rule will match. + + + The time needs to be specified as string using + the format: HH:MM + + + + Default value is start_hour. + + + + Set <quote>start_h</quote> parameter + +... +modparam("dispatcher", "start_h", "the_start_time") +... + + +
+ +
+ <varname>end_h</varname> (string) + + The column's name in the database storing the + the end time of the interval in which the + rule will match. + + + The time needs to be specified as string using + the format: HH:MM + + + + Default value is end_hour. + + + + Set <quote>end_h</quote> parameter + +... +modparam("dispatcher", "end_h", "the_end_time") +... + + +
+ +
+ <varname>days_col</varname> (string) + + The column's name in the database storing the + week days in which the fraud rule's interval + is available. + + + The daysoftheweek needs to be specified as a + string containing a list of days or intervals. + Each day must be specified using the first + three letters of its name. A valid string + would be: "Fri-Mon, Wed, Thu" + + + + Default value is daysoftheweek. + + + + Set <quote>days_col</quote> parameter + +... +modparam("dispatcher", "days_col", "days") +... + + +
+ +
+ <varname>cpm_thresh_warn_col</varname> (string) + + The column's name in the database storing the + warning threshold value for calls per minute. + + + + Default value is cpm_warning. + + + + Set <quote>cpm_thresh_warn_col</quote> parameter + +... +modparam("dispatcher", "cpm_thresh_warn_col", "cpm_warn_thresh") +... + + +
+ +
+ <varname>cpm_thresh_crit_col</varname> (string) + + The column's name in the database storing the + critical threshold value for calls per minute. + + + + Default value is cpm_critical. + + + + Set <quote>cpm_thresh_crit_col</quote> parameter + +... +modparam("dispatcher", "cpm_thresh_crit_col", "cpm_crit_thresh") +... + + +
+ +
+ <varname>calldur_thresh_warn_col</varname> (string) + + The column's name in the database storing the + warning threshold value for call duration. + + + + Default value is call_duration_warning. + + + + Set <quote>calldur_thresh_warn_col</quote> parameter + +... +modparam("dispatcher", "calldur_thresh_warn_col", "calldur_warn_thresh") +... + + +
+ +
+ <varname>calldur_thresh_crit_col</varname> (string) + + The column's name in the database storing the + critical threshold value for call duration. + + + + Default value is call_duration_critical. + + + + Set <quote>calldur_thresh_crit_col</quote> parameter + +... +modparam("dispatcher", "calldur_thresh_crit_col", "calldur_crit_thresh") +... + + +
+ +
+ <varname>totalc_thresh_warn_col</varname> (string) + + The column's name in the database storing the + warning threshold value for the number of total calls. + + + + Default value is total_calls_warning. + + + + Set <quote>totalc_thresh_warn_col</quote> parameter + +... +modparam("dispatcher", "totalc_thresh_warn_col", "totalc_warn_thresh") +... + + +
+ +
+ <varname>totalc_thresh_crit_col</varname> (string) + + The column's name in the database storing the + critical threshold value for the number of total calls. + + + + Default value is total_calls_critical. + + + + Set <quote>totalc_thresh_crit_col</quote> parameter + +... +modparam("dispatcher", "totalc_thresh_crit_col", "totalc_crit_thresh") +... + + +
+ +
+ <varname>concalls_thresh_warn_col</varname> (string) + + The column's name in the database storing the + warning threshold value for the number of + concurrent calls. + + + + Default value is concurrent_calls_warning. + + + + Set <quote>concalls_thresh_warn_col</quote> parameter + +... +modparam("dispatcher", "concalls_thresh_warn_col", "concalls_warn_thresh") +... + + +
+ +
+ <varname>concalls_thresh_crit_col</varname> (string) + + The column's name in the database storing the + critical threshold value for the number of + concurrent calls. + + + + Default value is concurrent_calls_critical. + + + + Set <quote>concalls_thresh_crit_col</quote> parameter + +... +modparam("dispatcher", "concalls_thresh_crit_col", "concalls_crit_thresh") +... + + +
+ +
+ <varname>seqcalls_thresh_warn_col</varname> (string) + + The column's name in the database storing the + warning threshold value for the number of + sequential calls. + + + + Default value is sequential_calls_warning. + + + + Set <quote>seqcalls_thresh_warn_col</quote> parameter + +... +modparam("dispatcher", "seqcalls_thresh_warn_col", "seqcalls_warn_thresh") +... + + +
+ +
+ <varname>seqcalls_thresh_crit_col</varname> (string) + + The column's name in the database storing the + critical threshold value for the number of + sequential calls. + + + + Default value is sequential_calls_critical. + + + + Set <quote>seqcalls_thresh_crit_col</quote> parameter + +... +modparam("dispatcher", "seqcalls_thresh_crit_col", "seqcalls_crit_thresh") +... + + +
+ +
+ +
+ Exported Functions +
+ + <function moreinfo="none">check_fraud(user, number, profile_id)</function> + + + This method should be called each time a given user + calls a given number. It will try to match a fraud rule + within de given fraud profile and update the stats (see above). Furthermore, + the stats will be checked against the rule's thresholds. If any of the stats + is above it's threhsold value the appropriate event will also be raised + (see further details below). + + Meaning of the parameters is as follows: + + + + user - the user who is making the call. Please keep in mind that + the user doesn't have to be registered. This string is only used do keep different stats + for different registered users. + + + + + number - the number the user is calling to. + + + + + profile_id - the fraud profile id (i.e. the subset of fraud + rules) in which to try and find a matching fraud rule. + + + + + The meaning of the return code is as follows: + + + + + 2 - no matching fraud rule was found + + + + + 1 - a matching rule was found, but there is no + parameter above the rule's threshlod, i.e - everything is ok + + + + + -1 - there is a parameter above the warning threhsold value. + Check the raised event for more info + + + + + -2 - there is a parameter above the critical threhsold value. + Check the raised event for more info + + + + + -3 - something went wrong (internal mechanism failed) + + + + + This function can be used from REQUEST_ROUTE and ONREPLY_ROUTE. + +
+
+ +
+ Exported MI Functions +
+ + <function moreinfo="none">show_fraud_stats</function> + + + Show the current stats of a given user for a given prefix. + + + Name: show_fraud_stats + + Parameters: + + user + + number + + prefix + +
+
+ + <function moreinfo="none">fraud_reload</function> + + + Reload the all the fraud rules. + + + Name: fraud_reload + + Parameters: none +
+ +
+ +
+ Exported Events +
+ + <function moreinfo="none">E_FRD_WARNING</function> + + + This event is raised whenever one of the 5 monitored parameters + is above the warning threhsold value + Parameters: + + + param - the name of the parameter. + + + value - the current value of the parameter. + + + threshold - the warning threshold value. + + + user - the user who initiated the call. + + + called_number - the number that was called. + + + rule_id - the id of the fraud rule that matched + when the call was initiated + + +
+
+ + <function moreinfo="none">E_FRD_CRITICAL</function> + + + This event is raised whenever one of the 5 monitored parameters + is above the warning threhsold value + Parameters: + + + param - the name of the parameter. + + + value - the current value of the parameter. + + + threshold - the warning threshold value. + + + user - the user who initiated the call. + + + called_number - the number that was called. + + + rule_id - the id of the fraud rule that matched + when the call was initiated + + +
+
+ +
+ diff --git a/modules/fraud_detection/fraud_detection.c b/modules/fraud_detection/fraud_detection.c new file mode 100644 index 0000000000..ef986bcebe --- /dev/null +++ b/modules/fraud_detection/fraud_detection.c @@ -0,0 +1,522 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#include "../../ut.h" +#include "../../db/db.h" +#include "../../time_rec.h" +#include "../../mod_fix.h" +#include "../drouting/dr_api.h" +#include "../dialog/dlg_load.h" + +#include "frd_stats.h" +#include "frd_load.h" +#include "frd_events.h" + +extern str db_url; +extern str table_name; + +extern str rid_col; +extern str pid_col; +extern str prefix_col; +extern str start_h_col; +extern str end_h_col; +extern str days_col; +extern str cpm_thresh_warn_col; +extern str cpm_thresh_crit_col; +extern str calldur_thresh_warn_col; +extern str calldur_thresh_crit_col; +extern str totalc_thresh_warn_col; +extern str totalc_thresh_crit_col; +extern str concalls_thresh_warn_col; +extern str concalls_thresh_crit_col; +extern str seqcalls_thresh_warn_col; +extern str seqcalls_thresh_crit_col; + +#define DEF_PARAM_STR_NAME(pname, strname)\ + static str pname ## _name = str_init(strname) + +DEF_PARAM_STR_NAME(cpm, "calls per minute"); +DEF_PARAM_STR_NAME(total_calls, "total calls"); +DEF_PARAM_STR_NAME(concurrent_calls, "concurrent calls"); +DEF_PARAM_STR_NAME(seq_calls, "sequential calls"); +#undef DEF_PARAM_STR_NAME + + +dr_head_p *dr_head; +struct dr_binds drb; +rw_lock_t *frd_data_lock; +gen_lock_t *frd_seq_calls_lock; + +struct dlg_binds dlgb; + +static int mod_init(void); +static int child_init(int); +static void destroy(void); + +static int check_fraud(struct sip_msg *msg, char *user, char *number, char *pid); +static int fixup_check_fraud(void **param, int param_no); +static struct mi_root* mi_show_stats(struct mi_root *cmd_tree, void *param); +static struct mi_root* mi_reload(struct mi_root *cmd_tree, void *param); + +static cmd_export_t cmds[]={ + {"check_fraud", (cmd_function)check_fraud, 3, fixup_check_fraud, 0, + REQUEST_ROUTE | ONREPLY_ROUTE}, + {0,0,0,0,0,0} +}; + +static param_export_t params[]={ + {"db_url", STR_PARAM, &db_url.s}, + {"table_name", STR_PARAM, &table_name.s}, + {"rid_col", STR_PARAM, &rid_col.s}, + {"pid_col", STR_PARAM, &pid_col.s}, + {"prefix_col", STR_PARAM, &prefix_col.s}, + {"start_h_col", STR_PARAM, &start_h_col.s}, + {"end_h_col", STR_PARAM, &end_h_col.s}, + {"days_col", STR_PARAM, &days_col.s}, + {"cpm_thresh_warn_col", STR_PARAM, &cpm_thresh_warn_col.s}, + {"cpm_thresh_crit_col", STR_PARAM, &cpm_thresh_crit_col.s}, + {"calldur_thresh_warn_col", STR_PARAM, &calldur_thresh_warn_col.s}, + {"calldur_thresh_crit_col", STR_PARAM, &calldur_thresh_crit_col.s}, + {"totalc_thresh_warn_col", STR_PARAM, &totalc_thresh_warn_col.s}, + {"totalc_thresh_crit_col", STR_PARAM, &totalc_thresh_crit_col.s}, + {"concalls_thresh_warn_col", STR_PARAM, &concalls_thresh_warn_col.s}, + {"concalls_thresh_crit_col", STR_PARAM, &concalls_thresh_crit_col.s}, + {"seqcalls_thresh_warn_col", STR_PARAM, &seqcalls_thresh_warn_col.s}, + {"seqcalls_thresh_crit_col", STR_PARAM, &seqcalls_thresh_crit_col.s}, + {0,0,0} +}; + +static mi_export_t mi_cmds[] = { + //{ "get_maps","return all mappings",mi_get_maps,MI_NO_INPUT_FLAG,0,0}, + {"show_fraud_stats", "print current stats for a particular user", + mi_show_stats, 0, 0, 0}, + {"fraud_reload", "reload fraud profiles from db", mi_reload, 0, 0, 0}, + {0,0,0,0,0,0} +}; + +static dep_export_t deps = { + { + {MOD_TYPE_SQLDB, NULL, DEP_ABORT}, + {MOD_TYPE_DEFAULT, "drouting", DEP_ABORT}, + {MOD_TYPE_DEFAULT, "dialog", DEP_ABORT}, + {MOD_TYPE_NULL, NULL, 0}, + }, + { + {NULL, NULL}, + }, +}; + +/** module exports */ +struct module_exports exports= { + "fraud_detection", /* module name */ + MOD_TYPE_DEFAULT, + MODULE_VERSION, + DEFAULT_DLFLAGS, /* dlopen flags */ + &deps, + cmds, /* exported functions */ + params, /* exported parameters */ + 0, /* exported statistics */ + mi_cmds, /* exported MI functions */ + 0, /* exported pseudo-variables */ + 0, /* extra processes */ + mod_init, /* module initialization function */ + (response_function) 0, /* response handling function */ + (destroy_function)destroy, /* destroy function */ + child_init /* per-child init function */ +}; + + +static void set_lengths(void) +{ + db_url.len = strlen(db_url.s); + table_name.len = strlen(table_name.s); + rid_col.len = strlen(rid_col.s); + pid_col.len = strlen(pid_col.s); + prefix_col.len = strlen(prefix_col.s); + start_h_col.len = strlen(start_h_col.s); + end_h_col.len = strlen(end_h_col.s); + days_col.len = strlen(days_col.s); + cpm_thresh_warn_col.len = strlen(cpm_thresh_warn_col.s); + cpm_thresh_crit_col.len = strlen(cpm_thresh_crit_col.s); + calldur_thresh_warn_col.len = strlen(calldur_thresh_warn_col.s); + calldur_thresh_crit_col.len = strlen(calldur_thresh_crit_col.s); + totalc_thresh_warn_col.len = strlen(totalc_thresh_warn_col.s); + totalc_thresh_crit_col.len = strlen(totalc_thresh_crit_col.s); + concalls_thresh_warn_col.len = strlen(concalls_thresh_warn_col.s); + concalls_thresh_crit_col.len = strlen(concalls_thresh_crit_col.s); + seqcalls_thresh_warn_col.len = strlen(seqcalls_thresh_warn_col.s); + seqcalls_thresh_crit_col.len = strlen(seqcalls_thresh_crit_col.s); +} + +static int mod_init(void) +{ + LM_INFO("Initializing module\n"); + + if ((frd_data_lock = lock_init_rw()) == NULL) { + LM_CRIT("failed to init reader/writer lock\n"); + return -1; + } + + if ((frd_seq_calls_lock = lock_alloc()) == NULL) { + LM_ERR("cannot alloc seq_calls lock\n"); + return -1; + } + if (lock_init(frd_seq_calls_lock) == NULL) { + LM_ERR ("cannot init seq_calls lock\n"); + return -1; + } + + if (load_dlg_api(&dlgb) != 0) { + LM_ERR("failed to load dialog binds\n"); + return -1; + } + + if (frd_event_init() != 0) { + LM_ERR("cannot register events\n"); + return -1; + } + + if (load_dr_api(&drb) != 0) { + LM_ERR("cannot load dr_api\n"); + return -1; + } + + dr_head = shm_malloc(sizeof(dr_head_p)); + if (dr_head == NULL) { + LM_ERR("no more shm memory\n"); + return -1; + } + + set_lengths(); + if (init_stats_table() != 0) + return -1; + + /* Check if table version is ok */ + frd_init_db(); + frd_disconnect_db(); + + return 0; +} + +static int child_init(int rank) +{ + if (rank == 1) { + + if (frd_connect_db() != 0 || frd_reload_data() != 0) { + LM_ERR ("cannot load data from db\n"); + return -1; + } + frd_disconnect_db(); + } + return 0; +} + +/* + * destroy function + */ +static void destroy(void) +{ + free_stats_table(); + frd_destroy_data(); +} + +static int fixup_check_fraud(void **param, int param_no) +{ + switch (param_no) { + + case 1: + case 2: + return fixup_spve(param); + + case 3: + return fixup_igp(param); + + default: + LM_CRIT ("Too many parameters for check_fraud\n"); + return -1; + } +} + +static int check_fraud(struct sip_msg *msg, char *_user, char *_number, char *_pid) +{ + + static const int rc_error = -3, rc_critical_thr = -2, rc_warning_thr = -1, + rc_ok_thr = 1, rc_no_rule = 2; + str user, number; + unsigned int pid; + + static str last_called_prefix; + extern unsigned int frd_data_rev; + + if (dr_head == NULL) { + /* No data, probably still loading */ + LM_ERR("no data\n"); + return rc_error; + } + + /* Get the actual params */ + + if (fixup_get_svalue(msg, (gparam_p) _user, &user) != 0) { + LM_ERR("Cannot get user value\n"); + return rc_error; + } + if (fixup_get_svalue(msg, (gparam_p) _number, &number) != 0) { + LM_ERR("Cannot get number value\n"); + return rc_error; + } + if (fixup_get_ivalue(msg, (gparam_p)_pid, (int*)&pid) != 0) { + LM_ERR("Cannot get the profile-id value\n"); + return rc_error; + } + + /* Find a rule */ + + unsigned int matched_len; + lock_start_read(frd_data_lock); + rt_info_t *rule = drb.match_number(*dr_head, pid, &number, &matched_len); + + if (rule == NULL) { + /* No match */ + LM_DBG("No rule matched for number=<%.*s>, pid=<%d>\n", + number.len, number.s, pid); + + lock_stop_read(frd_data_lock); + return rc_no_rule; + } + + /* We matched a rule */ + str prefix = number; + prefix.len = matched_len; + str shm_user; + frd_stats_entry_t *se = get_stats(user, prefix, &shm_user); + + /* Check if we need to reset the stats */ + + struct tm now, then; + time_t nowt = time(NULL); + + /* We lock all the stats values */ + lock_get(&se->lock); + if (gmtime_r(&se->stats.last_matched_time, &then) == NULL + || gmtime_r(&nowt, &now) == NULL) { + LM_ERR ("Cannot use gmtime function. Will exit\n"); + return rc_ok_thr; + } + + if (se->stats.last_matched_time == 0 || se->stats.last_matched_rule != rule->id + || then.tm_yday != now.tm_yday || then.tm_year != now.tm_year) { + se->stats.cpm = 0; + se->stats.total_calls = 0; + se->stats.concurrent_calls = 0; + } + + /* Update the stats */ + + lock_get(frd_seq_calls_lock); + if (last_called_prefix.len == matched_len && + memcmp(last_called_prefix.s, number.s, matched_len) == 0) { + + /* We have called the same number last time */ + ++se->stats.seq_calls; + } + else { + last_called_prefix.s = shm_realloc(last_called_prefix.s, + matched_len * sizeof(char)); + last_called_prefix.len = matched_len; + se->stats.seq_calls = 1; + } + lock_release(frd_seq_calls_lock); + + se->stats.last_matched_rule = rule->id; + ++se->stats.total_calls; + + /* Calls per FRD_SECS_PER_WINDOW */ + if (nowt - se->stats.last_matched_time >= FRD_SECS_PER_WINDOW) { + se->stats.cpm = 0; + memset(se->stats.calls_window, 0, + sizeof(unsigned short) * FRD_SECS_PER_WINDOW); + se->stats.calls_window[nowt % FRD_SECS_PER_WINDOW] = 1; + } + else { + unsigned int i = nowt % FRD_SECS_PER_WINDOW; + unsigned int j = (se->stats.last_matched_time + 1) % FRD_SECS_PER_WINDOW; + for (;i != j; i = (i - 1 + FRD_SECS_PER_WINDOW) % FRD_SECS_PER_WINDOW) { + se->stats.cpm -= se->stats.calls_window[i]; + se->stats.calls_window[i] = 0; + } + } + + ++se->stats.cpm; + ++se->stats.concurrent_calls; + se->stats.last_matched_time = nowt; + + /* Check the thresholds */ + + int rc = rc_no_rule; + + frd_thresholds_t *thr = (frd_thresholds_t*)rule->attrs.s; + +#define CHECK_AND_RAISE(pname, type) \ + (se->stats.pname >= thr->pname ## _thr.type) { \ + raise_ ## type ## _event(&pname ## _name, &se->stats.pname,\ + &thr->pname ## _thr.type, &user, &number, &rule->id);\ + rc = rc_ ## type ## _thr;\ + } + + if CHECK_AND_RAISE(cpm, critical) + else if CHECK_AND_RAISE(total_calls, critical) + else if CHECK_AND_RAISE(concurrent_calls, critical) + else if CHECK_AND_RAISE(seq_calls, critical) + else if CHECK_AND_RAISE(cpm, warning) + else if CHECK_AND_RAISE(total_calls, warning) + else if CHECK_AND_RAISE(concurrent_calls, warning) + else if CHECK_AND_RAISE(seq_calls, warning); + +#undef CHECK_AND_RAISE + + lock_release(&se->lock); + + /* Set dialog callback to check call duration */ + struct dlg_cell *dlgc = dlgb.get_dlg(); + if (dlgc == NULL) { + if (dlgb.create_dlg(msg, 0) < 0) + LM_ERR ("cannot create new_dlg\n"); + else if ( (dlgc = dlgb.get_dlg()) == NULL) + LM_ERR("cannot get the new dlg\n"); + } + + if (dlgc) { + frd_dlg_param *param = shm_malloc(sizeof(frd_dlg_param)); + if (param == NULL) + LM_ERR("no more shm memory"); + else if (shm_str_dup(¶m->number, &number) == 0){ + param->stats = se; + param->thr = thr; + param->user = shm_user; + param->ruleid = rule->id; + param->data_rev = frd_data_rev; + + /* Register the dlg_terminate cb */ + if (shm_str_dup(¶m->number, &number) != 0) + shm_free(param); + else if (dlgb.register_dlgcb(dlgc, DLGCB_TERMINATED, + dialog_terminate_CB, param, NULL) != 0) { + LM_ERR("cannot register dialog callback\n"); + shm_free(param->number.s); + shm_free(param); + } + } + else { + shm_free(param); + } + } + + lock_stop_read(frd_data_lock); + + return rc; +} + +static struct mi_root* mi_show_stats(struct mi_root *cmd_tree, void *param) +{ + /* User, number, pid */ + + struct mi_node *node = cmd_tree->node.kids; + str user, prefix; + unsigned int pid; + + if (node == NULL) + return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN); + + user = node->value; + node = node->next; + + if (node == NULL) + return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN); + + prefix = node->value; + node = node->next; + + if (node == NULL) + return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN); + + if (str2int(&node->value, &pid) != 0) { + LM_WARN("Wrong value for profile id. Token <%.*s>\n", node->value.len, + node->value.s); + return init_mi_tree(400, MI_BAD_PARM_S, MI_BAD_PARM_LEN); + } + + if (!stats_exist(user, prefix)) { + LM_WARN("There is no data for user<%.*s> and prefix=<%.*s>\n", + user.len, user.s, prefix.len, prefix.s); + return init_mi_tree(400, MI_BAD_PARM_S, MI_BAD_PARM_LEN); + } + + + struct mi_root* rpl_tree = init_mi_tree(200, MI_OK_S, MI_OK_LEN); + if (rpl_tree == NULL) + return 0; + rpl_tree->node.flags |= MI_IS_ARRAY; + + frd_stats_entry_t *se = get_stats(user, prefix, NULL); + lock_get(&se->lock); + +#define ADD_STAT_CHILD(pname, pval) do {\ + int val_len;\ + char *cval = int2str(pval, &val_len);\ + if (add_mi_node_child(&rpl_tree->node, MI_DUP_VALUE,\ + pname ## _name.s, pname ## _name.len, cval, val_len) == 0)\ + goto add_error;\ +} while (0) + + ADD_STAT_CHILD(cpm, se->stats.cpm); + ADD_STAT_CHILD(total_calls, se->stats.total_calls); + ADD_STAT_CHILD(concurrent_calls, se->stats.concurrent_calls); + ADD_STAT_CHILD(seq_calls, se->stats.seq_calls); + +#undef ADD_STAT_CHILD + + lock_release(&se->lock); + return rpl_tree; + +add_error: + lock_release(&se->lock); + LM_ERR("failed to add node\n"); + free_mi_tree(rpl_tree); + return 0; +} + +static struct mi_root* mi_reload(struct mi_root *cmd_tree, void *param) +{ + if (frd_connect_db() != 0 || frd_reload_data() != 0) { + LM_ERR ("cannot load data from db\n"); + return init_mi_tree(500, MI_INTERNAL_ERR_S, MI_INTERNAL_ERR_LEN); + } + else { + frd_disconnect_db(); + return init_mi_tree(200, MI_OK_S, MI_OK_LEN); + } +} diff --git a/modules/fraud_detection/fraud_detection.h b/modules/fraud_detection/fraud_detection.h new file mode 100644 index 0000000000..f309258841 --- /dev/null +++ b/modules/fraud_detection/fraud_detection.h @@ -0,0 +1,13 @@ +#ifndef new_mod_h +#define new_mod_h + +#define MAPPING_DELIM '=' + +struct ua_mapping { + str value; + str translated; + struct ua_mapping *next; +}; + +#endif + diff --git a/modules/fraud_detection/frd_events.c b/modules/fraud_detection/frd_events.c new file mode 100644 index 0000000000..51ac81c702 --- /dev/null +++ b/modules/fraud_detection/frd_events.c @@ -0,0 +1,175 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#include "../../evi/evi_params.h" +#include "../../evi/evi_modules.h" + +#include "../dialog/dlg_load.h" +#include "frd_events.h" +#include "../../mem/shm_mem.h" + + +/* Events name and ids */ + +static event_id_t ei_warn_id = EVI_ERROR; +static event_id_t ei_crit_id = EVI_ERROR; + +static str ei_warn_name = str_init("E_FRD_WARNING"); +static str ei_crit_name = str_init("E_FRD_CRITICAL"); +static evi_params_p event_params; + +/* Events' parameters name and pointers*/ + +static str ei_param_name = str_init("param"); +static str ei_val_name = str_init("value"); +static str ei_thr_name = str_init("threshold"); +static str ei_user_name = str_init("user"); +static str ei_number_name = str_init("called_number"); +static str ei_ruleid_name = str_init("rule_id"); + +static evi_param_p param_p, val_p, thr_p, user_p, number_p, ruleid_p; + + +/* + * Function to init the warning and critical events +*/ + +int frd_event_init(void) +{ + /* First publish the events */ + ei_warn_id = evi_publish_event(ei_warn_name); + if (ei_warn_id == EVI_ERROR) { + LM_ERR("cannot register warning event\n"); + return -1; + } + ei_crit_id = evi_publish_event(ei_crit_name); + if (ei_crit_id == EVI_ERROR) { + LM_ERR("cannot register critical event\n"); + return -1; + } + + event_params = pkg_malloc(sizeof(evi_params_t)); + if (event_params == NULL) + return -1; + memset(event_params, 0, sizeof(evi_params_t)); + +#define CREATE_PARAM(pname) \ + pname ## _p = evi_param_create(event_params, &ei_ ## pname ## _name);\ + if (! pname ## _p) \ + goto create_param_err + + CREATE_PARAM(param); + CREATE_PARAM(val); + CREATE_PARAM(thr); + CREATE_PARAM(user); + CREATE_PARAM(number); + CREATE_PARAM(ruleid); +#undef CREATE_PARAM + + return 0; + +create_param_err: + LM_ERR("cannot create event parameter"); + return -1; +} + +void frd_event_destroy(void) +{ + evi_free_params(event_params); +} + +/* + * Function to be called internally for raising an event +*/ +static void raise_event(event_id_t e, + str *param, unsigned int *val, unsigned int *thr, str *user, + str *number, unsigned int *ruleid) +{ +#define SET_PARAM(pname, ptype) \ + if (evi_param_set_ ##ptype (pname ## _p, pname) < 0) { \ + LM_ERR("cannot set " # pname "parameter\n"); \ + return; \ + } + + SET_PARAM(param, str); + SET_PARAM(val, int); + SET_PARAM(thr, int); + SET_PARAM(user, str); + SET_PARAM(number, str); + SET_PARAM(ruleid, int); +#undef SET_PARAM + + if (evi_raise_event(e, event_params) < 0) + LM_ERR("cannot raise event\n"); +} + +void raise_warning_event(str *param, unsigned int *val, unsigned int *thr, + str *user, str *number, unsigned int *ruleid) +{ + raise_event(ei_warn_id, param, val, thr, user, number, ruleid); +} + +void raise_critical_event(str *param, unsigned int *val, unsigned int *thr, + str *user, str *number, unsigned int *ruleid) +{ + raise_event(ei_crit_id, param, val, thr, user, number, ruleid); +} + + +/* + * Callback called whenever a dialog is ended. + * Check the duration against the thresholds (sent through the params) + * and raise appropriate event +*/ + + void dialog_terminate_CB(struct dlg_cell *dlgc, int type, + struct dlg_cb_params *params) +{ + static str call_dur_name = str_init ("call_duration"); + frd_dlg_param *frdparam = (frd_dlg_param*) *(params->param); + extern unsigned int frd_data_rev; + + if (type == DLGCB_TERMINATED && frd_data_rev == frdparam->data_rev) { + unsigned int duration = time(NULL) - dlgc->start_ts; + if ( duration >= frdparam->thr->call_duration_thr.critical) + raise_critical_event(&call_dur_name, &duration, + &frdparam->thr->call_duration_thr.critical, + &frdparam->user, &frdparam->number, &frdparam->ruleid); + + else if ( duration >= frdparam->thr->call_duration_thr.warning) + raise_warning_event(&call_dur_name, &duration, + &frdparam->thr->call_duration_thr.warning, + &frdparam->user, &frdparam->number, &frdparam->ruleid); + } + + lock_get(&frdparam->stats->lock); + --frdparam->stats->stats.concurrent_calls; + lock_release(&frdparam->stats->lock); + + shm_free(frdparam->number.s); + shm_free(frdparam); +} diff --git a/modules/fraud_detection/frd_events.h b/modules/fraud_detection/frd_events.h new file mode 100644 index 0000000000..64abe9f40f --- /dev/null +++ b/modules/fraud_detection/frd_events.h @@ -0,0 +1,56 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#ifndef __FRD_EVENTS_H__ +#define __FRD_EVENTS_H__ + +#include "frd_stats.h" + +int frd_event_init(void); +void frd_event_destroy(void); +void raise_warning_event(str *param, unsigned int *val, unsigned int *thr, + str *user, str *number, unsigned int *ruleid); +void raise_critical_event(str *param, unsigned int *val, unsigned int *thr, + str *user, str *number, unsigned int *ruleid); + + +/* Dialog callback */ + +typedef struct { + frd_stats_entry_t *stats; + frd_thresholds_t *thr; + str user; + str number; + unsigned int ruleid; + unsigned int data_rev; +} frd_dlg_param; + + void dialog_terminate_CB(struct dlg_cell *dlgc, int type, + struct dlg_cb_params *params); + + +#endif diff --git a/modules/fraud_detection/frd_hashmap.c b/modules/fraud_detection/frd_hashmap.c new file mode 100644 index 0000000000..3c4e8ac2f4 --- /dev/null +++ b/modules/fraud_detection/frd_hashmap.c @@ -0,0 +1,87 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#include "frd_hashmap.h" + +#include "../../hash_func.h" +#include "../../str.h" +#include "../../locking.h" + +int init_hash_map(hash_map_t *hm) +{ + hm->buckets = shm_malloc(hm->size * sizeof(hash_bucket_t)); + if (hm->buckets == NULL) { + LM_ERR("No more shm memory\n"); + return -1; + } + + unsigned int i; + + for (i = 0; i < hm->size; ++i) { + hm->buckets[i].items = map_create(AVLMAP_SHARED); + hm->buckets[i].lock = lock_init_rw(); + if (hm->buckets[i].lock == NULL) { + LM_ERR("cannot init lock\n"); + shm_free(hm->buckets); + return -1; + } + } + + return 0; +} + +void** get_item (hash_map_t *hm, str key) +{ + unsigned int hash = core_hash(&key, NULL, hm->size); + + lock_start_read(hm->buckets[hash].lock); + void **find_res = map_find(hm->buckets[hash].items, key); + lock_stop_read(hm->buckets[hash].lock); + if (find_res) { + return find_res; + } + else { + lock_start_write(hm->buckets[hash].lock); + find_res = map_get(hm->buckets[hash].items, key); + lock_stop_write(hm->buckets[hash].lock); + return find_res; + } +} + +void free_hash_map(hash_map_t* hm, void (*value_destroy_func)(void *)) +{ + unsigned int i; + for (i = 0; i < hm->size; ++i) { + lock_start_write(hm->buckets[i].lock); + map_destroy(hm->buckets[i].items, value_destroy_func); + lock_stop_write(hm->buckets[i].lock); + lock_destroy(hm->buckets[i].lock); + } + shm_free(hm->buckets); +} + + diff --git a/modules/fraud_detection/frd_hashmap.h b/modules/fraud_detection/frd_hashmap.h new file mode 100644 index 0000000000..7f6cdda839 --- /dev/null +++ b/modules/fraud_detection/frd_hashmap.h @@ -0,0 +1,50 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#ifndef __FRD_HASHMAP_H__ +#define __FRD_HASHMAP_H__ + +#include "../../map.h" +#include "../../rw_locking.h" + +typedef struct { + map_t items; + rw_lock_t *lock; +} hash_bucket_t; + +typedef struct { + hash_bucket_t *buckets; + size_t size; +} hash_map_t; + + + +int init_hash_map(hash_map_t* hm); +void** get_item (hash_map_t *hm, str key); +void free_hash_map(hash_map_t* hm, void (*value_destroy_func)(void *)); + +#endif diff --git a/modules/fraud_detection/frd_load.c b/modules/fraud_detection/frd_load.c new file mode 100644 index 0000000000..15baef2ac4 --- /dev/null +++ b/modules/fraud_detection/frd_load.c @@ -0,0 +1,531 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#include "../../ut.h" +#include "../../db/db.h" +#include "../drouting/dr_api.h" +#include "../../time_rec.h" + +#include "frd_stats.h" + +#define FRD_TABLE_VERSION 1 +#define FRD_TIME_SEP ':' + +#define FRD_RID_COL "ruleid" +#define FRD_PID_COL "profileid" +#define FRD_PREFIX_COL "prefix" +#define FRD_START_H_COL "start_hour" +#define FRD_END_H_COL "end_hour" +#define FRD_DAYS_COL "daysoftheweek" +#define FRD_CPM_THRESH_WARN_COL "cpm_warning" +#define FRD_CPM_THRESH_CRIT_COL "cpm_critical" +#define FRD_CALLDUR_THRESH_WARN_COL "call_duration_warning" +#define FRD_CALLDUR_THRESH_CRIT_COL "call_duration_critical" +#define FRD_TOTALC_THRESH_WARN_COL "total_calls_warning" +#define FRD_TOTALC_THRESH_CRIT_COL "total_calls_critical" +#define FRD_CONCALLS_THRESH_WARN_COL "concurrent_calls_warning" +#define FRD_CONCALLS_THRESH_CRIT_COL "concurrent_calls_critical" +#define FRD_SEQCALLS_THRESH_WARN_COL "sequential_calls_warning" +#define FRD_SEQCALLS_THRESH_CRIT_COL "sequential_calls_critical" + + +str db_url; +str table_name = str_init("fraud_detection"); + +str rid_col = str_init(FRD_RID_COL); +str pid_col = str_init(FRD_PID_COL); +str prefix_col = str_init(FRD_PREFIX_COL); +str start_h_col = str_init(FRD_START_H_COL); +str end_h_col = str_init(FRD_END_H_COL); +str days_col = str_init(FRD_DAYS_COL); +str cpm_thresh_warn_col = str_init(FRD_CPM_THRESH_WARN_COL); +str cpm_thresh_crit_col = str_init(FRD_CPM_THRESH_CRIT_COL); +str calldur_thresh_warn_col = str_init(FRD_CALLDUR_THRESH_WARN_COL); +str calldur_thresh_crit_col = str_init(FRD_CALLDUR_THRESH_CRIT_COL); +str totalc_thresh_warn_col = str_init(FRD_TOTALC_THRESH_WARN_COL); +str totalc_thresh_crit_col = str_init(FRD_TOTALC_THRESH_CRIT_COL); +str concalls_thresh_warn_col = str_init(FRD_CONCALLS_THRESH_WARN_COL); +str concalls_thresh_crit_col = str_init(FRD_CONCALLS_THRESH_CRIT_COL); +str seqcalls_thresh_warn_col = str_init(FRD_SEQCALLS_THRESH_WARN_COL); +str seqcalls_thresh_crit_col = str_init(FRD_SEQCALLS_THRESH_CRIT_COL); + + +unsigned int frd_data_rev; + +static db_con_t *db_handle; +static db_func_t dbf; + +extern dr_head_p *dr_head; +extern struct dr_binds drb; +extern rw_lock_t *frd_data_lock; + +/* List of data kept in dr's attr and freed here - pkg */ + +typedef struct _free_list_t{ + tmrec_p trec; + frd_thresholds_t *thr; + unsigned int n; + struct _free_list_t *next; +} free_list_t; + +static free_list_t *free_list; + + +/* + * Function that parse time string like %H:%M to a tm struct + * tm struct must be allocated and initialized +*/ + +static int strtime(const str *time, int *ihrs, int *imin) +{ + char *colon = q_memchr(time->s, FRD_TIME_SEP, time->len); + if (colon == NULL) + goto parse_error; + + str hrs = {time->s, colon - time->s}; + str min = {colon + 1, time->len - hrs.len - 1}; + if (hrs.len == 0 || min.len == 0) + goto parse_error; + + unsigned int uhrs, umin; + if (str2int(&hrs, &uhrs) || str2int(&min, &umin)) + goto parse_error; + + if (uhrs > 23 || umin >= 60) + goto parse_error; + + *imin = umin; + *ihrs = uhrs; + + return 0; +parse_error: + LM_ERR("cannot parse time-value <%.*s>", time->len, time->s); + return -1; +} + +static int strcmp_case_insensitive(char *s1, char *s2, int len) +{ + int i; + for (i = 0; i < len; ++i) + if (tolower(s1[i]) != tolower(s2[i])) + return -1; + + return 0; +} + +static int parse_week_days(const str *week_days, unsigned short *day_set) +{ + static const str str_days[] = { + str_init("sun"), str_init("mon"), str_init("tue"), str_init("wed"), + str_init("thu"), str_init("fri"), str_init("sat") + }; + + static const char interval_delim = '-'; + static const char list_delim = ','; + + if (week_days->len == 0) + return 0; + + char *p = week_days->s, *np, *dash; + int rem_len = week_days->len, token_len, i, j, n = 0; + str t1, t2; + + do { + np = q_memchr(p, list_delim, rem_len); + token_len = np ? np - p : rem_len; + rem_len -= token_len + 1; + + if (token_len < 3) + goto parse_error; + + /* Now we see if it is an interval */ + dash = q_memchr(p, interval_delim, token_len); + + if (dash){ + /* It is an interval */ + t1.s = p; + t1.len = dash - p; + trim_spaces_lr(t1); + + t2.s = dash + 1; + t2.len = token_len - t1.len - 1; + trim_spaces_lr(t2); + + if (t1.len != 3 || t2.len != 3) + goto parse_error; + + for (i = 0; i < 7; ++i) + if (strcmp_case_insensitive(str_days[i].s, t1.s, 3) == 0) + break; + if (i == 7) + goto parse_error; + + for (j = 0; j < 7; ++j) + if (strcmp_case_insensitive(str_days[j].s, t2.s, 3) == 0) + break; + if (j == 7) + goto parse_error; + + /* We increase the size of the days set */ + n += (j - i + 7) % 7 + 1; + + for (; i <= j; i = (i + 1) % 7) + *day_set |= 1 << i; + } + else { + /* Just one value */ + t1.s = p; + t1.len = token_len; + trim_spaces_lr(t1); + + if (t1.len != 3) + goto parse_error; + + for (i = 0; i < 7; ++i) + if (strcmp_case_insensitive(str_days[i].s, t1.s, 3) == 0) + break; + if (i == 7) + goto parse_error; + + *day_set |= 1 << i; + ++n; + } + + p = np + 1; + } while (rem_len > 0); + + return n; + +parse_error: + LM_ERR("Cannot parse week day list <%.*s>", week_days->len, week_days->s); + return -1; +} + +static int create_time_rec(const str *time_start, const str *time_end, + const str *week_days, tmrec_p trec) +{ + int end_h, end_m; + + memset(trec, 0, sizeof(tmrec_t)); + + if (strtime(time_start, &trec->ts.tm_hour, &trec->ts.tm_min) != 0 + || strtime(time_end, &end_h, &end_m) != 0) + return -1; + + trec->duration = (end_h * 3600 + end_m * 60) - + (trec->ts.tm_hour * 3600 + trec->ts.tm_min * 60); + trec->ts.tm_isdst = -1 /*daylight*/; + trec->dtstart = trec->duration; + trec->freq = FREQ_DAILY; + + unsigned short day_set = 0; + int n = parse_week_days(week_days, &day_set); + + if (n == -1) + return -1; + + if (n) { + //TODO - byday custom init - no req needed + trec->byday = tr_byxxx_new(SHM_ALLOC); + if (trec->byday == NULL) + return -1; + + if (tr_byxxx_init(trec->byday, n) < 0) { + tr_byxxx_free(trec->byday); + return -1; + } + + short i, j = 0; + + for (i = 0; i < 7; ++i) + if (day_set & 1 << i) + trec->byday->xxx[j++] = i; + } + + return 0; +} + +static int frd_load_data(dr_head_p drp, free_list_t **fl) +{ + static const size_t col_count = 16; + db_res_t *res = NULL; + unsigned int no_rows = 0, row_count, i; + db_row_t *rows; + db_val_t *values; + + db_key_t query_cols[] = { + &rid_col, &pid_col, &prefix_col, &start_h_col, &end_h_col, &days_col, + &cpm_thresh_warn_col, &cpm_thresh_crit_col, &calldur_thresh_warn_col, + &calldur_thresh_crit_col, &totalc_thresh_warn_col, &totalc_thresh_crit_col, + &concalls_thresh_warn_col, &concalls_thresh_crit_col, &seqcalls_thresh_warn_col, + &seqcalls_thresh_crit_col + }; + + if (db_handle == NULL) { + LM_ERR("Invalid db handler\n"); + return -1; + } + + if (dbf.use_table(db_handle, &table_name) != 0) { + LM_ERR("Cannot use table\n"); + return -1; + } + + if (DB_CAPABILITY(dbf, DB_CAP_FETCH)) { + if (dbf.query(db_handle, 0, 0, 0, query_cols, 0, col_count, 0, 0) != 0) { + LM_ERR("Error while querying db\n"); + goto error; + } + /* estimate rows */ + no_rows = estimate_available_rows(4 + 64 + 5 + 5 + 64 + 5 * 2 * 4, col_count); + + if (no_rows == 0) + no_rows = 10; + + if (dbf.fetch_result(db_handle, &res, no_rows) != 0) { + LM_ERR("Error while fetching rows\n"); + goto error; + } + } else { + /* No fetching capability */ + if (dbf.query(db_handle, 0, 0, 0, query_cols, 0, col_count, 0, &res) != 0) { + LM_ERR("Error while querying db\n"); + goto error; + } + } + + /* Process the actual data */ + + unsigned int rid, pid, j; + str prefix, start_time, end_time, days; + free_list_t *fl_it = NULL; + *fl = NULL; + + do { + row_count = RES_ROW_N(res); + rows = RES_ROWS(res); + fl_it = pkg_malloc(sizeof(free_list_t)); + if (fl_it == NULL) { + LM_ERR ("no more pkg memory"); + dbf.free_result(db_handle, res); + return -1; + } + fl_it ->next = *fl; + *fl = fl_it; + fl_it->trec = shm_malloc(sizeof(tmrec_t) * row_count); + if (fl_it->trec == NULL) + goto no_more_shm; + fl_it->thr = shm_malloc(sizeof(frd_thresholds_t) * row_count); + if (fl_it->thr == NULL) + goto no_more_shm; + fl_it->n = row_count; + + for (i = 0; i < row_count; ++i) { + values = ROW_VALUES(rows + i); + fl_it->trec[i].byday = NULL; + + /* rule id */ + if (VAL_NULL(values)) { + LM_ERR("rule id cannot be NULL - skipping rule\n"); + continue; + } + rid = VAL_INT(values); + + /* profile id */ + if (VAL_NULL(values + 1)) { + LM_ERR("profile id cannot be NULL - skipping rule\n"); + continue; + } + pid = VAL_INT(values + 1); + + get_str_from_dbval(prefix_col.s, values + 2, 1, 1, prefix, null_val); + get_str_from_dbval(start_h_col.s, values + 3, 1, 1, start_time, null_val); + get_str_from_dbval(end_h_col.s, values + 4, 1, 1, end_time, null_val); + get_str_from_dbval(days_col.s, values + 5, 1, 1, days, null_val); + + if (create_time_rec(&start_time, &end_time, &days, fl_it->trec + i) != 0) + goto null_val; + + /* Now load the thresholds */ + for (j = 0; j < 2 * 5; ++j) { + if (VAL_NULL(values + 6 + j)) + goto null_val; + memcpy((char*)fl_it->thr + i * sizeof(frd_thresholds_t) + + j * sizeof(unsigned int), &VAL_INT(values + 6 + j), + sizeof(unsigned int)); + } + + /* Rule OK, time to put it in DR */ + if (drb.add_rule(drp, rid, &prefix, pid, 0, fl_it->trec + i, + (void*)(&fl_it->thr[i])) != 0) { + + LM_ERR("Cannot add rule in dr <%u>. Skipping...\n", rid); + } + + null_val: + continue; + } + + if (DB_CAPABILITY(dbf, DB_CAP_FETCH)) { + /* any more rows to fetch ? */ + if(dbf.fetch_result(db_handle, &res, no_rows)<0) { + LM_ERR("error while fetching rows\n"); + goto error; + } + /* success in fetching more rows - continue the loop */ + } else + break; + + } while (RES_ROW_N(res) > 0); + + dbf.free_result(db_handle, res); + return 0; + +no_more_shm: + LM_ERR ("no more shm memory\n"); + dbf.free_result(db_handle, res); + +error: + return -1; +} + +/* This function assumes no one is using the dr_head anymore */ +static void frd_destroy_data_unsafe(dr_head_p dr_head, free_list_t *fl) +{ + if (dr_head == NULL && fl == NULL) + return; + + drb.free_head(dr_head); + free_list_t *it = fl, *aux; + int i; + + while (it) { + for (i = 0; i < it->n; ++i) + if (it->trec[i].byday) + tr_byxxx_free(it->trec[i].byday); + shm_free(it->trec); + shm_free(it->thr); + aux = it; + it = it->next; + pkg_free(aux); + } +} + +/* Function to be called in mod_destroy + * Still unsafe!!! +*/ + +void frd_destroy_data(void) +{ + frd_destroy_data_unsafe(*dr_head, free_list); +} + +int frd_reload_data(void) +{ + dr_head_p new_head, old_head; + + if ((new_head = drb.create_head()) == NULL) { + LM_ERR ("cannot create dr_head\n"); + return -1; + } + + free_list_t *new_list = NULL, *old_list; + + if (frd_load_data(new_head, &new_list) != 0) { + LM_ERR("cannot load fraud data\n"); + return -1; + } + + old_head = *dr_head; + old_list = free_list; + ++frd_data_rev; + lock_start_write(frd_data_lock); + *dr_head = new_head; + free_list = new_list; + lock_stop_write(frd_data_lock); + frd_destroy_data_unsafe(old_head, old_list); + return 0; +} + +int frd_connect_db(void) +{ + if (db_url.s == NULL || db_url.len == 0) { + LM_ERR("invalid db_url\n"); + return -1; + } + + if (db_handle != NULL) { + LM_CRIT("[BUG] connection already open\n"); + return -1; + } + + if ((db_handle = dbf.init(&db_url)) == 0) { + LM_ERR("unable to connect to the database\n"); + return -1; + } + + return 0; +} + +void frd_disconnect_db(void) +{ + if (db_handle) { + dbf.close(db_handle); + db_handle = NULL; + } +} + + +int frd_init_db(void) +{ + int table_version; + + if (table_name.s == NULL || table_name.len == 0) { + LM_ERR("invalid table name\n"); + return -1; + } + + if (db_bind_mod(&db_url, &dbf) != 0) { + LM_ERR("unable to bind to a database driver\n"); + return -1; + } + + if(frd_connect_db() != 0) + return -1; + + table_version = db_table_version(&dbf, db_handle, &table_name); + if (table_version < 0) { + LM_ERR("failed to query table version\n"); + return -1; + } else if (table_version != FRD_TABLE_VERSION) { + LM_ERR("invalid table version (found %d , required %d)\n", + table_version, FRD_TABLE_VERSION ); + return -1; + } + + return 0; +} diff --git a/modules/fraud_detection/frd_load.h b/modules/fraud_detection/frd_load.h new file mode 100644 index 0000000000..44cc9974f6 --- /dev/null +++ b/modules/fraud_detection/frd_load.h @@ -0,0 +1,38 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#ifndef __FRD_LOAD_H__ +#define __FRD_LOAD_H__ + +int frd_init_db(void); +int frd_connect_db(void); +void frd_disconnect_db(void); + +int frd_reload_data(void); +void frd_destroy_data(void); + +#endif diff --git a/modules/fraud_detection/frd_stats.c b/modules/fraud_detection/frd_stats.c new file mode 100644 index 0000000000..6943f10063 --- /dev/null +++ b/modules/fraud_detection/frd_stats.c @@ -0,0 +1,150 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#include +#include "frd_stats.h" +#include "frd_hashmap.h" +#include "../../ut.h" + + +/* Struct used for the first level of the hashmap + * the user is kept in shm for two reasons : + * a) to keep using core's map + * b) to pass it for the dialog_end callback +*/ + +typedef struct { + + hash_map_t numbers_hm; + str user; +} frd_users_map_item_t; + +static hash_map_t stats_table; + +/* + * Function to init the stats hash table +*/ + +int init_stats_table(void) +{ + stats_table.size = FRD_USER_HASH_SIZE; + return init_hash_map(&stats_table); +} + + +frd_stats_entry_t* get_stats(str user, str prefix, str *shm_user) +{ + /* First go one level below using the user key */ + frd_users_map_item_t **hm = + (frd_users_map_item_t **)get_item(&stats_table, user); + + if (*hm == NULL) { + /* First time the user is seen, we must create a hashmap */ + *hm = shm_malloc(sizeof(frd_users_map_item_t)); + if (*hm == NULL) { + LM_ERR("no more shm memory\n"); + return NULL; + } + + (*hm)->numbers_hm.size = FRD_PREFIX_HASH_SIZE; + if (init_hash_map(&(*hm)->numbers_hm) != 0) { + LM_ERR("cannot init hashmap\n"); + shm_free(*hm); + return NULL; + } + + if (shm_str_dup(&(*hm)->user, &user) != 0) { + shm_free(*hm); + return NULL; + } + } + + if (shm_user) + *shm_user = (*hm)->user; + + frd_stats_entry_t **stats_entry = + (frd_stats_entry_t**)get_item(&(*hm)->numbers_hm, prefix); + if (*stats_entry == NULL) { + /* First time the prefix is seen for this user */ + *stats_entry = shm_malloc(sizeof(frd_stats_entry_t)); + if (*stats_entry == NULL) { + LM_ERR("no more shm memory\n"); + return NULL; + } + + /* Now init the auxiliary info for a stats structure */ + if (!lock_init(&(*stats_entry)->lock)) { + LM_ERR ("cannot init lock\n"); + shm_free(*stats_entry); + return NULL; + } + memset(&((*stats_entry)->stats), 0, sizeof(frd_stats_t)); + } + + return *stats_entry; +} + + +int stats_exist(str user, str prefix) +{ + /* First go one level below using the user key */ + frd_users_map_item_t **hm = + (frd_users_map_item_t **)get_item(&stats_table, user); + + if (*hm == NULL) + return 0; + + frd_stats_entry_t **stats_entry = + (frd_stats_entry_t**)get_item(&(*hm)->numbers_hm, prefix); + if (*stats_entry == NULL) + return 0; + + return 1; +} + +/* + * Functions for freeing the stats hash table +*/ + +static void destroy_stats_entry(void *e) +{ + lock_destroy( &((frd_stats_entry_t*)e)->lock ); + shm_free(e); +} + +static void destroy_users(void *u) +{ + frd_users_map_item_t *hm = (frd_users_map_item_t*)u; + free_hash_map(&hm->numbers_hm, destroy_stats_entry); + shm_free(hm->user.s); + shm_free(u); +} + +void free_stats_table(void) +{ + free_hash_map(&stats_table, destroy_users); +} diff --git a/modules/fraud_detection/frd_stats.h b/modules/fraud_detection/frd_stats.h new file mode 100644 index 0000000000..cfa7f573a6 --- /dev/null +++ b/modules/fraud_detection/frd_stats.h @@ -0,0 +1,71 @@ +/** + * + * Fraud Detection Module + * + * Copyright (C) 2014 OpenSIPS Foundation + * + * This file is part of opensips, a free SIP server. + * + * opensips 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 + * + * opensips 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 + * + * History + * ------- + * 2014-09-26 initial version (Andrei Datcu) +*/ + +#ifndef __FRD_STATS_H__ +#define __FRD_STATS_H__ + +#include "../../str.h" +#include "../../locking.h" +#include "../../rw_locking.h" + +#define FRD_USER_HASH_SIZE 1000 +#define FRD_PREFIX_HASH_SIZE 10 +#define FRD_SECS_PER_WINDOW 60 + +typedef struct { + unsigned int cpm; + unsigned int total_calls; + unsigned int concurrent_calls; + unsigned int seq_calls; + + unsigned int last_matched_rule; + time_t last_matched_time; + unsigned short calls_window[FRD_SECS_PER_WINDOW]; +} frd_stats_t; + +typedef struct _frd_hash_item { + gen_lock_t lock; + frd_stats_t stats; +} frd_stats_entry_t; + +int init_stats_table(void); +frd_stats_entry_t* get_stats(str user, str prefix, str *shm_user); +int stats_exist(str user, str prefix); +void free_stats_table(void); + + +typedef struct { + unsigned int warning; + unsigned int critical; +} frd_threshold_t; + +typedef struct { + frd_threshold_t cpm_thr, call_duration_thr, total_calls_thr, + concurrent_calls_thr, seq_calls_thr; +} frd_thresholds_t; + +#endif diff --git a/scripts/db_berkeley/opensips/fraud_detection b/scripts/db_berkeley/opensips/fraud_detection new file mode 100644 index 0000000000..0a1c190160 --- /dev/null +++ b/scripts/db_berkeley/opensips/fraud_detection @@ -0,0 +1,12 @@ +METADATA_COLUMNS +ruleid(int) profileid(int) prefix(str) start_hour(str) end_hour(str) daysoftheweek(str) cpm_warning(int) cpm_critical(int) call_duration_warning(int) call_duration_critical(int) total_calls_warning(int) total_calls_critical(int) concurrent_calls_warning(int) concurrent_calls_critical(int) sequential_calls_warning(int) sequential_calls_critical(int) +METADATA_KEY +0 +METADATA_READONLY +0 +METADATA_LOGFLAGS +0 +METADATA_DEFAULTS +NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL +fraud_detection| +fraud_detection|1 diff --git a/scripts/dbtext/opensips/fraud_detection b/scripts/dbtext/opensips/fraud_detection new file mode 100644 index 0000000000..7aacc07939 --- /dev/null +++ b/scripts/dbtext/opensips/fraud_detection @@ -0,0 +1,2 @@ +ruleid(int,auto) profileid(int) prefix(string) start_hour(string) end_hour(string) daysoftheweek(string) cpm_warning(int) cpm_critical(int) call_duration_warning(int) call_duration_critical(int) total_calls_warning(int) total_calls_critical(int) concurrent_calls_warning(int) concurrent_calls_critical(int) sequential_calls_warning(int) sequential_calls_critical(int) +fraud_detection:1 diff --git a/scripts/mysql/fraud_detection-create.sql b/scripts/mysql/fraud_detection-create.sql new file mode 100644 index 0000000000..8c981c9ab4 --- /dev/null +++ b/scripts/mysql/fraud_detection-create.sql @@ -0,0 +1,20 @@ +INSERT INTO version (table_name, table_version) values ('fraud_detection','1'); +CREATE TABLE fraud_detection ( + ruleid INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL, + profileid INT UNSIGNED NOT NULL, + prefix CHAR(64) NOT NULL, + start_hour CHAR(5) NOT NULL, + end_hour CHAR(5) NOT NULL, + daysoftheweek CHAR(64) NOT NULL, + cpm_warning INT(5) UNSIGNED NOT NULL, + cpm_critical INT(5) UNSIGNED NOT NULL, + call_duration_warning INT(5) UNSIGNED NOT NULL, + call_duration_critical INT(5) UNSIGNED NOT NULL, + total_calls_warning INT(5) UNSIGNED NOT NULL, + total_calls_critical INT(5) UNSIGNED NOT NULL, + concurrent_calls_warning INT(5) UNSIGNED NOT NULL, + concurrent_calls_critical INT(5) UNSIGNED NOT NULL, + sequential_calls_warning INT(5) UNSIGNED NOT NULL, + sequential_calls_critical INT(5) UNSIGNED NOT NULL +) ENGINE=MyISAM; + diff --git a/scripts/oracle/fraud_detection-create.sql b/scripts/oracle/fraud_detection-create.sql new file mode 100644 index 0000000000..37f01a357d --- /dev/null +++ b/scripts/oracle/fraud_detection-create.sql @@ -0,0 +1,28 @@ +INSERT INTO version (table_name, table_version) values ('fraud_detection','1'); +CREATE TABLE fraud_detection ( + ruleid NUMBER(10) PRIMARY KEY, + profileid NUMBER(10), + prefix VARCHAR2(64), + start_hour VARCHAR2(5), + end_hour VARCHAR2(5), + daysoftheweek VARCHAR2(64), + cpm_warning NUMBER(10), + cpm_critical NUMBER(10), + call_duration_warning NUMBER(10), + call_duration_critical NUMBER(10), + total_calls_warning NUMBER(10), + total_calls_critical NUMBER(10), + concurrent_calls_warning NUMBER(10), + concurrent_calls_critical NUMBER(10), + sequential_calls_warning NUMBER(10), + sequential_calls_critical NUMBER(10) +); + +CREATE OR REPLACE TRIGGER fraud_detection_tr +before insert on fraud_detection FOR EACH ROW +BEGIN + auto_id(:NEW.id); +END fraud_detection_tr; +/ +BEGIN map2users('fraud_detection'); END; +/ diff --git a/scripts/pi_http/fraud_detection-mod b/scripts/pi_http/fraud_detection-mod new file mode 100644 index 0000000000..99b1199ef0 --- /dev/null +++ b/scripts/pi_http/fraud_detection-mod @@ -0,0 +1,77 @@ + + fraud_detection + show + fraud_detection + DB_QUERY + + ruleidupdate + profileid + prefix + start_hour + end_hour + daysoftheweek + cpm_warning + cpm_critical + call_duration_warning + call_duration_critical + total_calls_warning + total_calls_critical + concurrent_calls_warning + concurrent_calls_critical + sequential_calls_warning + sequential_calls_critical + + + add + fraud_detection + DB_INSERT + + profileid + prefix + start_hour + end_hour + daysoftheweek + cpm_warning + cpm_critical + call_duration_warning + call_duration_critical + total_calls_warning + total_calls_critical + concurrent_calls_warning + concurrent_calls_critical + sequential_calls_warning + sequential_calls_critical + + + update + fraud_detection + DB_UPDATE + + ruleid= + + + profileid + prefix + start_hour + end_hour + daysoftheweek + cpm_warning + cpm_critical + call_duration_warning + call_duration_critical + total_calls_warning + total_calls_critical + concurrent_calls_warning + concurrent_calls_critical + sequential_calls_warning + sequential_calls_critical + + + delete + fraud_detection + DB_DELETE + + ruleid= + + + diff --git a/scripts/pi_http/fraud_detection-table b/scripts/pi_http/fraud_detection-table new file mode 100644 index 0000000000..55edc4d1fa --- /dev/null +++ b/scripts/pi_http/fraud_detection-table @@ -0,0 +1,21 @@ + + + fraud_detection + mysql + ruleidDB_INT + profileidDB_INT + prefixDB_STR + start_hourDB_STR + end_hourDB_STR + daysoftheweekDB_STR + cpm_warningDB_INT + cpm_criticalDB_INT + call_duration_warningDB_INT + call_duration_criticalDB_INT + total_calls_warningDB_INT + total_calls_criticalDB_INT + concurrent_calls_warningDB_INT + concurrent_calls_criticalDB_INT + sequential_calls_warningDB_INT + sequential_calls_criticalDB_INT + diff --git a/scripts/postgres/fraud_detection-create.sql b/scripts/postgres/fraud_detection-create.sql new file mode 100644 index 0000000000..80695237bb --- /dev/null +++ b/scripts/postgres/fraud_detection-create.sql @@ -0,0 +1,21 @@ +INSERT INTO version (table_name, table_version) values ('fraud_detection','1'); +CREATE TABLE fraud_detection ( + ruleid SERIAL PRIMARY KEY NOT NULL, + profileid INTEGER NOT NULL, + prefix VARCHAR(64) NOT NULL, + start_hour VARCHAR(5) NOT NULL, + end_hour VARCHAR(5) NOT NULL, + daysoftheweek VARCHAR(64) NOT NULL, + cpm_warning INTEGER NOT NULL, + cpm_critical INTEGER NOT NULL, + call_duration_warning INTEGER NOT NULL, + call_duration_critical INTEGER NOT NULL, + total_calls_warning INTEGER NOT NULL, + total_calls_critical INTEGER NOT NULL, + concurrent_calls_warning INTEGER NOT NULL, + concurrent_calls_critical INTEGER NOT NULL, + sequential_calls_warning INTEGER NOT NULL, + sequential_calls_critical INTEGER NOT NULL +); + +ALTER SEQUENCE fraud_detection_ruleid_seq MAXVALUE 2147483647 CYCLE;