#!KAMAILIO #====================================================================== # Defined Features Enabled #====================================================================== #!define WITH_MYSQL #!define WITH_AUTH #!define WITH_IPAUTH #!define WITH_UAC #!define WITH_USRLOCDB #!define WITH_ACCDB ##define WITH_CDRS #!define WITH_DROUTE ##!define WITH_DEBUG #!define WITH_NAT #!define WITH_DISPATCHER #!define WITH_CALL_SETTINGS ##!define WITH_SERVERNAT ##!define WITH_SERVERNAT6 #!define WITH_MULTIDOMAIN #!define WITH_TELEBLOCK #!define WITH_ANTIFLOOD ##!define WITH_DBCLUSTER #!define WITH_LCR #!define WITH_TLS ##!define WITH_SCTP #!define WITH_WEBSOCKETS ##!define WITH_DMQ #!define WITH_MSTEAMS #!define WITH_DNID_LNP_ENRICHMENT ##!define WITH_RTPENGINE #!define WITH_TRANSNEXUS #!define WITH_PRESENCE #!define WITH_STIRSHAKEN ##!define WITH_IPV6 ##!define WITH_HOMER ##!define WITH_DMZ ##!define WITH_PUSH #====================================================================== # Define String Replacements Within Config #====================================================================== #!subst "!DMQ_REPLICATE_ENABLED!0!g" #====================================================================== # Defined Constants with String Replacement #====================================================================== #!substdef "!DSIP_ID!29c7724e0ea088b49c13a93e2d31f11ad276fc0f694f57867a27f02f6e9493b48984711cec87067c78f02aa2ad34de9841313364443235396641303435454344!g" #!substdef "!DSIP_CLUSTER_ID!1!g" #!substdef "!DSIP_VERSION!0.76!g" #!substdef "!HOMER_ID!386615042!g" #!substdef "!INTERNAL_IP_ADDR!185.61.116.74!g" #!substdef "!INTERNAL_IP_NET!185.61.116.74/24!g" #!substdef "!INTERNAL_IP6_ADDR!fe80::2a6e:d4ff:fe88:e4f9!g" #!substdef "!INTERNAL_IP6_NET!!g" #!substdef "!INTERNAL_FQDN!acrobits-server.prosoluce.net!g" #!substdef "!EXTERNAL_IP_ADDR!185.61.116.74!g" #!substdef "!EXTERNAL_IP6_ADDR!fe80::2a6e:d4ff:fe88:e4f9!g" #!substdef "!EXTERNAL_FQDN!acrobits-server.prosoluce.net!g" #!substdef "!UAC_REG_ADDR!185.61.116.74!g" #!substdef "!INBOUND_NLB_FQDN!!g" #!substdef "!OUTBOUND_NLB_FQDN!!g" #!substdef "!HOMER_HOST!!g" #!substdef "!SIP_PORT!5060!g" #!substdef "!SIPS_PORT!5061!g" #!substdef "!DMQ_PORT!5090!g" #!substdef "!WSS_PORT!4443!g" #!substdef "!HEP_PORT!9060!g" #====================================================================== # Defined Constants #====================================================================== # - database URL - used to connect to database server by modules #!ifdef WITH_MYSQL #!ifdef WITH_DBCLUSTER #!define DBURL "cluster://dbcluster" #!define SQLCONN_KAM "kam=>mysql://kamailio:AHkdsXIWtYqkgwJbTSb0HYHdEooau29t2JgGaOWWPDWkcnfGg9MxEYYEdPNaeno7@localhost:3306/kamailio" #!define SQLCONN_AST "asterisk=>mysql://kamailio:AHkdsXIWtYqkgwJbTSb0HYHdEooau29t2JgGaOWWPDWkcnfGg9MxEYYEdPNaeno7@localhost:3306/kamailio" #!else #!define DBURL "mysql://kamailio:AHkdsXIWtYqkgwJbTSb0HYHdEooau29t2JgGaOWWPDWkcnfGg9MxEYYEdPNaeno7@localhost:3306/kamailio" #!define SQLCONN_KAM "kam=>mysql://kamailio:AHkdsXIWtYqkgwJbTSb0HYHdEooau29t2JgGaOWWPDWkcnfGg9MxEYYEdPNaeno7@localhost:3306/kamailio" #!define SQLCONN_AST "asterisk=>mysql://kamailio:AHkdsXIWtYqkgwJbTSb0HYHdEooau29t2JgGaOWWPDWkcnfGg9MxEYYEdPNaeno7@localhost:3306/kamailio" #!endif #!endif ### constants used by dispatcher routes #!define DSTALG_ROUND_ROBIN 4 #!define DSTALG_PRIORITY_BASED 8 #!define DSTALG_WEIGHT_BASED 9 #!define DSTALG_LOAD_DISTRIBUTION 10 #!define DSTALG_RELATIVE_WEIGHT 11 #!define DSTALG_PARALLEL_FORKING 12 #!ifdef WITH_MULTIDOMAIN # - the value for 'use_domain' parameters #!define MULTIDOMAIN 1 #!else #!define MULTIDOMAIN 0 #!endif ### flag definitions and usage: # NOTE: flags are stored in each bit of an integer, there range is from 0-31 (32 bits) # NOTE: it seems each set/type of flags has their own variable / namespace # FLT_ per transaction flags # usage: setflag(), resetflag(), isflagset() # FLB_ per branch flags # usage: setbflag(), resetbflag(), isbflagset() # FLS_ per script flags # usage: setsflag(), resetsflag(), issflagset() # FLD_ per dialog flags # usage: dlg_setflag(), dlg_isflagset(), dlg_resetflag() #!define FLT_ACC 0 #!define FLT_ACCMISSED 1 #!define FLT_ACCFAILED 2 #!define FLT_FAILOVER 3 #!define FLT_DIALOG 4 #!define FLT_USE_RTPE 5 #!define FLT_DOMAINROUTING 6 #!define FLT_PBX_AUTH 7 #!define FLT_CARRIER 8 #!define FLT_PBX 9 #!define FLT_CARRIER_AUTH 10 #!define FLT_EXTERNAL_AUTH 11 #!define FLT_PASSTHRU_AUTH 12 #!define FLT_SRC_SIP 13 #!define FLT_SRC_WS 14 #!define FLT_SRC_ALLOWED 15 #!define FLT_DST_INTERNAL_IP 16 #!define FLT_MSTEAMS 17 #!define FLT_SRC_INTERNAL_IP 18 #!define FLT_DST_IPV4 19 #!define FLT_DST_IPV6 20 #!define FLT_SRC_IPV4 21 #!define FLT_SRC_IPV6 22 #!define FLB_NATB 0 #!define FLB_NATSIPPING 1 #!define FLB_WS_DEVICE 2 #!define FLB_SRC_PBX 3 #!define FLB_DST_PBX 4 #!define FLB_SRC_CARRIER 5 #!define FLB_DST_CARRIER 6 #!define FLB_SRC_MSTEAMS 7 #!define FLB_DST_MSTEAMS 8 #!define FLB_SRC_MSTEAMS_ONHOLD 9 #!define FLB_SRC_SELF 10 # NOTE: not actual flags #!define FLT_OUTBOUND 8000 #!define FLT_INBOUND 9000 #!define NOTIFICATION_OVERLIMIT "0" #!define NOTIFICATION_GWFAILURE "1" #====================================================================== # Global Parameters #====================================================================== ### LOG Levels: # L_ALERT -5 # L_BUG -4 # L_CRIT2 -3 # L_CRIT -2 # L_ERR -1 # L_WARN 0 # L_NOTICE 1 # L_INFO 2 # L_DBG 3 #!ifdef WITH_DEBUG debug = 3 log_stderror = yes #!else debug = 2 log_stderror = false #!endif # specifies on which log level the memory debugger/statistics will be logged memdbg = 5 memlog = 5 # syslog log facility messages will be logged to log_facility = LOG_LOCAL0 # configure the prefix for all log messages log_prefix_mode = 1 log_prefix = "[$cfg(name):$cfg(line):$cfg(route)] [$ci:$rm:$rs] " # multiprocess settings on startup fork = true children = 1 # increase loop limit to allow 10000 DID checks for inbound calls max_while_loops = 10000 # uncomment the next line to disable TCP (default on) #disable_tcp = true # uncomment the next line to disable the auto discovery of local aliases # based on reverse DNS on IPs (default on) #auto_aliases = false # add local domain aliases # NOTE: port is required in alias for loose routing to function properly # REF: https://www.kamailio.org/wiki/cookbooks/5.5.x/core#alias alias = "EXTERNAL_FQDN:SIP_PORT" alias = "EXTERNAL_FQDN:SIPS_PORT" alias = "EXTERNAL_FQDN:DMQ_PORT" alias = "EXTERNAL_FQDN:WSS_PORT" alias = "INBOUND_NLB_FQDN:SIP_PORT" alias = "INBOUND_NLB_FQDN:SIPS_PORT" alias = "INBOUND_NLB_FQDN:DMQ_PORT" alias = "INBOUND_NLB_FQDN:WSS_PORT" alias = "OUTBOUND_NLB_FQDN:SIP_PORT" alias = "OUTBOUND_NLB_FQDN:SIPS_PORT" alias = "OUTBOUND_NLB_FQDN:DMQ_PORT" alias = "OUTBOUND_NLB_FQDN:WSS_PORT" # configure interface/port/proto kamailio will bind on listen = udp:127.0.0.1:SIP_PORT listen = tcp:127.0.0.1:SIP_PORT #!ifdef WITH_TLS listen = tls:127.0.0.1:SIPS_PORT #!ifdef WITH_WEBSOCKETS listen = tls:127.0.0.1:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:127.0.0.1:SIP_PORT #!endif #!ifdef WITH_DMZ listen = udp:EXTERNAL_IP_ADDR:SIP_PORT listen = tcp:EXTERNAL_IP_ADDR:SIP_PORT #!endif #!ifdef WITH_SERVERNAT listen = udp:INTERNAL_IP_ADDR:SIP_PORT advertise EXTERNAL_IP_ADDR:SIP_PORT listen = tcp:INTERNAL_IP_ADDR:SIP_PORT advertise EXTERNAL_IP_ADDR:SIP_PORT #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:INTERNAL_IP_ADDR:WSS_PORT advertise "EXTERNAL_FQDN":WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:INTERNAL_IP_ADDR:SIP_PORT advertise EXTERNAL_IP_ADDR:SIP_PORT #!endif #!else listen = udp:INTERNAL_IP_ADDR:SIP_PORT listen = tcp:INTERNAL_IP_ADDR:SIP_PORT #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:INTERNAL_IP_ADDR:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:INTERNAL_IP_ADDR:SIP_PORT #!endif #!endif #!ifdef WITH_IPV6 listen = udp:[::1]:SIP_PORT listen = tcp:[::1]:SIP_PORT #!ifdef WITH_TLS listen = tls:[::1]:SIPS_PORT #!ifdef WITH_WEBSOCKETS listen = tls:[::1]:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:[::1]:SIP_PORT #!endif #!ifdef WITH_DMZ listen = udp:[EXTERNAL_IP6_ADDR]:SIP_PORT listen = tcp:[EXTERNAL_IP6_ADDR]:SIP_PORT #!endif #!ifdef WITH_SERVERNAT6 listen = udp:[INTERNAL_IP6_ADDR]:SIP_PORT advertise EXTERNAL_IP6_ADDR:SIP_PORT listen = tcp:[INTERNAL_IP6_ADDR]:SIP_PORT advertise EXTERNAL_IP6_ADDR:SIP_PORT #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:[INTERNAL_IP6_ADDR]:WSS_PORT advertise "EXTERNAL_FQDN":WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:[INTERNAL_IP6_ADDR]:SIP_PORT advertise EXTERNAL_IP6_ADDR:SIP_PORT #!endif #!else listen = udp:[INTERNAL_IP6_ADDR]:SIP_PORT listen = tcp:[INTERNAL_IP6_ADDR]:SIP_PORT #!endif #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:[INTERNAL_IP6_ADDR]:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:[INTERNAL_IP6_ADDR]:SIP_PORT #!endif #!endif #!ifdef WITH_DMQ listen = udp:INTERNAL_IP_ADDR:DMQ_PORT #!endif #!ifdef WITH_TLS enable_tls = true tcp_accept_no_cl = true tcp_rd_buf_size = 16384 listen = tls:INTERNAL_IP_ADDR:SIPS_PORT advertise "EXTERNAL_FQDN":SIPS_PORT #!ifdef WITH_IPV6 listen = tls:[INTERNAL_IP6_ADDR]:SIPS_PORT advertise "EXTERNAL_FQDN":SIPS_PORT #!endif #!endif # life time of TCP connection when there is no traffic # - a bit higher than registration expires to cope with UA behind NAT tcp_connection_lifetime = 3605 # Whether the “Server” header for locally generated messages is set server_signature = false # What the generated "Server" header will be set to server_header = "Server: dSIPRouter/DSIP_VERSION" # disable Stream Control Tranmission Protocol (SCTP) #!ifdef WITH_SCTP enable_sctp = 1 #!else enable_sctp = 0 #!endif # enable/disable DMZ support #!ifdef WITH_DMZ mhomed=1 #!else mhomed=0 #!endif ####### Custom Parameters ######### # These parameters can be modified runtime via RPC interface # - see the documentation of 'cfg_rpc' module. # # Format: group.id = value 'desc' description # Access: $sel(cfg_get.group.id) or @cfg_get.group.id # #!ifdef WITH_PSTN # PSTN GW Routing # # - pstn.gw_ip: valid IP or hostname as string value, example: # pstn.gw_ip = "10.0.0.101" desc "My PSTN GW Address" # # - by default is empty to avoid misrouting pstn.gw_ip = "" desc "PSTN GW Address" pstn.gw_port = "" desc "PSTN GW Port" #!endif #!ifdef WITH_VOICEMAIL # VoiceMail Routing on offline, busy or no answer # # - by default Voicemail server IP is empty to avoid misrouting voicemail.srv_ip = "" desc "VoiceMail IP Address" voicemail.srv_port = "5060" desc "VoiceMail Port" #!endif #!ifdef WITH_TELEBLOCK teleblock.gw_enabled = 0 desc "Enable Teleblock support" teleblock.gw_ip = "62.34.24.22" desc "Teleblock IP" teleblock.gw_port = "5066" desc "Teleblock Port" teleblock.media_ip = "" desc "Teleblock media ip" teleblock.media_port = "" desc "Teleblock media port" #!endif # Define the role of the server # "" - default behavior # outbound - outbound only (no domain routing) # inout - inbound and outbound only (no domain routing) server.role = "" desc "Role of the server in the topology" # Local calling maximum digits for the initiating PBX - PBX sending the INVITE server.pbx_max_local_digits = 30 desc "Maximum digits for local pbx extensions" # PBX INVITE Timeout (msecs) to support having a Primary and Secondary PBX server.pbx_invite_timeout = 5000 desc "The default PBX INVITE timeout" # PBX INVITE Timeout (msecs) if a SIP 100/180/181/183 message is received server.pbx_invite_timeout_aftertry = 16000 desc "PBX INVITE timeout value if a SIP 100 message is received" # DSIPRouter API Server Settings server.api_server = "https://127.0.0.1:5000" desc "URL to the DSIPRouter API Server" server.api_token = "avVgCRKajWckPNDTAv1vpiJRHHK66p1RxWYULjVmBHapqZYcLPnGh3CBbsq6Co7H" desc "API Token for DSIPRouter API Server" # Emergency Numbers server.emergency_numbers = "^([2-9]11|112|999|000|988|933)$" desc "Emergency Numbers" # Transnexus External STIR/SHAKEN Support transnexus.authservice_lrn_enabled = 0 desc "Enable LRN for authservice" transnexus.authservice_enabled = 0 desc "Enable auth service for all Trunks" transnexus.authservice_host = "outbound.sip.clearip.com:5060" desc "Host to connect to for auth service" transnexus.verifyservice_enabled = 0 desc "Enable verify service for all Trunks" transnexus.verifyservice_host = "inbound.sip.clearip.com:5060" desc "Host to connect to for verify service" # Native Kamailio STIR/SHAKEN Support stir_shaken.stir_shaken_enabled = 0 desc "Whether native STIR/SHAKEN support is enabled" stir_shaken.stir_shaken_prefix_a = "" desc "Default Empty" stir_shaken.stir_shaken_prefix_b = "" desc "Default Empty" stir_shaken.stir_shaken_prefix_c = "" desc "Default Empty" stir_shaken.stir_shaken_prefix_invalid = "" desc "Default Empty" stir_shaken.stir_shaken_block_invalid = 0 desc "Default Disabled" stir_shaken.stir_shaken_key_path = "/etc/dsiprouter/certs/stirshaken/stirshaken-key.pem" desc "Local path to RSA key" stir_shaken.stir_shaken_cert_url = " https://cr.example.com/xK/order/xk" desc "URL to download X509 certificate from" # MSTeams Settings server.msteams_disable_refer = 1 # set location to load modules from (source or installation folders) #!ifdef WITH_SRCPATH mpath = "/usr/lib/x86_64-linux-gnu/kamailio/modules/" #!else mpath = "/usr/lib/x86_64-linux-gnu/kamailio/modules/" #!endif #====================================================================== # Module Loading #====================================================================== #!ifdef WITH_TLS loadmodule "tls.so" #!endif #!ifdef WITH_SCTP loadmodule "sctp.so" #!endif #!ifdef WITH_MYSQL loadmodule "db_mysql.so" #!endif loadmodule "kex.so" loadmodule "corex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "rr.so" loadmodule "path.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "usrloc.so" loadmodule "registrar.so" loadmodule "textops.so" loadmodule "textopsx.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "cfg_rpc.so" loadmodule "acc.so" loadmodule "xhttp.so" loadmodule "json.so" loadmodule "jansson.so" loadmodule "jsonrpcs.so" loadmodule "http_async_client.so" loadmodule "uuid.so" loadmodule "ipops.so" loadmodule "sdpops.so" loadmodule "rtimer.so" loadmodule "sqlops.so" #!ifdef WITH_DEBUG loadmodule "sipdump.so" #!endif #!ifdef WITH_DMQ loadmodule "dmq.so" loadmodule "dmq_usrloc.so" #!endif # must be loaded after dmq loadmodule "htable.so" loadmodule "dialog.so" #!ifdef WITH_AUTH loadmodule "auth.so" loadmodule "auth_db.so" #!ifdef WITH_IPAUTH loadmodule "permissions.so" #!endif #!ifdef WITH_UAC loadmodule "uac.so" loadmodule "uac_redirect.so" #!endif #!endif #!ifdef WITH_ALIASDB loadmodule "alias_db.so" #!endif #!ifdef WITH_SPEEDDIAL loadmodule "speeddial.so" #!endif #!ifdef WITH_MULTIDOMAIN loadmodule "domain.so" #!endif #!ifdef WITH_PRESENCE loadmodule "presence.so" loadmodule "presence_xml.so" loadmodule "presence_dialoginfo.so" loadmodule "pua.so" loadmodule "pua_dialoginfo.so" #!endif #!ifdef WITH_NAT loadmodule "nathelper.so" #!endif #!ifdef WITH_RTPENGINE loadmodule "rtpengine.so" #!endif #!ifdef WITH_ANTIFLOOD loadmodule "pike.so" #!endif #!ifdef WITH_XMLRPC loadmodule "xmlrpc.so" #!endif #!ifdef WITH_DEBUG loadmodule "debugger.so" #!endif #!ifdef WITH_DROUTE loadmodule "drouting.so" #!endif #!ifdef WITH_DBCLUSTER loadmodule "db_cluster" #!endif #!ifdef WITH_DISPATCHER loadmodule "keepalive.so" loadmodule "dispatcher.so" #!endif #!ifdef WITH_WEBSOCKETS loadmodule "websocket.so" #!endif #!ifdef WITH_STIRSHAKEN loadmodule "stirshaken.so" #!endif #!ifdef WITH_HOMER loadmodule "siptrace.so" #!endif #!ifdef WITH_PUSH loadmodule "tsilo.so" #!endif #====================================================================== # Module-Specific Parameters #====================================================================== # ---- xlog global params ---- modparam("xlog", "buf_size", 8192) modparam("xlog", "prefix", "") # ---- htable global params ---- modparam("htable", "db_url", DBURL) # ---- dispatcher params ---- #!ifdef WITH_DISPATCHER modparam("dispatcher", "flags", 2) modparam("dispatcher", "db_url", DBURL) modparam("dispatcher", "table_name", "dispatcher") # The flags column controls the mode of a destination and keepalives. It is a bitwise value that can be built using the following flags: # 1 (1 <<0): inactive destination # 2 (1 <<1): temporary trying destination (in the way to become inactive if it does not reply to keepalives # 4 (1 <<2): admin disabled destination # 8 (1 <<3): probing destination (sending keep alives) # 16 (1 <<4): skip DNS A/AAAA resolve at startup, useful when the hostname of the destination address is a NAPTR or SRV record only modparam("dispatcher", "flags_col", "flags") # Controls what gateways are tested to see if they are reachable: # 0: Only the gateways with state PROBING (flags & 0x08 == 1) are tested. After a gateway is probed, the PROBING state is cleared in this mode (it will probe only one time at startup or after dispatcher reload). # 1: All gateways are tested. If there is a failure of keepalive to an active gateway, then it is set to TRYING state. # 2: Only gateways in INACTIVE state with PROBING mode set are tested. # 3: Any gateway with state PROBING is continually probed without modifying/removing the PROBING state. This allows selected gateways to be probed continually, regardless of state changes. modparam("dispatcher", "ds_probing_mode", 3) modparam("dispatcher", "ds_ping_latency_stats", 1) # 1 means to provide latency stats modparam("dispatcher", "ds_ping_method", "OPTIONS") # The SIP method to use when pinging destinations modparam("dispatcher", "ds_ping_interval", 60) # How often (seconds) to ping destinations to check status modparam("dispatcher", "ds_probing_threshold", 1) # How many failed ping requests before marking the destination as inactive modparam("dispatcher", "ds_inactive_threshold", 1) # How many successful ping requests before marking the destination as active modparam("dispatcher", "xavp_dst", "dispatcher_dst") # Will contain selected destination info modparam("dispatcher", "xavp_dst_mode", 0) # What attributes to set in the xavp modparam("dispatcher", "xavp_ctx", "dispatcher_ctx") # Will contain current dispatcher context info modparam("dispatcher", "xavp_ctx_mode", 0) # What attributes to set in the xavp modparam("dispatcher", "reload_delta", 1) # how quickly (in seconds) a RPC reload is allowed #!endif # ----- db_cluster params ---- # connection: set for each db connection uri # cluster: s == serial, r == roundrobin, p == parallel (write/only) #!ifdef WITH_DBCLUSTER modparam("db_cluster", "connection", "c1=>mysql://kamailio:kamailiorw@192.168.1.2/kamailio") modparam("db_cluster", "connection", "c2=>mysql://kamailio:kamailiorw@192.168.1.3/kamailio") modparam("db_cluster", "connection", "c3=>mysql://kamailio:kamailiorw@192.168.1.4/kamailio") modparam("db_cluster", "connection", "c4=>mysql://kamailio:kamailiorw@192.168.1.5/kamailio") modparam("db_cluster", "cluster", "dbcluster=>c1=9r9r;c2=9r9r;c3=9r9r;c4=9r9r") modparam("db_cluster", "inactive_interval", 180) #!endif # ----- jsonrpcs params ----- modparam("jsonrpcs", "pretty_format", 1) modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "transport", 3) #modparam#("jsonrpcs", "dgram_socket", "/var/run/kamailio/kamailio_rpc.sock") # ----- ctl params ----- modparam("ctl", "binrpc", "unix:/var/run/kamailio/kamailio_ctl") # ----- tm params ----- # auto-discard branches from previous serial forking leg modparam("tm", "failure_reply_mode", 3) # default retransmission timeout: 30sec modparam("tm", "fr_timer", 30000) # default invite retransmission timeout after 1xx: 60sec modparam("tm", "fr_inv_timer", 60000) # XAVP used to store contacts used by t_load_contacts()/t_next_contacts() modparam("tm", "contacts_avp", "tm_contacts") # Used to store contacts (if any) that it skipped, because they contained same +sip.instance modparam("tm", "contact_flows_avp", "tm_contact_flows") # consider branch failures for to_on_failure() routing as well modparam("tm", "failure_exec_mode", 1) # ----- rr params ----- # set next param to 1 to add value to ;lr param (helps with some UAs) modparam("rr", "enable_full_lr", 0) # append from tag to the RR (required for is_direction() to work) modparam("rr", "append_fromtag", 1) # ----- registrar params ----- modparam("registrar", "method_filtering", 1) /* uncomment the next line to disable parallel forking via location */ # modparam("registrar", "append_branches", 0) /* uncomment the next line not to allow more than 10 contacts per AOR */ #modparam("registrar", "max_contacts", 10) # max value for expires of registrations modparam("registrar", "max_expires", 0) # set it to 1 to enable GRUU modparam("registrar", "gruu_enabled", 0) # ----- acc params ----- # what special events should be accounted? modparam("acc", "early_media", 0) modparam("acc", "report_ack", 0) modparam("acc", "report_cancels", 0) # by default ww do not adjust the direction of the sequential requests # if you enable this parameter, be sure the enable "append_fromtag" in "rr" module modparam("acc", "detect_direction", 0) modparam("acc", "log_flag", FLT_ACC) modparam("acc", "log_facility", "LOG_LOCAL0") modparam("acc", "log_missed_flag", FLT_ACCMISSED) modparam("acc", "log_extra", "src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;" "calltype=$avp(calltype);src_gwgroupid=$avp(src_gwgroupid);dst_gwgroupid=$avp(dst_gwgroupid)") modparam("acc", "failed_transaction_flag", FLT_ACCFAILED) # enhanced DB accounting #!ifdef WITH_ACCDB modparam("acc", "db_flag", FLT_ACC) modparam("acc", "db_missed_flag", FLT_ACCMISSED) modparam("acc", "db_url", DBURL) modparam("acc", "db_extra", "src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;" "calltype=$avp(calltype);src_gwgroupid=$avp(src_gwgroupid);dst_gwgroupid=$avp(dst_gwgroupid)") #!endif # ----- usrloc params ----- /* enable DB persistency for location entries */ #!ifdef WITH_USRLOCDB modparam("usrloc", "db_url", DBURL) modparam("usrloc", "db_mode", 3) modparam("usrloc", "use_domain", MULTIDOMAIN) modparam("usrloc", "handle_lost_tcp", 1) #!endif # ----- auth_db params ----- #!ifdef WITH_AUTH modparam("auth_db", "db_url", DBURL) modparam("auth_db", "calculate_ha1", 1) modparam("auth_db", "password_column", "password") # We use the rpid field of the subscriber table to track the assigned gwgroup (type of endpoint or carrier) modparam("auth_db", "load_credentials", "$avp(s:src_gwgroupid)=rpid;") modparam("auth_db", "use_domain", MULTIDOMAIN) # ----- permissions params ----- #!ifdef WITH_IPAUTH modparam("permissions", "db_url", DBURL) modparam("permissions", "db_mode", 1) # how quickly (in seconds) a RPC reload is allowed modparam("permissions", "reload_delta", 1) #!endif #!endif # ----- alias_db params ----- #!ifdef WITH_ALIASDB modparam("alias_db", "db_url", DBURL) modparam("alias_db", "use_domain", MULTIDOMAIN) #!endif # ----- speeddial params ----- #!ifdef WITH_SPEEDDIAL modparam("speeddial", "db_url", DBURL) modparam("speeddial", "use_domain", MULTIDOMAIN) #!endif # ----- domain params ----- #!ifdef WITH_MULTIDOMAIN modparam("domain", "db_url", DBURL) # register callback to match myself condition with domains list modparam("domain", "register_myself", 1) #!endif #!ifdef WITH_PRESENCE # ----- presence params ----- modparam("presence", "db_url", DBURL) modparam("presence", "server_address", "sip:185.61.116.61:5060") modparam("presence", "db_url", DBURL) modparam("presence", "subs_db_mode", 3) # DB-Only scheme. modparam("presence", "db_table_lock_type", 0) modparam("presence", "notifier_processes", 0) # setting this parameter to 0 when subs_db_mode is 3 keeps the old behaviour (sending NOTIFY requests immediately). modparam("presence", "subs_htable_size", 15) modparam("presence", "delete_same_subs", 1) modparam("presence", "clean_period", 60) modparam("presence", "retrieve_order", 1) modparam("presence", "sip_uri_match", 1) modparam("presence", "pres_subs_mode", 0) modparam("presence", "timeout_rm_subs", 0) modparam("presence", "fetch_rows", 10000) modparam("presence", "pres_htable_size", 15) modparam("presence", "max_expires", 1800) modparam("presence", "send_fast_notify", 1) modparam("presence", "force_delete", 0) modparam("presence", "expires_offset", 10) modparam("presence", "send_fast_notify", 0) # modparam("presence", "match_etag_on_refresh", 1) modparam("presence", "db_update_period", 20) modparam("presence", "clean_period", 100) modparam("presence", "max_expires", 3600) modparam("presence", "subs_db_mode", 2) modparam("presence", "expires_offset", 10) modparam("presence", "fetch_rows", 1000) # ----- presence_xml params ----- modparam("presence_xml", "db_url", DBURL) modparam("presence_xml", "force_active", 1) # ----- presence_xml params ----- modparam("presence_xml", "db_url", DBURL) modparam("presence_xml", "force_active", 1) # ----- presence_dialoginfo params ----- modparam("presence_dialoginfo", "force_single_dialog", 1) # ----- pua params ----- modparam("pua", "db_url", DBURL) modparam("pua", "db_mode", 1) modparam("pua", "update_period", 60) modparam("pua", "dlginfo_increase_version", 0) modparam("pua", "reginfo_increase_version", 0) modparam("pua", "check_remote_contact", 1) modparam("pua", "fetch_rows", 1000) # ----- pua_dialoginfo params ----- modparam("pua_dialoginfo", "include_callid", 1) modparam("pua_dialoginfo", "send_publish_flag", 10) modparam("pua_dialoginfo", "caller_confirmed", 0) modparam("pua_dialoginfo", "include_tags", 1) modparam("pua_dialoginfo", "override_lifetime", 124) #!endif #!ifdef WITH_NAT # ----- nathelper params ----- modparam("nathelper", "natping_interval", 60) modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) modparam("nathelper", "sipping_from", "sip:pinger@UAC_REG_ADDR") # params needed for NAT traversal in other modules modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") modparam("usrloc", "nat_bflag", FLB_NATB) #!endif #!ifdef WITH_RTPENGINE # ----- rtpengine params ----- modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223") # Specify if the RTPEngine instances have to be pinged at startup to detect if they are active. Set it to 0 to disable pinging and to 1 to activate pinging modparam("rtpengine", "ping_mode", 0) # Once an RTP proxy was found unreachable and marked as disabled, the rtpengine module will not attempt to establish communication to that RTP proxy for rtpengine_disable_tout seconds modparam("rtpengine", "rtpengine_disable_tout", 0) # How many times the module should retry to send and receive after timeout was generated modparam("rtpengine", "rtpengine_retr", 5) # Number of seconds after an rtpengine hash table entry is marked for deletion. The entries correspond to active calls using an rtpengine instance # This should match the default rtpengine timeout set in rtpengine.conf modparam("rtpengine", "hash_table_tout", 60) #!endif #!ifdef WITH_TLS # ----- tls params ----- modparam("tls", "config", "//etc/kamailio/tls.cfg") #!endif #!ifdef WITH_SCTP modparam("sctp", "sctp_assoc_tracking", 0) modparam("sctp", "sctp_assoc_reuse", 0) #!endif #!ifdef WITH_ANTIFLOOD # ----- pike params ----- modparam("pike", "sampling_time_unit", 2) modparam("pike", "reqs_density_per_unit", 50) modparam("pike", "remove_latency", 30) modparam("pike", "remove_latency", 30) # ----- htable params ----- # ip ban htable with autoexpire after 5 minutes modparam("htable", "htable", "ipban=>size=8;autoexpire=300;dmqreplicate=DMQ_REPLICATE_ENABLED;") #!endif #!ifdef WITH_XMLRPC # ----- xmlrpc params ----- modparam("xmlrpc", "route", "XMLRPC"); modparam("xmlrpc", "url_match", "^/RPC") #!endif #!ifdef WITH_DEBUG # ----- debugger params ----- modparam("debugger", "cfgtrace", 0) modparam("debugger", "log_level_name", "debugger") # ----- sipdump params ----- modparam("sipdump", "enable", 1) modparam("sipdump", "wait", 100) modparam("sipdump", "rotate", 3600) modparam("sipdump", "folder", "/tmp") modparam("sipdump", "fprefix", "dsipdump-") #!endif #!ifdef WITH_UAC # ----- uac params ----- modparam("uac", "restore_mode", "none") modparam("uac", "reg_db_url", DBURL) modparam("uac", "reg_db_table", "uacreg") modparam("uac", "reg_timer_interval", 60) modparam("uac", "reg_retry_interval", 120) modparam("uac", "reg_keep_callid", 1) modparam("uac", "reg_gc_interval", 30) modparam("uac", "credential", "username:domain:password") modparam("uac", "auth_realm_avp", "$avp(arealm)") modparam("uac", "auth_username_avp", "$avp(auser)") modparam("uac", "auth_password_avp", "$avp(apass)") modparam("uac", "reg_contact_addr", "UAC_REG_ADDR:SIP_PORT") # how quickly (in seconds) a RPC reload is allowed modparam("uac", "reload_delta", 1) #!endif #!ifdef WITH_DROUTE # ----- drouting params ----- modparam("drouting", "db_url", DBURL) modparam("drouting", "ruri_avp", "$avp(dr_ruri)") # our convention for passing some data about the selected route: # attrs[0]: gwid, # attrs[1]: gwtype # attrs[2]: msteams domain # attrs[3]: signalling transport # attrs[4]: media transport modparam("drouting", "attrs_avp", "$avp(dr_attrs)") # don't match on domain (per group) only use routing group modparam("drouting", "use_domain", 0) # do not resolve DNS names during load (will blindly try them) modparam("drouting", "force_dns", 0) # how gateways are selected from each rule # 0: destination groups are ignored and all the destinations are tried in the given order # 1: the destinations from each group are randomly arranged (only the two first elements are randomly selected); groups do maintain their order (as given) # 2: from each destination group, only a single destination is randomly selected; groups do maintain their order (as given) modparam("drouting", "sort_order", 0) # htable for maintenance mode modparam("htable", "htable", "maintmode=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_maintmode;cols='ipaddr,gwid';") #modparam("drouting", "enable_keepalive", 1) #!endif #!ifdef WITH_LCR # ----- htable params for from/to prefix lookup ----- modparam("htable", "htable", "tofromprefix=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_lcr;cols='pattern,dr_groupid';") #!endif # ----- rtimer params ----- #!ifdef WITH_CDRS modparam("rtimer", "timer", "name=cdr;interval=300;mode=1;") modparam("rtimer", "exec", "timer=cdr;route=CDRS") #!endif # ----- sqlops params ----- # Kamailio Connection modparam("sqlops", "sqlcon", SQLCONN_KAM) # Asterisk Realtime Connection modparam("sqlops", "sqlcon", SQLCONN_AST) # ----- dialog params ----- modparam("dialog", "db_url", DBURL) modparam("dialog", "db_mode", 0) modparam("dialog", "enable_stats", 1) modparam("dialog", "hash_size", 4096) modparam("dialog", "detect_spirals", 1) modparam("dialog", "track_cseq_updates", 1) #!ifdef WITH_DMQ # ---- dmq params ---- # TODO: dmq module only supports one server_address so we do not listen on IPV6, possibly change to INTERNAL_FQDN? # this would only be an issue for IPv4 disabled machines, which is very uncommon modparam("dmq", "server_address", "sip:INTERNAL_IP_ADDR:DMQ_PORT") modparam("dmq", "notification_address", "sip:local.cluster:DMQ_PORT") modparam("dmq", "multi_notify", 1) modparam("dmq", "num_workers", 4) modparam("dmq", "ping_interval", 15) modparam("dmq_usrloc", "enable", 1) # ---- dmq-related params ---- modparam("dialog", "enable_dmq", 1) modparam("htable", "enable_dmq", 1) # only valid for kam ver >= 5.2 modparam("htable", "dmq_init_sync", 1) #!endif #!ifdef WITH_CALL_SETTINGS modparam("dialog", "profiles_with_value", "gwgroup") modparam("htable", "htable", "call_settings=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_call_settings_h;cols='gwgroupid,limit,timeout';colnull='';") modparam("htable", "htable", "concurrent_calls=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;initval=0;") #!endif # gw2gwroup is used to lookup gwgroupid modparam("htable", "htable", "gw2gwgroup=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_gw2gwgroup;cols='gwid,gwgroupid';") # gwgroup2lb is used to lookup the dispatcher setid associated with a gwgroupid modparam("htable", "htable", "gwgroup2lb=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_gwgroup2lb;cols='gwgroupid,setid,enabled';") # inbound_hardfwd is used to lookup did and dr_groupid for forwarding calls unconditionally modparam("htable", "htable", "inbound_hardfwd=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_hardfwd;cols='dr_ruleid,did,dr_groupid';") # inbound_failfwd is used to lookup did and dr_groupid for forwarding calls on failover modparam("htable", "htable", "inbound_failfwd=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_failfwd;cols='dr_ruleid,did,dr_groupid';") # inbound_prefixmap is used to lookup dr_ruleid for a prefix modparam("htable", "htable", "inbound_prefixmap=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_prefix_mapping;cols='prefix,ruleid,priority';") # to manage Pass Thru Auth for Registration. Used to send Authorization requests back to the same backend media server modparam("htable", "htable", "pass_thru_auth=>size=8;autoexpire=3600;dmqreplicate=DMQ_REPLICATE_ENABLED;") # enrichdnid_lnpmap is used to lookup dnid prefixes to match against #!ifdef WITH_DNID_LNP_ENRICHMENT modparam("htable", "htable", "enrichdnid_lnpmap=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_dnid_lnp_mapping;cols='dnid,prefix';") #!endif # Pass-Thru Auth IP to Domain mapping lookup. Allows PJSIP Pass-Thru to work correctly #modparam("htable", "htable", "pbxip2domain=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED") # ----- http_async_client params ----- modparam("http_async_client", "workers", 1) modparam("http_async_client", "connection_timeout", 500) modparam("http_async_client", "hash_size", 2048) #!ifdef WITH_DEBUG modparam("http_async_client", "curl_verbose", 1) #!endif #!ifdef WITH_TLS modparam("http_async_client", "tls_client_cert", "/etc/dsiprouter/certs/dsiprouter-cert.pem") modparam("http_async_client", "tls_client_key", "/etc/dsiprouter/certs/dsiprouter-key.pem") modparam("http_async_client", "tls_ca_path", "/etc/dsiprouter/certs/") #!endif #!ifdef WITH_STIRSHAKEN modparam("htable", "htable", "dr_rules=>size=14;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dr_rules;cols='prefix,ruleid';") #!endif # ---- sip trace params ---- #!ifdef WITH_HOMER modparam("siptrace", "duplicate_uri", "sip:HOMER_HOST:HEP_PORT") modparam("siptrace", "hep_version", 3) modparam("siptrace", "hep_mode_on", 1) modparam("siptrace", "hep_capture_id", HOMER_ID) modparam("siptrace", "trace_to_database", 0) modparam("siptrace", "trace_on", 1) modparam("siptrace", "trace_mode", 1) modparam("siptrace", "trace_sl_acks", 1) #!endif #!ifdef WITH_PUSH modparam("htable", "htable", "push=>size=10;autoexpire=120;") #!endif #====================================================================== # Routing Logic #====================================================================== # Main SIP request routing logic # - executed for each SIP request received from the network # - specific request processing logic should be abstracted into sub-routes # - sub-routes are defined using the syntax: route[...] { ... } # - sub-routes can be executed within any routing block request_route { #!ifdef WITH_DEBUG xlog("L_DBG", "processing request from $sas on $Rut\n"); #!endif # handle DMQ messages route(DMQ); # per request initial checks route(REQINIT); #!ifdef WITH_STIRSHAKEN if ((int)$sel(cfg_get.stir_shaken.stir_shaken_enabled)) { route(STIRSHAKEN_INBOUND); } #!endif # NAT detection route(NATDETECT); # CANCEL processing route(HANDLE_CANCEL); # handle requests within SIP dialogs route(WITHINDLG); # handle retransmissions route(HANDLE_RETRANS); # authentication route(AUTH); # handle registrations route(REGISTRAR); # handle presence related requests route(PRESENCE); # dispatch to local endpoints that registered thru the proxy route(LOCATION); # enrich dialed number before routing route(ENRICH_DNID); #!ifdef WITH_TRANSNEXUS # Process call if Transnexus validation service is enabled if ((int)$sel(cfg_get.transnexus.verifyservice_enabled) && !has_totag()) { route(TRANSNEXUS_INBOUND); } #!endif # route the call to the next hop route(NEXTHOP); } # Main SIP response handling logic # - executed for each SIP response received from the network # - specific reply processing logic should be abstracted into sub-routes # - onreply routes should not be used here and are only executed when set w/ t_on_reply() # - sub-routes called here are executed by the core (onreply routes are executed by tm module) reply_route { #!ifdef WITH_DEBUG xlog("L_DBG", "processing reply from $sas on $Rut\n"); #!endif return; } # Pre-Send SIP request handling logic # - executed prior to forwarding specific SIP requests from the network # - not executed for replies, retransmissions, or locally generated messages # - a very limited set of core functions are available here, no sub-routes either onsend_route { #!ifdef WITH_DEBUG xlog("L_DBG", "sending message\n"); #!endif return; } route[HANDLE_CANCEL] { if (is_method("CANCEL")) { if (t_check_trans()) { route(RELAY); } exit; } } route[HANDLE_RETRANS] { if (t_precheck_trans()) { t_check_trans(); exit; } t_check_trans(); } route[DMQ] { #!ifdef WITH_DMQ if ($rm == "KDMQ" && $rp == DMQ_PORT) { dmq_handle_message(); exit; } #!endif return; } route[REFORMATRURI] { xlog("L_DBG", "original rU <$rU> and original tU <$tU>\n"); # This is to deal with those who are used to dialing 7 digits # assuming that the 7 digit number being dialed is in the same area code as the FROM number if ($(rU{s.len}) == 7) { if ($(fU{s.len}) == 10) { $rU = $(fU{s.substr,0,3}) + $rU; } else { $rU = $(fU{s.substr,0,4}) + $rU; } $tU = $rU; } else if ($(rU{s.len}) > 10) { # Check for +1 and remove it from the RURI and the To header if ($(rU{s.substr,0,2}) == "+1") { $rU = $(rU{s.substr,2,0}); $tU = $rU; } # Check for 1 and remove it from the RURI and the To header else if ($(rU{s.substr,0,1}) == "1") { $rU = $(rU{s.substr,1,0}); $tU = $rU; } } xlog("L_DBG", "modified rU <$rU> and modified tU <$tU>\n"); } route[ENRICH_SIPHEADER] { if (!strempty($xavp(ra=>sipdomain))) { append_hf("X-SIPDOMAIN: $xavp(ra=>sipdomain)\r\n"); } } route[ENRICH_DNID] { route(ENRICH_DNID_LNP); #route(REFORMATRURI); } route[ENRICH_DNID_LNP] { #!ifdef WITH_DNID_LNP_ENRICHMENT # enrich dialed number with country code and area code $var(dnid) = $(rU{s.unescape.user}); $avp(dnid_prefix) = $sht(enrichdnid_lnpmap=>$var(dnid)); if ($avp(dnid_prefix) != $null && !strempty($avp(dnid_prefix))) { $rU = $(avp(dnid_prefix){s.escape.user}) + $rU; $tU = $(avp(dnid_prefix){s.escape.user}) + $tU; } #!endif return; } # Route the call to the next hop, which can be a PBX or Carrier route[NEXTHOP] { # handle dialog tracking dlg_manage(); ###################################### # Endpoint to PBX via Domain Routing # ###################################### # TODO: pbx_type in domain_attrs is not standardized, standardize this in v0.80 if (isflagset(FLT_DOMAINROUTING) && !isflagset(FLT_EXTERNAL_AUTH)) { #Grab the value of the avp that contains the domain_pbx_ip. #This is where requests for that domain should be routed # Route to the endpoints defined in the Endpoint Gateway list (dr_gw_list) if ($avp(domain_pbx_type) == "2") { xlog("L_INFO", "<$ci> Routing to Endpoint Gateway List\n"); if (!strempty($sht(pass_thru_auth=>$ci))) { $du = $sht(pass_thru_auth=>$ci); xlog("L_INFO", "DOMAINROUTING last du was $du\n"); } # Forward the registration onto one of the servers in the cluster else if (!strempty($avp(domain_dispatcher_set_id))) { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); if (!strempty($avp(domain_dispatcher_reg_alg))) { # Set the registration algoritm $avp(dispatcher_alg) = $avp(domain_dispatcher_reg_alg); } else { # Set the dispatcher algorthim to round robin by default $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; } route(DISPATCHER_SELECT); } } # Otherwise, send to the single PBX defined by PBX_IP else { xlog("L_INFO", "DOMAINROUTING Routing to Single Endpoint Gateway\n"); $du = "sip:" + $avp(domain_pbx_ip); route(SET_CALLDST_INFO); route(CHECK_CALL_LIMIT); route(SET_CALL_DLGCFG); } xlog("L_INFO", "DOMAINROUTING should be routed to $rd:$rp\n"); route(RELAY); exit; } #Route to one PBX using an algorithm with External Authentication #(aka We are acting as a Registration and Location Server) else if (isflagset(FLT_DOMAINROUTING) && isflagset(FLT_EXTERNAL_AUTH) && !is_method("REGISTER")) { if (strempty($avp(domain_dispatcher_set_id))) { send_reply("404", "Destination Not Found"); exit; } else { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); xlog("L_INFO", "DOMAINROUTING Routing for $fd via dispatcher set $avp(dispatcher_setid)\n"); } # set the algorithm to load balancing if not set if (strempty($avp(domain_dispatcher_alg))) { $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; } else { $avp(dispatcher_alg) = $avp(domain_dispatcher_alg); } #!ifdef WITH_MSTEAMS # check if destination is msteams if ($avp(domain_pbx_type) == "3") { setbflag(FLB_DST_MSTEAMS); $avp(dst_msteams_domain) = $fd; # contact must match the domain of the TLS cert (i.e. the CN) remove_hf("Contact"); append_hf("Contact: \r\n"); xlog("L_DBG", "Changed contact to $ct\n"); # prevent MSTeams from sending REFER requests if ((int)$sel(cfg_get.server.msteams_disable_refer)) { route(REMOVE_REFER); } route(DISPATCHER_SELECT); route(RELAY); exit; } #!endif # routing to PBX destination set route(ENRICH_SIPHEADER); route(DISPATCHER_SELECT); route(RELAY); exit; } #!ifdef WITH_DROUTE ###################################### # From Carrier # ###################################### # Check if this is coming from carrier if (isbflagset(FLB_SRC_CARRIER)) { xlog("L_INFO", "The call coming from $si is from a carrier\n"); # Enrich inbound calls from carriers route(ENRICH_CARRIER_INBOUND); # Rewrite R-URI if it contains a transport if ($ru =~ ".*;transport.*") { if ($rU != $null ) { $ru = "sip:" + $rU + "@" + $rd + ":" + $rp; xlog("L_INFO", "Normalizing R-URI to $ru\n"); } } # Route to PBX xlog("L_DBG", "DROUTING Logic for routing to PBX\n"); append_hf("P-hint: inbound\r\n"); $avp(calltype) = "inbound"; # don't overwrite fwding info if we already set it if (!isflagset(FLT_FAILOVER)) { route(SET_CALLFWD_INFO); } # don't overwrite dr_groupid if this is n'th time executing this route if ($avp(dr_groupid) == $null) { # first time executing, save the dialed DID for forwarding features $avp(dr_saved_rU) = $rU; # check for hard fwd, then set did and dr_groupid accordingly if ($avp(hardfwdinfo) != $null) { # allow DID to be unchanged if (!strempty($(avp(hardfwdinfo){s.select,0,,}))) { $rU = $(avp(hardfwdinfo){s.select,0,,}); } $avp(dr_groupid) = $(avp(hardfwdinfo){s.select,1,,}{s.int}); } # otherwise we are using inbound mapping rules else { $avp(dr_groupid) = FLT_INBOUND; } } # try routing based on rules in dr_rules table if (!do_routing($avp(dr_groupid))) { xlog("L_WARN", "RURI routing failed, trying to route on the To header\n"); $rU = $tU; # otherwise as a last ditch effort try to route based on To header if (!do_routing($avp(dr_groupid))) { # No rules defined for the phone number xlog("L_WARN", "No rules defined for $rU coming from this carrier endpoint: $si\n"); sl_reply("500", "No rules defined for number"); exit; } } # found a match, set du to match what drouting selected $du = $(ru{uri.duri}); route(MAINTMODE_CHECK); route(SET_CALLDST_INFO); route(CHECK_CALL_LIMIT); route(SET_CALL_DLGCFG); # handle clientside NAT for the rest of the dialog # TODO: the other NAT checks are all over the place, we should simplify and aggregate them if (nat_uac_test("64")) { add_contact_alias(); } # check if routing via dispatcher if ($avp(dispatcher_setid) != $null && $avp(lb_enabled)) { # dispatcher algo is always weighted here $avp(dispatcher_alg) = DSTALG_RELATIVE_WEIGHT; route(DISPATCHER_SELECT_LB); } #!ifdef WITH_MSTEAMS # Check if routing to MSTeams if ($avp(dr_attrs) != $null) { if ($(avp(dst_msteams_domain){s.len}) > 0) { setbflag(FLB_DST_MSTEAMS); # TODO: review in v0.80, do we need to change any of these other headers? #if (!subst_hf("Remote-Party-ID", "/^(.*.*)$/\1$avp(dst_msteams_domain)\3/", "f")) { # xlog("L_ERR", "failed updating Remote-Party-ID\n"); #} # #$fd = $avp(dst_msteams_domain); #$td = $avp(dst_msteams_domain); # drouting does not have an easy way to update the transport on a matched address # not wasting the time to go through and integrate it into the dr_attrs column $du = "sip:" + $rd + ":" + $rp + ";transport=tls"; # contact must match the domain of the TLS cert (i.e. the CN) remove_hf("Contact"); append_hf("Contact: \r\n"); xlog("L_DBG", "Changed contact to $ct\n"); #Prevent MSTeams from sending REFER requests if ((int)$sel(cfg_get.server.msteams_disable_refer)) { route(REMOVE_REFER); } } } #!endif # Set INVITE max lifetime to ensure Primary and Secondary PBX server feature works. t_set_fr((int)$sel(cfg_get.server.pbx_invite_timeout_aftertry), (int)$sel(cfg_get.server.pbx_invite_timeout)); #route(SET_CALLID_INBOUND_ENDPOINT_MAP); route(RELAY); exit; } else if (isbflagset(FLB_SRC_PBX) || isflagset(FLT_PBX_AUTH) || isbflagset(FLB_SRC_MSTEAMS)) { xlog("L_INFO", "The call coming from $si will be routed to carrier groups via drouting\n"); # Route to Carrier append_hf("P-hint: outbound\r\n"); $avp(calltype) = "outbound"; #!ifdef WITH_LCR # LCR Routing # - route based on from prefix and to prefix # - match selection is similar to dRouting module from longest to shortest match # Logic Summary: # 1. iterate through htable matching entries starting with prefixes # 2. find diff between match and lookup (must be absolute value) # 3. if diff is less than previous overwrite match # 4. if a match is present attempt to set carrier group and relay # TODO: # we could store and iterate through all matches if we use dispatcher instead # this would allow failover in LCR Routing to shorter prefixes (if we wanted that) $var(lookup) = $(fU{s.unescape.user}) + "-" + $(tU{s.unescape.user}); $avp(lcr_match_group) = $null; $var(lcr_match_diff) = 1000; $var(diff) = 1000; sht_iterator_start("iter", "tofromprefix"); while(sht_iterator_next("iter")) { $var(regex) = $(shtitkey(iter){s.select,0,-}{re.subst,/(\+|\*|\#)/\\\1/g}) + "([0-9])*-" + $(shtitkey(iter){s.select,-1,-}{re.subst,/(\+|\*|\#)/\\\1/g}) + "([0-9])*"; if ($var(lookup) =~ $var(regex)) { xlog("L_DBG", "LCR match on $shtitkey(iter)\n"); $var(diff) = $(var(lookup){s.len}) - $(shtitkey(iter){s.len}); # bitwise absolute value: ( mask = n>>31; (mask^n) - mask ) # we are assuming 32 bit integers $var(mask) = $var(diff) >> 31; $var(diff) = ($var(mask) ^ $var(diff)) - $var(mask); if ($var(diff) < $var(lcr_match_diff)) { xlog("L_DBG", "LCR prefix closer match diff=$var(diff)\n"); $avp(lcr_match_group) = $shtitval(iter); $var(lcr_match_diff) = $var(diff); } } } sht_iterator_end("iter"); if ($avp(lcr_match_group) > 0) { $avp(carrier_groupid) = $avp(lcr_match_group); } else { $avp(carrier_groupid) = FLT_OUTBOUND; } #!else $avp(carrier_groupid) = FLT_OUTBOUND; #!endif if (do_routing($avp(carrier_groupid))) { # set du to match what drouting selected $du = $(ru{uri.duri}); route(SET_CALLDST_INFO); route(CHECK_CALL_LIMIT); route(SET_CALL_DLGCFG); # Checking if the carrier is using username/password auth # sets the req uri, dst uri and auth_* avp's to the values matched by user if (uac_reg_request_to($avp(dst_gwgroupid), 0)) { xlog("L_INFO", "Found remote user [$fU] with username: $avp(auser)\n"); uac_replace_from("\"$fU\"", "sip:$avp(auser)@$rd"); uac_replace_to("sip:$tU@$rd"); subst_hf("Contact", "/(.*)?<(sips?):([0-9]+)@([^:]+)(:[0-9]{1,5})?(;.*)?>(.*)?/<\2:\3@UAC_REG_ADDR:$Rp\6>\7/", "f"); remove_hf("P-Asserted-Identity"); append_hf("P-Asserted-Identity: \r\n"); } else { subst_hf("Contact", "/(.*)?<(sips?):([0-9]+)@([^:]+)(:[0-9]{1,5})?(;.*)?>(.*)?/<\2:\3@EXTERNAL_IP_ADDR:$Rp\6>\7/", "f"); remove_hf("P-Asserted-Identity"); append_hf("P-Asserted-Identity: \r\n"); } msg_apply_changes(); add_contact_alias(); route(ENRICH_CARRIER_OUTBOUND); # check if routing via dispatcher if ($avp(dispatcher_setid) != $null && $avp(lb_enabled)) { # dispatcher algo is always weighted here $avp(dispatcher_alg) = DSTALG_RELATIVE_WEIGHT; route(DISPATCHER_SELECT_LB); } route(RELAY); exit; } # No rules defined for the phone number else { xlog("L_WARN", "No rules defined for $fu going to $tu\n"); sl_reply("500", "No rules defined for number"); } } else { sl_send_reply("407", "Proxy Authentication Required. Add the PBX or Carrier IP using GUI"); } #!endif } route[NEXTHOP_FAILOVER] { # check for failover fwd, then set did and dr_groupid accordingly if (($avp(failfwdinfo) != $null) && !isflagset(FLT_FAILOVER)) { # flag to make sure we don't loop endlessly setflag(FLT_FAILOVER); # reset DID if user did not explicitly ask to overwrite it if (strempty($(avp(failfwdinfo){s.select,0,,}))) { $rU = $avp(dr_saved_rU); } else { $rU = $(avp(failfwdinfo){s.select,0,,}); } $avp(dr_groupid) = $(avp(failfwdinfo){s.select,1,,}{s.int}); # go back through NEXTHOP to allow our standard routing checks to run for this dr_group route(NEXTHOP); # we successfully routed via NEXTHOP, return true if we did not exit return 1; } # return false if we did not route the call return -1; } # MaintMode Check - recursive function for checking if a number is in maintmode route[MAINTMODE_CHECK] { xlog("L_DBG", "The request domain $rd before maintmode check\n"); if ($sht(maintmode=>$rd)!=$null) { xlog("L_DBG", "request $rd is in maintenance mode\n"); # The selected endpoint is in maintenance mode, try next endpoint # If there is only one endpoint then immediately return Service not Available # Otherwise, select the next gateway and see if it is in maintenance mode if (!use_next_gw()) { xlog("L_DBG", "request $rd has no other gateways available\n"); sl_send_reply("503", "Service not available"); exit; } # set du to match what drouting selected $du = $(ru{uri.duri}); route(MAINTMODE_CHECK); } } #!ifdef WITH_TRANSNEXUS import_file "transnexus.cfg" #!endif #!ifdef WITH_STIRSHAKEN import_file "stir-shaken.cfg" #!endif # TeleBlock routing route[TELEBLOCK] { if (!isbflagset(FLB_SRC_PBX)) { xlog("L_DBG", "source is not a pbx, skipping teleblock blacklist check\n"); return; } # TODO: This should be dynamic # Only route to teleblock if User-to-User header is present # if (!is_present_hf("User-to-User")) { # xlog("L_DBG", "User-to-User header not found\n"); # return; # } # save the requested route selections before overwriting for teleblock $avp(tb_saved_fu) = $fu; $avp(tb_saved_ru) = $ru; $avp(tb_saved_du) = $du; # Change source address to this proxy server $fu = "sip:" + $fU + "@" + $Ri + ":" + $Rp; # Send Invite to TeleBlock with header fields: # Number == To Username # CPN == From Username # BTN == Billing Number (optional) # Zipcode == US Postal Code (optional) # refkey == Record ID (optional) $ru = "sip:" + $rU + "@" + $sel(cfg_get.teleblock.gw_ip) + ":" + $sel(cfg_get.teleblock.gw_port); $du = $(ru{uri.duri}); xlog("L_DBG", "Forwarding to teleblock: fu=$fu ru=$ru du=$du\n"); # set failure route if (is_method("INVITE")) { t_on_failure("TELEBLOCK_FAILURE"); } } failure_route[TELEBLOCK_FAILURE] { if (t_is_canceled()) { exit; } xlog("L_DBG", "Processing reply for: $rU\n"); # Check if a media server is setup for teleblock if (strempty($sel(cfg_get.teleblock.media_ip)) || strempty($sel(cfg_get.teleblock.media_port))) { $avp(s:teleblock_media_enabled) = "0"; } else { $avp(s:teleblock_media_enabled) = "1"; } # interpret teleblock response if (t_check_status("499")) { $fu = $avp(tb_saved_fu); $ru = $avp(tb_saved_ru); $du = $avp(tb_saved_du); t_relay(); exit; } xlog("L_DBG", "Relaying to: $sel(cfg_get.teleblock.media_ip):$sel(cfg_get.teleblock.media_port)\n"); if (t_check_status("403|433")) { if ($avp(s:teleblock_media_enabled) == "1") { # make sure media server can route back to kamailio route(SET_RECORD_ROUTE); $ru = "sip:" + $rU + "@" + $sel(cfg_get.teleblock.media_ip) + ":" + $sel(cfg_get.teleblock.media_port); if (!t_relay()) { t_reply("403", "Do-Not-Contact"); } } else { if (!t_relay()) { t_reply("403", "Do-Not-Contact"); } } } else { if ($avp(s:teleblock_media_enabled) == "1") { # make sure media server can route back to kamailio route(SET_RECORD_ROUTE); $ru = "sip:" + $rU + "@" + $sel(cfg_get.teleblock.media_ip) + ":" + $sel(cfg_get.teleblock.media_port); if (!t_relay()) { t_reply("500", "Connection Failure"); } } } exit; } # Wrapper for relaying requests route[RELAY] { if (!has_totag()) { # update the signalling / media settings per the destination endpoint route(SET_DST_SIGNALLING); xlog("777777777777777777777i $avp(dst_signalling) du=$du $(ru{uri.duri})"); route(SET_DST_MEDIA); } # check for serverside NAT route(SERVERNATDETECT); # only set Record-Route for new dialog creating requests if (!has_totag()) { route(SET_RECORD_ROUTE); } # handle STIR/SHAKEN #!ifdef WITH_TRANSNEXUS if ((int)$sel(cfg_get.transnexus.authservice_enabled)) { route(TRANSNEXUS_OUTBOUND); } #!endif #!ifdef WITH_STIRSHAKEN if ((int)$sel(cfg_get.stir_shaken.stir_shaken_enabled)) { route(STIRSHAKEN_OUTBOUND); } #!endif # Check TeleBlock Blacklist #!ifdef WITH_TELEBLOCK if ((int)$sel(cfg_get.teleblock.gw_enabled)) { route(TELEBLOCK); } #!endif # enable additional event routes for forwarded requests # - serial forking, RTP relaying handling, a.s.o. if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) { if (!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH"); } if (is_method("INVITE|SUBSCRIBE|UPDATE")) { if (!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY"); } if (is_method("INVITE")) { if (!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE"); } # do accounting only for INVITE's if (is_method("NOTIFY")) { # t_on_reply("MANAGE_REPLY"); xlog("L_INFO", "NOTIFY ON REPLY ROUTE ru=$ru du=$du fu=$fu tu=$tu\n"); } if (is_method("INVITE")) { setflag(FLT_ACC); } # Now modify the $ru to reflect the public IP (after NAT traversal) # $ru = "sip:" + $du + "@" + $external_ip; xlog("L_INFO", "Attempting to route call. ru=$ru du=$du fu=$fu tu=$tu\n"); if (!t_relay()) { sl_reply_error(); } exit; } route[CHECK_CALL_LIMIT] { #!ifdef WITH_CALL_SETTINGS # Manage call limits for gwgroups xlog("L_DBG", "dst_gwtype: $avp(dst_gwtype), dst_gwid: $avp(dst_gwid), dst_gwgroupid: $avp(dst_gwgroupid) src_gwtype: $avp(src_gwtype), src_gwid: $avp(src_gwid), src_gwgroupid: $avp(src_gwgroupid)\n"); # if src and dst gwgroup is the same there is no need to check both if ($avp(src_gwgroupid) != $avp(dst_gwgroupid)) { if (!strempty($xavu(src_call_settings=>limit))) { $var(num_calls) = $sht(concurrent_calls=>$avp(src_gwgroupid)); if ($var(num_calls) >= $xavu(src_call_settings=>limit)) { sl_reply("480", "Call Limit Exceeded"); $avp(notification_type) = NOTIFICATION_OVERLIMIT; $avp(notification_gwid) = $avp(src_gwid); $avp(notification_gwgroupid) = $avp(src_gwgroupid); route(SEND_NOTIFICATION); exit; } } } if (!strempty($xavu(dst_call_settings=>limit))) { $var(num_calls) = $sht(concurrent_calls=>$avp(dst_gwgroupid)); if ($var(num_calls) >= $xavu(dst_call_settings=>limit)) { sl_reply("480", "Call Limit Exceeded"); $avp(notification_type) = NOTIFICATION_OVERLIMIT; $avp(notification_gwid) = $avp(dst_gwid); $avp(notification_gwgroupid) = $avp(dst_gwgroupid); route(SEND_NOTIFICATION); exit; } } #!endif return; } # Per SIP request initial checks route[REQINIT] { # reusable flag denoting the source is an allowed address # checking if the flag is already set is a simple optimization for follow-on messages in the transaction if (!isflagset(FLT_SRC_ALLOWED)) { if (is_myself("$si")) { setbflag(FLB_SRC_SELF); setflag(FLT_SRC_ALLOWED); } else if (allow_source_address_group()) { setflag(FLT_SRC_ALLOWED); } } #!ifdef WITH_ANTIFLOOD # if not from self then do flood detection on the source IP if (src_ip=='185.61.116.61'){ xlog("L_INFO", "Adresse IP autorisée"); } else { if (!isbflagset(FLB_SRC_SELF)) { if ($sht(ipban=>$si) != $null) { # refreshing ip ban pike_check_req(); xlog("L_INFO", "pike blocking request with source address $si:$sp\n"); exit; } if (!pike_check_req()) { # new ip ban xlog("L_ALERT", "pike banning requests from source address $si:$sp\n"); $sht(ipban=>$si) = 1; exit; } } } #!endif if (!mf_process_maxfwd_header("10")) { sl_send_reply("483", "Too Many Hops"); exit; } # Only reply to option messages if the endpoint or the carrier is defined if (is_method("OPTIONS") && isflagset(FLT_SRC_ALLOWED)) { sl_send_reply("200", "Keepalive"); exit; } if (!sanity_check("1511", "7")) { xlog("L_WARN", "Malformed SIP message from source address $si:$sp\n"); exit; } # TODO: re-evaluate RURI validation # request with no Username in RURI if coming from an unknown address #if ($rU == $null && is_method("INVITE")) { # sl_send_reply("484","Address Incomplete"); # exit; #} # set a flag denoting the source address type # checking if one of the flags is already set is a simple optimization for follow-on messages in the branch if (!isbflagset(FLB_SRC_SELF) && !isbflagset(FLB_SRC_PBX) && !isbflagset(FLB_SRC_CARRIER) && !isbflagset(FLB_SRC_MSTEAMS)) { if (allow_source_address(FLT_PBX)) { setbflag(FLB_SRC_PBX); } else if (allow_source_address(FLT_CARRIER)) { setbflag(FLB_SRC_CARRIER); } else if (allow_source_address(FLT_MSTEAMS)) { setbflag(FLB_SRC_MSTEAMS); } else if (is_myself("$si")) { setbflag(FLB_SRC_SELF); } } # set a flag denoting the type of UAC if ($pr == "ws" || $pr == "wss") { setflag(FLT_SRC_WS); } else { setflag(FLT_SRC_SIP); } } # Handle requests within SIP dialogs route[WITHINDLG] { if (!has_totag()) { return; } route(MANAGE_ONHOLD); # Handling Session Timers from Carriers to MSTeams if (!isbflagset(FLB_SRC_MSTEAMS) && $rd =~ "pstnhub.microsoft.com") { setbflag(FLB_DST_MSTEAMS); } # Handling onhold, but could be used for more if (is_method("INVITE") && $hdr(User-Agent) =~ "Microsoft.PSTNHub" && $avp(sdp_media_direction) == "inactive") { setbflag(FLB_SRC_MSTEAMS_ONHOLD); } # sequential request withing a dialog should # take the path determined by record-routing if (loose_route_mode("1")) { route(DLGURI); if (is_method("BYE")) { # do accounting even if the transaction fails # we grab the gwgroup info from the initiating INVITE's record sql_xquery("kam", "select src_gwgroupid, dst_gwgroupid from acc where callid = '$ci' and method = 'INVITE' limit 1", "rb"); if (!strempty($xavp(rb=>src_gwgroupid))) { $avp(src_gwgroupid) = $xavp(rb=>src_gwgroupid); $avp(dst_gwgroupid) = $xavp(rb=>dst_gwgroupid); } sql_result_free("rb"); setflag(FLT_ACC); setflag(FLT_ACCFAILED); route(PBX_TO_ENDPOINT_LOOKUP); } else if (is_method("ACK")) { # ACK is forwarded statelessy xlog("L_DBG", "In Loose Method is: ACK\n"); route(NATMANAGE); route(PBX_TO_ENDPOINT_LOOKUP); } else if (is_method("UPDATE")) { route(PBX_TO_ENDPOINT_LOOKUP); } route(RELAY); exit; } #!ifdef WITH_MSTEAMS # when handling double record route from msteams->dsip->pbx rewrite the destination based on the initial INVITE if (isbflagset(FLB_SRC_SELF) && allow_address(FLT_MSTEAMS, "$(dlg_var(dst_uri){uri.host})", "$(dlg_var(dst_uri){uri.port})")) { xlog("L_INFO", "handling msteams double record route, rewriting ru from $ru to $dlg_var(src_uri)\n"); $ru = $dlg_var(src_uri); } #!endif if (is_method("SUBSCRIBE") && uri == myself) { # in-dialog subscribe requests route(PRESENCE); exit; } if (is_method("ACK|UPDATE|INVITE|BYE|PRACK")) { route(DLGURI); # Set Accounting flags for strict-routing transactions if (is_method("BYE")) { sql_xquery("kam", "select src_gwgroupid, dst_gwgroupid from acc where callid = '$ci' and method = 'INVITE' limit 1", "rb"); if (!strempty($xavp(rb=>src_gwgroupid))) { $avp(src_gwgroupid) = $xavp(rb=>src_gwgroupid); $avp(dst_gwgroupid) = $xavp(rb=>dst_gwgroupid); } sql_result_free("rb"); setflag(FLT_ACC); setflag(FLT_ACCFAILED); } # if the message has a transaction try strict routing if (t_check_trans()) { route(RELAY); exit; } sl_send_reply("481", "Call/Transaction Does Not Exist"); exit; } sl_send_reply("604", "Does Not Exist Anywhere"); exit; } # Handle on hold route[MANAGE_ONHOLD] { if (!is_method("INVITE")) { return; } # handle sdp media direction for SBC's/proxies that require on reply # rtpengine by default will use a=sendrecv if valid sdp if (has_body("application/sdp")) { $avp(sdp_media_direction) = $null; if (search_body("^a=inactive.*")) { $avp(sdp_media_direction) = "inactive"; } else if (search_body("^a=recvonly.*")) { $avp(sdp_media_direction) = "recvonly"; } else if (search_body("^a=sendonly.*")) { $avp(sdp_media_direction) = "sendonly"; } } } # Handle SIP registrations route[REGISTRAR] { if (!is_method("REGISTER")) { return; } # Set the device type if a WS device # TODO: the type of UAC won't change in the middle of a transaction, marked for review/removal if (isflagset(FLT_SRC_WS)) { setbflag(FLB_WS_DEVICE); } # TODO: why are we setting clientside NAT here if serverside NAT is enabled? #!ifdef WITH_SERVERNAT setbflag(FLB_NATB); #!endif #!ifdef WITH_SERVERNAT6 setbflag(FLB_NATB); #!endif #!ifdef WITH_NAT # do SIP NAT pinging via OPTIONS messages setbflag(FLB_NATSIPPING); #!endif if (isflagset(FLT_PBX_AUTH)) { # Handle Register Event - We are now acting as a REGISTRAR. if (!save("location")) { sl_reply_error(); } # TODO: can we move these to in memory changes # Update dr_gateways and dr_gw_lists accordingly if ($sel(contact.expires) == "0") { xlog("L_DBG", "received an unregister request\n"); if ($avp(src_gwid) != $null) { xlog("L_DBG", "removing registration address $var(received_addr) from gateways for gwgroup $avp(src_gwgroupid)\n"); sql_query("kam", "DELETE FROM dr_gateways WHERE gwid='$avp(src_gwid)'"); sql_query("kam", "UPDATE dr_gw_lists SET gwlist=REGEXP_REPLACE(REGEXP_REPLACE(gwlist, '([,;])?$avp(src_gwid)', ''), '^([,;])', '') WHERE id=$avp(src_gwgroupid)"); jsonrpc_exec('{"jsonrpc": "2.0", "method": "drouting.reload", "id": 1}'); } } else { xlog("L_DBG", "received an register request\n"); if ($avp(src_gwid) == $null) { xlog("L_DBG", "adding registration address $var(received_addr) to gateways for gwgroup $avp(src_gwgroupid)\n"); sql_query("kam", "INSERT INTO dr_gateways(type,address,attrs,description) VALUES ($avp(src_gwtype),'$var(received_addr)',',,,proxy,proxy','name:autoregister,type:$avp(src_gwtype),gwgroup:$avp(src_gwgroupid)');"); sql_query("kam", "UPDATE dr_gw_lists SET gwlist=REGEXP_REPLACE(CONCAT(gwlist,',',(SELECT MAX(gwid) FROM dr_gateways)), '^,', '') WHERE id=$avp(src_gwgroupid)"); jsonrpc_exec('{"jsonrpc": "2.0", "method": "drouting.reload", "id": 1}'); } } exit; } if (isflagset(FLT_DOMAINROUTING) && !isflagset(FLT_EXTERNAL_AUTH)) { # Save the location, but DON'T send a 200 reply back. # Let the upstream PBX authenticate the UAC (aka endpoint) if (!save("location", "0x02")) { sl_reply_error(); } # Keep the origin request domain $var(rd_orig) = $rd; # Route to the endpoints defined in the Endpoint group (using dispatcher) if ($avp(domain_pbx_type) == "2") { #Forward the registration onto one of the servers in the cluster if (!strempty($avp(domain_dispatcher_set_id))) { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); if (!strempty($avp(domain_dispatcher_reg_alg))) { # Set the registration algoritm $avp(dispatcher_alg) = $avp(domain_dispatcher_reg_alg); } else { # Set the dispatcher algorthim to round robin by default $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; } if (!strempty($sht(pass_thru_auth=>$ci))) { $du = $sht(pass_thru_auth=>$ci); xlog("L_INFO", "DOMAINROUTING last du was $du\n"); } else { route(DISPATCHER_SELECT); xlog("L_INFO", "DOMAINROUTING Routing to Endpoint Gateway List $avp(dispatcher_setid)\n"); } } } else { # Grab the value of the avp that contains the domain_pbx_ip. # This is where requests for that domain should be routed $var(rd) = $(avp(domain_pbx_ip){s.select,0,:}); $var(rp) = $(avp(domain_pbx_ip){s.select,1,:}); $rd = $var(rd); $rp = $var(rp); } # Rewrite Contact based on the domain being routed to #sips:2000@df7jal23ls0d.invalid;rtcweb-breaker=no;transport=wss if (subst_hf("Contact", "/(.*)?<(sips?):([0-9]+)@(.*)/<\2:\3@$fd>/", "f")) { xlog("L_INFO", "changed contact to match From domain\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes to message\n"); } } if (isbflagset(FLB_WS_DEVICE)) { add_path(); } else { #Add the Path header for SIP UAC's - so that we know how to route back add_path_received($fU); } # Store the pbx ip to domain mapping so that SIP messages from the PBX can be rewritten if (!is_ip($(avp(domain_pbx_ip){s.select,0,:}))) { if (dns_query($(avp(domain_pbx_ip){s.select,0,:}), "xyz")) { $var(i) = 0; while ($var(i) < $dns(xyz=>count)) { $sht(pass_thru_auth=>$dns(xyz=>addr[$var(i)])) = $var(rd_orig); $var(i) = $var(i) + 1; } } } else { $sht(pass_thru_auth=>$(avp(domain_pbx_ip){s.select,0,:})) = $var(rd_orig); } #We are going to pass this request on to the backend server route(RELAY); exit; } else if (isflagset(FLT_DOMAINROUTING) && isflagset(FLT_EXTERNAL_AUTH)) { if (!save("location")) { sl_reply_error(); } exit; } #!ifdef WITH_PUSH if (($hdr(Expires) != "0") || !($hdr(Contact) =~ "expires=0") && ($sht(push=>join::$tU@td) != $null)) { xlog("L_INFO", "[REGISTER] [PUSH] about to un-suspend transaction rm=$rm ru=$ru tU=$tU td=$td \n"); route(JOIN); } #!endif # #Forward the registration onto one of the servers in the cluster # if (!strempty($avp(domain_dispatcher_set_id))) { # $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); # if (!strempty($avp(domain_dispatcher_reg_alg))) { # #Set the registration algoritm # $avp(dispatcher_alg) = $avp(domain_dispatcher_reg_alg); # } # else { # #Set the dispatcher algorthim to round robin by default # $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; # } # # route(DISPATCHER_SELECT); # route(RELAY); # } # exit; #} } # Dispatcher request load balancing (we don't route here) route[DISPATCHER_SELECT] { # round robin dispatching on dispatcher gateways set if (!ds_select_dst($avp(dispatcher_setid), $avp(dispatcher_alg))) { xlog("L_ERR", "no destination selected for domain: $fd\n"); send_reply("404", "Destination Not Found"); exit; } if (strempty($xavp(dispatcher_dst=>uri)) && $avp(dispatcher_alg) == DSTALG_PARALLEL_FORKING) { xlog("L_DBG", "sending to multiple servers in parallel\n"); } else if (!strempty($xavp(dispatcher_dst=>uri))) { xlog("L_DBG", "dispatcher selected $xavp(dispatcher_dst=>uri)\n"); } t_on_failure("DISPATCHER_NEXT"); return; } failure_route[DISPATCHER_NEXT] { # try next destionations in failure route if (t_is_canceled()) { exit; } if (t_check_status("401|407")) { xlog("L_INFO", "DOMAINROUTING 401 or 407 and the du was $du)\n"); $sht(pass_thru_auth=>$ci) = $du; return; } # next DST - only for 500 or local timeout if (t_check_status("4[0-9][2-6,8-9]|5[0-9][0-9]") or (t_branch_timeout() and !t_branch_replied())) { if (ds_next_dst()) { xlog("L_DBG", "dispatcher selected $xavp(dispatcher_dst=>uri)\n"); t_on_failure("DISPATCHER_NEXT"); route(RELAY); return; } else { # Drop the replies if this is a REGISTER if (is_method('REGISTER')) { t_drop_replies(); t_reply("401", "Unauthorized"); } } } } route[DISPATCHER_SELECT_LB] { # dispatch using the selected algorithm for this endpoint group if (!ds_select_dst($avp(dispatcher_setid), $avp(dispatcher_alg))) { xlog("L_WARN", "no destination selected for dispatcher set $avp(dispatcher_setid)\n"); # check failover forwarding, otherwise reply with an error if (!route(NEXTHOP_FAILOVER)) { send_reply("600", "No Destination Found"); exit; } # if for some reason the failover forwarding was successful but did not exit then we return to calling routine return; } # rewrite domain / port in request URIs $ru = $rz + ":" + $rU + "@" + $dd + ":" + $dp; $tu = $rz + ":" + $rU + "@" + $dd + ":" + $dp; # we have to flush the buffer in case other changes are/were made msg_apply_changes(); t_on_failure("DISPATCHER_NEXT_LB"); return; } failure_route[DISPATCHER_NEXT_LB] { # try next destinations in failure route if (t_is_canceled()) { exit; } # next DST if (t_check_status("4[0-9][1-6,7-9]|5[0-9][0-9]") || (t_branch_timeout() && !t_branch_replied())) { if (ds_next_dst()) { xlog("L_DBG", "trying next destination\n"); # rewrite domain / port in request URIs $ru = $rz + ":" + $rU + "@" + $dd + ":" + $dp; $tu = $rz + ":" + $rU + "@" + $dd + ":" + $dp; t_on_failure("DISPATCHER_NEXT_LB"); route(RELAY); } else { xlog("L_INFO", "all destinations unavailable for dispatcher set $avp(dispatcher_setid)\n"); # check failover forwarding, otherwise reply with an error if (!route(NEXTHOP_FAILOVER)) { send_reply("600", "No Destination Available"); exit; } # if for some reason the failover forwarding was successful but did not exit then we return to calling routine return; } } exit; } # User location service route[LOCATION] { xlog("L_INFO", "In the location route ru=$rU rd=$rd du=$du fu=$fu tu=$tu\n"); $rd = "sipis.prosoluce.net"; # Return immediately if the source address is not a PBX. Only PBX's should be trying to route to endpoints if (!isbflagset(FLB_SRC_PBX)) { return; } # Emergency / N11 services should return immediately so that it can be routed to a carrier # ITU officially recognizes 911 (NA) and 112 (EU) as the international emergency numbers # However 999 (UK) and 000 (AU) are still commonly used # Emergency Numbers Overview: https://en.wikipedia.org/wiki/Emergency_telephone_number # N11 Ref: https://nationalnanpa.com/number_resource_info/n11_codes.html # On 2020-Jul-16 the FCC also adopted 988 as an N11 number # 988 Adoption Refs: https://www.fcc.gov/document/fcc-designates-988-national-suicide-prevention-lifeline if ($rU =~ $sel(cfg_get.server.emergency_numbers)) { return; } # Return if the rU is more then local calling maximum digits for the initiating PBX if ($(rU{s.len}) > $sel(cfg_get.server.pbx_max_local_digits)) return; # If request is coming from a FreePBX or Asterisk server use the Pass-Thru htable if ($hdr(User-Agent) =~ "FPBX.*|Asterisk.*") { if ($sht(pass_thru_auth=>$si) != "") { $rd = $sht(pass_thru_auth=>$si); } } # Logic to to deal with a broken PATH implmentation in Asterisk PJSIP xlog("L_INFO", "In the location route ASTERISK $rU $rd\n"); # $var(asterisk_domain) = $(ru{uri.param,x-ast-orig-host}); # $var(asterisk_domain) = $(var(asterisk_domain){re.subst,/^(.*):(.*)/\1/}); #if ($var(asterisk_domain) != "") { # $rd = $var(asterisk_domain); # if (!msg_apply_changes()) { # xlog("L_ERR", "failed applying changes to message\n"); # } # xlog("L_INFO", "routing message for domain $var(asterisk_domain) to $rU@$rd\n"); # } $avp(oexten) = $rU; # Lookup the location of the endpoint by username@request_domain if (!lookup("location","sip:$rU@$rd")) { #!ifdef WITH_PUSH xlog("L_INFO", " In the route[LOCATION] [PUSH] logic."); send_reply("100", "Suspending"); route(SENDPUSH); route(SUSPEND); #!endif # Lookup the location of the endpoint by username@from_domain if (!lookup("location","sip:$rU@$fd")) { # Check if coming from a Zoiper Push Server # If so, the username for the extension is part of the Route header, grab it $var(Route) = @hf_value.route.uri; $var(user) = $(var(Route){uri.user}); xlog("L_DBG", "$var(Route) / sip:$var(user)@$fd\n"); if (!lookup("location", "sip:$var(user)@$fd")) { $var(rc) = $rc; route(TOVOICEMAIL); t_newtran(); switch ($var(rc)) { case -1: case -3: send_reply("404", "Not Found"); exit; case -2: send_reply("405", "Method Not Allowed"); exit; } } xlog("L_INFO", "ru: $ru, nh(u): $nh(u), WS:$var(WS_DEVICE)\n"); } } # when routing via usrloc, log the missed calls also if (is_method("INVITE")) { setflag(FLT_ACCMISSED); # rtpengine_offer(); xlog("88888888888888888888888888888"); # t_relay(); } #Set the INVITE timeout for sending calls to invites t_set_fr(120000,10000); route(RELAY); exit; } #!ifdef WITH_PUSH # Suspend Transaction route[SUSPEND] { xlog("L_INFO", "In the [PUSH] route[SUSPEND] logic\n"); t_set_fr(30000); if (!t_suspend()) { xlog("[SUSPEND] [PUSH] failed suspending trasaction [$T(id_index):$T(id_label)]\n"); send_reply("501", "Unknown destination"); exit; } xlog("L_INFO", "[SUSPEND] [PUSH] suspended transaction [$T(id_index):$T(id_label)] $fU => $rU@$rd\n"); $sht(push=>join::$rU@$rd) = "" + $T(id_index) + ":" + $T(id_label); xlog("L_INFO", "[SUSPEND] [PUSH] suspended htable key value [$sht(push=>join::$rU@$rd)]\n"); exit; } # Logic to invoke push route[SENDPUSH] { xlog("L_INFO", "[SENDPUSH] Sending the push notification\n"); #rabbitmq_publish("kamailio", "routing_key", "application/json", "$avp(json_request)"); #$var(luaret) = 0; #if(lua_runstring("do_push([[$hdr(X-VxTo)]], [[$tU]], [[$hdr(X-VxFrom)]], [[$fU]], [[$ci]])")<0){ # send_reply("501", "No link to destination"); # exit; #} return; } # Suspend route[JOIN] { xlog("L_INFO", " In the [PUSH] route[JOIN] logic."); $var(index)=(int) $(sht(push=>join::$tU@$td){s.select,0,:}); $var(label)=(int) $(sht(push=>join::$tU@$td){s.select,1,:}); xlog("L_INFO", "[JOIN] [PUSH] suspend $var(index) $var(label)"); t_set_fr(30000); t_continue("$var(index)", "$var(label)", "RESUME"); } # Resume route[RESUME] { xlog("L_INFO", " In the [PUSH] route[RESUME] logic."); xlog("L_INFO","[RESUME] [PUSH] suspend **before lookup** rm=$rm ru=$rU rd=$rd du=$du \n"); if (!lookup("location","sip:$rU@$rd")) { switch ($retcode) { case 1: xlog("L_INFO","[RESUME] suspend **found** rm=$rm ru=$rU rd=$rd du=$du \n"); case -1: case -3: sl_send_reply("404", "Not Found"); exit; break; case -2: sl_send_reply("405", "Not Found"); exit; break; } } xlog("L_INFO","[RESUME] [PUSH] suspend rm=$rm ru=$rU rd=$rd du=$du \n"); record_route(); t_relay(); exit; } #!endif # Presence server processing route[PRESENCE] { if (!is_method("PUBLISH|SUBSCRIBE")) { return; } if (is_method("SUBSCRIBE") && $hdr(Event)=="message-summary") { route(TOVOICEMAIL); # returns here if no voicemail server is configured sl_send_reply("404", "No voicemail service"); exit; } #!ifdef WITH_PRESENCE if (!t_newtran()) { sl_reply_error(); exit; } if (is_method("PUBLISH")) { xlog("L_INFO", "Received PUBLISH from $fu with E-Tag $hdr(etag)\n"); handle_publish(); t_release(); } else if (is_method("SUBSCRIBE")) { xlog("L_INFO", "Presence request received: $rm from $fu to $ru\n"); handle_subscribe(); t_release(); } exit; #!endif # if presence enabled, this part will not be executed if (is_method("PUBLISH") || $rU==$null) { sl_send_reply("404", "Not here"); exit; } return; } # IP authorization and user authentication route[AUTH] { #!ifdef WITH_AUTH if (is_myself("$si")) { return; } # AUTH route logic summary: # 1) attempt domain auth # 2) Check if request is coming from a carrier that's using username/password auth (remote or local) # 3) attempt IP auth # 4) attempt username/password auth against local subscriber database #================= # Domain AUTH #================= # Check if this is any type of SIP request from a known domain only if the role of the server is not "inout". # The role of "inout" means that the role of this Kamailio instance is to just route calls inbound and outbound # using only IP auth or username/password auth if (lookup_domain("$fd", "domain_") && ($sel(cfg_get.server.role) != 'inout')) { # Turn on domain routing by setting the FLT_DOMAINROUTING flag setflag(FLT_DOMAINROUTING); # If the domain is mapped to single PBX then route to the PBX IP for authentication if ($avp(domain_domain_auth) == "passthru") { fix_nated_contact(); setflag(FLT_PASSTHRU_AUTH); route(SET_CALLINFO); xlog("L_INFO", "DOMAIN_AUTH $tU@$fd will be routed to $avp(domain_pbx_ip)\n"); return; } #Check if the domain is configured to route to a cluster of PBX's by checking if the dispatcher set_id is set #If so, we need to auth the user against an external database or local subscriber database #This will allow INVITE requests to be sent to any backend PBX's because we have validated the user #Hence, the backend PBX's should be setup only to trust SIP connections from dSIPRouter instances else if (!strempty($avp(domain_dispatcher_set_id))) { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); if (is_method("REGISTER|INVITE") || from_uri==myself) { # Each domain has a auth type thats external to the backend destination server # 1 = Kamailo Subscriber table # 2 = Asterisk DB setflag(FLT_EXTERNAL_AUTH); xlog("L_INFO", "Generic Domain Routing for $tU@$fd - the defined auth type for $fd is $avp(domain_domain_auth)\n"); if ($avp(domain_domain_auth) == "realtime") { xlog("L_INFO", "DOMAIN_AUTH Asterisk Realtime auth is being used\n"); # Load data needed for custom SIP headers if ($avp(domain_enrich_headers) == 1) { $var(query) = "select sippasswd,sipdomain from sipusers where name=$fU"; } else { $var(query) = "select sippasswd from sipusers where name=$fU"; } #Let's auth against the database defined by the domain attributes sql_xquery("asterisk","$var(query)","ra"); $var(sippasswd) = $xavp(ra=>sippasswd); sql_result_free("ra"); xlog("L_DBG", "DOMAIN_AUTH The password for user $fU@$fd is $var(sippasswd)\n"); if (!pv_auth_check("$fd", "$xavp(ra=>sippasswd)", "2","0")) { auth_challenge("$fd", "0"); exit; } } else if ($avp(domain_domain_auth) == "local") { xlog("L_INFO", "DOMAIN_AUTH Local auth is being used\n"); if (!auth_check("$fd", "subscriber", "3")) { auth_challenge("$fd", "0"); exit; } } # TODO: return error if domain_dispatcher_set_id is set and domain_domain_auth not realtime or local? } # user authenticated - remove auth header if (!is_method("REGISTER|PUBLISH")) { xlog("L_INFO", "DOMAIN_AUTH $tU@$fd was authenticated\n"); consume_credentials(); } return; } } #================= # Digest AUTH #================= if (is_subscriber("$fu", "subscriber", "3")) { # authenticate requests if (!auth_check("$fd", "subscriber", "3")) { auth_challenge("$fd", "0"); exit; } # user authenticated - remove Authorization header consume_credentials(); # set flags denoting what auth/src we have setflag(FLT_PBX_AUTH); setbflag(FLB_SRC_PBX); route(SET_CALLINFO); return; } #================= # IP AUTH #================= #!ifdef WITH_IPAUTH # If domain not known, then check IP AUTH to see if the user if allowed to connect # Changed from allow_source_address to allow_source_addess_group because it will allow any addresses within any address group. # This means that both carriers and pbx's will be allowed to access the proxy with one function call # TODO: we already check source address in REQINIT, why are we checking again here? if (isflagset(FLT_SRC_ALLOWED)) { # source IP allowed route(SET_CALLINFO); return; } #!endif if (is_method("REGISTER|INVITE") || from_uri==myself) { # authenticate requests if (!auth_check("$fd", "subscriber", "3")) { auth_challenge("$fd", "0"); exit; } # user authenticated - remove auth header if (!is_method("REGISTER|PUBLISH")) { consume_credentials(); } # Set a flag denoting that a PBX has authenticated with username/password setflag(FLT_PBX_AUTH); route(SET_CALLINFO); } #!endif return; } route[SET_CALLINFO] { # defaults to null $avp(src_gwtype) = $null; $avp(src_gwid) = $null; $avp(src_gwgroupid) = $null; $var(received_addr) = $(su{re.subst,/^(sip:|sips:)?(.*)$/\2/}); # Set call info for tracking call limits for username/pass auth if (isflagset(FLT_PBX_AUTH)) { if (sql_xquery("kam", "select rpid as gwgroupid from subscriber where username='$au'", "rows") == 1) { $avp(src_gwtype) = (str)FLT_PBX; $avp(src_gwgroupid) = $xavp(rows=>gwgroupid); if (sql_xquery("kam", "select gwid from dr_gateways where address='$var(received_addr)' AND description REGEXP 'gwgroup:$avp(src_gwgroupid)(,|$$)'", "rows") == 1) { $avp(src_gwid) = $xavp(rows=>gwid); } } } # TODO: these assumptions here are broken throughout the codebase, marked for review in v0.80 else if (isflagset(FLT_DOMAINROUTING)) { $avp(src_gwtype) = $avp(domain_pbx_type); $avp(src_gwgroupid) = $avp(domain_pbx_list); } # Set call info for tracking call limits for ip auth else { if (sql_xquery("kam","select type, gwid from dr_gateways where address like '$si%'", "rows") == 1) { $avp(src_gwtype) = $xavp(rows=>type); $avp(src_gwid) = $xavp(rows=>gwid); $avp(src_gwgroupid) = $sht(gw2gwgroup=>$avp(src_gwid)); } } #!ifdef WITH_CALL_SETTINGS $vn(call_settings) = $sht(call_settings=>$avp(src_gwgroupid)); if ($vn(call_settings) != $null) { $xavu(src_call_settings=>limit) = $(vn(call_settings){s.select,0,,}); $xavu(src_call_settings=>timeout) = $(vn(call_settings){s.select,1,,}); } else { $xavu(src_call_settings=>limit) = ""; $xavu(src_call_settings=>timeout) = ""; } #!endif # dst gateway info is unknown until routed $avp(dst_gwid) = $null; $avp(dst_gwtype) = $null; $avp(dst_gwgroupid) = $null; $avp(dst_msteams_domain) = $null; # set call forwarding info to null by default $avp(hardfwdinfo) = $null; $avp(failfwdinfo) = $null; return; } # once a destination is selected route here to get more detailed info about the gwgroup route[SET_CALLDST_INFO] { if ($avp(dr_attrs) != $null) { $avp(dst_gwid) = $(avp(dr_attrs){s.select,0,,}); $avp(dst_gwtype) = $(avp(dr_attrs){s.select,1,,}); $avp(dst_msteams_domain) = $(avp(dr_attrs){s.select,2,,}); $avp(dst_signalling) = $(avp(dr_attrs){s.select,3,,}); $avp(dst_media) = $(avp(dr_attrs){s.select,4,,}); $avp(dst_gwgroupid) = $sht(gw2gwgroup=>$avp(dst_gwid)); # by convention the setid is the same as the gwgroupid $avp(dispatcher_setid) = $avp(dst_gwgroupid); } else if (isflagset(FLT_PASSTHRU_AUTH)) { # passthru auth only maps to a single pbx $avp(dst_gwid) = $(avp(domain_pbx_list){s.select,0,,}); sql_pvquery( "kam", "SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 2), ',', -1), SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 3), ',', -1), SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 4), ',', -1), SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 5), ',', -1), REGEXP_REPLACE(description, '.*gwgroup:([0-9]+).*', '\\1') FROM dr_gateways WHERE gwid='$avp(dst_gwid)'", "$avp(dst_gwtype), $avp(dst_msteams_domain), $avp(dst_signalling), $avp(dst_media), $avp(dst_gwgroupid)" ); } else { xlog("L_ERR", "can not lookup destination info\n"); return; } $vn(gwgroup2lb) = $sht(gwgroup2lb=>$avp(dst_gwgroupid)); if ($vn(gwgroup2lb) != $null) { $avp(lb_enabled) = (int)$(vn(gwgroup2lb){s.select,1,,}); } else { $avp(lb_enabled) = 0; } #!ifdef WITH_CALL_SETTINGS $vn(call_settings) = $sht(call_settings=>$avp(dst_gwgroupid)); if ($vn(call_settings) != $null) { $xavu(dst_call_settings=>limit) = $(vn(call_settings){s.select,0,,}); $xavu(dst_call_settings=>timeout) = $(vn(call_settings){s.select,1,,}); } else { $xavu(dst_call_settings=>limit) = ""; $xavu(dst_call_settings=>timeout) = ""; } #!endif } # TODO: we should avoid recreating the drouting algo here and instead check hardfwd and failfwd dr_group by default # drouting would be called 3 times by default in this case, satisfying the following logic: # 1. check hardfwd dr_group for prefix matches # 2. check default dr_group for prefix matches (pbx or carrier) # 3. check failfwd dr_group for prefix matches # this would avoid prefix match prediction as we do below and support time criteria by default route[SET_CALLFWD_INFO] { # we need to know what prefix drouting will match before it runs # this only checks against inbound rules, this shouldn't be used for outbound # TODO: this algorithm ignores time criteria of the dr_rule # which could lead to false positives if using this setting in drouting $avp(dr_ruleid) = $null; # dr_ruleid matched $var(prefix_match_diff) = 1000; # last match prefix diff $var(diff) = 1000; # current entry prefix diff $var(prefix_match_priority) = 0; # last match priority $var(priority) = 0; # current entry priority $var(lookup) = $(rU{s.unescape.user}); # dnid to match against sht_iterator_start("iter", "inbound_prefixmap"); while(sht_iterator_next("iter")) { # TODO: for now we use a literal prefix but we should change to supporting patterns # this would require updating drouting module to support pattern matching $var(regex) = $(shtitkey(iter){re.subst,/(\+|\*|\#)/\\\1/g}) + "([0-9])*"; if ($var(lookup) =~ $var(regex)) { xlog("L_DBG", "prefix match on $shtitkey(iter)\n"); # if priority is less than last match we don't update match $var(priority) = $(shtitval(iter){s.select,1,,}{s.int}); if ($var(priority) >= $var(prefix_match_priority)) { # get the difference between prefix and match $var(diff) = $(var(lookup){s.len}) - $(shtitkey(iter){s.len}); # bitwise absolute value: ( mask = n>>31; (mask^n) - mask ) # we are assuming 32 bit integers $var(mask) = $var(diff) >> 31; $var(diff) = ($var(mask) ^ $var(diff)) - $var(mask); # if priority is greater than last match or priority is equal and # diff is less than last match (closer match) we update match if ($var(priority) > $var(prefix_match_priority) || $var(diff) < $var(prefix_match_diff)) { xlog("L_DBG", "prefix closer match priority=$var(priority) diff=$var(diff)\n"); $avp(dr_ruleid) = $(shtitval(iter){s.select,0,,}); $var(prefix_match_priority) = $var(priority); $var(prefix_match_diff) = $var(diff); } } } } sht_iterator_end("iter"); if ($avp(dr_ruleid) != $null) { $avp(hardfwdinfo) = $sht(inbound_hardfwd=>$avp(dr_ruleid)); $avp(failfwdinfo) = $sht(inbound_failfwd=>$avp(dr_ruleid)); } return; } route[SET_CALL_DLGCFG] { #!ifdef WITH_CALL_SETTINGS # set the dialog timeout to the smallest timeout between the src and dst gwgroups if (!strempty($xavu(dst_call_settings=>timeout))) { $vn(call_timeout) = (int)$xavu(dst_call_settings=>timeout); if (!strempty($xavu(src_call_settings=>timeout)) && $vn(call_timeout) > $xavu(src_call_settings=>timeout)) { $vn(call_timeout) = (int)$xavu(src_call_settings=>timeout); } } else if (!strempty($xavu(src_call_settings=>timeout))) { $vn(call_timeout) = (int)$xavu(src_call_settings=>timeout); } if ($vn(call_timeout) != $null) { $dlg_ctx(timeout_bye) = 1; $dlg_ctx(timeout) = $vn(call_timeout); } #!endif return; } # TODO: ipv6 support route[SET_RECORD_ROUTE] { # remove any previous headers set remove_record_route(); # TODO: someone give good explanation of the NLB record routing here if (("OUTBOUND_NLB_FQDN" != "") && ("INBOUND_NLB_FQDN" != "")) { record_route_preset("OUTBOUND_NLB_FQDN","INBOUND_NLB_FQDN"); } else if (("OUTBOUND_NLB_FQDN" == "") && ("INBOUND_NLB_FQDN" != "")) { record_route_advertised_address("INBOUND_NLB_FQDN"); } #!ifdef WITH_MSTEAMS # TODO: needs check everytime this is called, can't store/check flag # MS Teams special use case - add hop from SIPS port to SIP port else if (isbflagset(FLB_DST_MSTEAMS)) { xlog("L_INFO","ru==$ru\n"); # TODO: if end-to-end encryption is used this may not work # TODO: we should be dynamically checking the transport for each side of the record route # in this case we are missing TLS <-> TCP connections and TLS <-> TLS connections # we should instead generalize the handling of different ports/protocols and whether r2=on should be added # TODO: not adding in check for IPv4 vs IPv6 vs Domain here since we are sending to ourself - is this assumption correct? record_route_preset("$avp(dst_msteams_domain):SIPS_PORT;transport=tls;r2=on", "INTERNAL_IP_ADDR:SIP_PORT;transport=udp;r2=on"); force_send_socket(tls:INTERNAL_IP_ADDR:SIPS_PORT); } # If coming from MSTEAMS, change Record Route to match the domain it's coming from else if (isbflagset(FLB_SRC_MSTEAMS)) { record_route_preset("$td:SIP_PORT;transport=udp;r2=on", "$td:SIPS_PORT;transport=tls;r2=on"); } #!endif # the source of the request will be following the Route / Record-Route headers # so we only need send them back to our internal ip if from within our subnet else if (isflagset(FLT_SRC_INTERNAL_IP)) { if (isflagset(FLT_SRC_IPV6)) { record_route_preset("[INTERNAL_IP6_ADDR]:$Rp;rwdst"); } else { record_route_preset("INTERNAL_IP_ADDR:$Rp;rwdst"); } } else { if (isflagset(FLT_SRC_IPV6)) { record_route_preset("[EXTERNAL_IP6_ADDR]:$Rp;rwdst"); } else { record_route_preset("EXTERNAL_IP_ADDR:$Rp;rwdst"); } } # store the source and destination for later usage within the dialog if ($du == $null) { $dlg_var(dst_uri) = $(ru{uri.duri}); } else { $dlg_var(dst_uri) = $du; } $dlg_var(src_uri) = $sut; return; } # Caller NAT detection # TODO: FLB_SRC_PBX may not be set yet for endpoints using digest auth route[NATDETECT] { #!ifdef WITH_NAT if (nat_uac_test("19")) { # TODO: explain why fixing the contact is disabled for these use cases if (isflagset(FLT_SRC_SIP) && !isbflagset(FLB_SRC_CARRIER) && !isbflagset(FLB_SRC_PBX) && !isbflagset(FLB_SRC_MSTEAMS)) { fix_nated_contact(); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes to message\n"); } force_rport(); } if (is_method("REGISTER")) { if (isflagset(FLT_SRC_WS)) { t_on_reply("WS_REPLY"); } fix_nated_register(); } # TODO: explain why we are rewriting the contact instead of using contact aliasing # else if (is_first_hop()) { # set_contact_alias(); # } } #!endif return; } # TODO: on configuration failure continue with next endpoint instead of routing to this endpoint route[SET_DST_SIGNALLING] { # rtpengine_offer(); $avp(dst_signalling)="sip_udp"; switch ($avp(dst_signalling)) { case "proxy": # TODO: in future releases "proxy" will mean record route through kamailio # and another option for bypassing kamailio will be available if ($du == $null) { $du = $(ru{uri.duri}); } break; case "sip_udp": if ($du == $null) { $du = "sip:" + $sel(ruri.hostport) + ";transport=udp"; } else { $du = "sip:" + $sel(dst_uri.hostport) + ";transport=udp"; } break; case "sip_tcp": if ($du == $null) { $du = "sip:" + $sel(ruri.hostport) + ";transport=tcp"; } else { $du = "sip:" + $sel(dst_uri.hostport) + ";transport=tcp"; } break; case "sip_sctp": if ($du == $null) { $du = "sip:" + $sel(ruri.hostport) + ";transport=sctp"; } else { $du = "sip:" + $sel(dst_uri.hostport) + ";transport=sctp"; } break; # case "sip_ws": # if ($du == $null) { # $du = "sip:" + $sel(ruri.hostport) + ";transport=ws"; # } # else { # $du = "sip:" + $sel(dst_uri.hostport) + ";transport=ws"; # } # break; case "sips_tls": if ($du == $null) { $du = "sips:" + $sel(ruri.hostport) + ";transport=tls"; } else { $du = "sips:" + $sel(dst_uri.hostport) + ";transport=tls"; } break; case "sips_sctp": if ($du == $null) { $du = "sips:" + $sel(ruri.hostport) + ";transport=sctp"; } else { $du = "sips:" + $sel(dst_uri.hostport) + ";transport=sctp"; } break; # case "sips_wss": # if ($du == $null) { # $du = "sips:" + $sel(ruri.hostport) + ";transport=ws"; # } # else { # $du = "sips:" + $sel(dst_uri.hostport) + ";transport=ws"; # } # break; default: xlog("L_WARN", "invalid signalling configuration requested $avp(dst_signalling) for endpoint $avp(dst_gwid)\n"); break; } return; } # TODO: on configuration failure continue with next endpoint instead of routing to this endpoint route[SET_DST_MEDIA] { xlog("99999999999999999999 $avp(dst_media) du=$du $(ru{uri.duri})"); $avp(dst_media)="rtp_avpf_any"; switch ($avp(dst_media)) { case "proxy": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = ""; # if (sdp_get_line_startswith("$vn(mline)", "m=")) { # #$avp(src_media_tp) = $(vn(mline){re.subst,/^m=[^ ]+ [^ ]+ ([^ ]+) .*/\1/}); # } sdp_transport("$avp(src_media_tp)"); break; case "direct": resetflag(FLT_USE_RTPE); break; case "rtp_avp": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "RTP/AVP"; sdp_transport("$avp(src_media_tp)"); break; case "rtp_savp": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "RTP/SAVP"; sdp_transport("$avp(src_media_tp)"); break; case "rtp_avpf": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "RTP/AVPF"; sdp_transport("$avp(src_media_tp)"); break; case "rtp_savpf": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "RTP/SAVPF"; sdp_transport("$avp(src_media_tp)"); break; case "rtp_avp_any": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "UDP/TLS/RTP/SAVP"; sdp_transport("$avp(src_media_tp)"); break; case "rtp_avpf_any": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "UDP/TLS/RTP/SAVPF"; sdp_transport("$avp(src_media_tp)"); break; case "udptl": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "T.38=force"; sdp_transport("$avp(src_media_tp)"); break; case "osrtp_avp": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "transport-protocol=RTP/AVP OSRTP=offer"; sdp_transport("$avp(src_media_tp)"); break; case "osrtp_avpf": setflag(FLT_USE_RTPE); $avp(dst_media_tp) = "transport-protocol=RTP/AVPF OSRTP=offer"; sdp_transport("$avp(src_media_tp)"); break; default: xlog("L_WARN", "invalid media configuration requested for endpoint $avp(dst_gwid)\n"); break; } return; } # Server / DMZ NAT detection # Determine NAT requirements for destination and source and set flags for later usage # TODO: source self is known after REQINIT, consider copying over transaction flags such as FLT_SRC_SELF (multiple transactions in flow) route[SERVERNATDETECT] { # default to NULL $var(dst_ipv4) = $null; $var(dst_ipv6) = $null; # always reset flags when called resetflag(FLT_SRC_INTERNAL_IP); resetflag(FLT_DST_INTERNAL_IP); resetflag(FLT_SRC_IPV4); resetflag(FLT_DST_IPV4); resetflag(FLT_SRC_IPV6); resetflag(FLT_DST_IPV6); if ($dd == $null) { $var(dst) = $rd; } else { $var(dst) = $dd; } if (is_ipv4($var(dst))) { $var(dst_ipv4) = $var(dst); } else if (is_ipv6($var(dst))) { $var(dst_ipv6) = $var(dst); } else { if (dns_query($var(dst), "dst")) { $var(i) = 0; while ($var(i) < $dns(dst=>count)) { if ($var(dst_ipv4) == $null && $dns(dst=>type[$var(i)]) == 4) { $var(dst_ipv4) = $dns(dst=>addr[$var(i)]); } else if ($var(dst_ipv6) == $null && $dns(dst=>type[$var(i)]) == 6) { $var(dst_ipv6) = $dns(dst=>addr[$var(i)]); } $var(i) = $var(i) + 1; } } else { xlog("L_ERR", "dns query failed for $var(dst)\n"); } } #!ifdef WITH_SERVERNAT # source does not change throughout if (is_in_subnet($si, "INTERNAL_IP_NET") || is_myself("$si")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV4); } if (is_in_subnet($var(dst_ipv4), "INTERNAL_IP_NET") || is_myself("$var(dst)")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV4); } #!endif #!ifdef WITH_SERVERNAT6 # source does not change throughout if (!isflagset(FLT_SRC_INTERNAL_IP) && is_in_subnet($si, "INTERNAL_IP6_NET")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV6); } if (!isflagset(FLT_DST_INTERNAL_IP) && is_in_subnet($var(dst_ipv6), "INTERNAL_IP6_NET")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV6); } #!endif #!ifdef WITH_DMZ # source does not change throughout if (!isflagset(FLT_SRC_INTERNAL_IP)) { if (is_in_subnet($si, "INTERNAL_IP_NET")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV4); } #!ifdef WITH_IPV6 else if (is_in_subnet($si, "INTERNAL_IP6_NET")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV6); } #!endif } if (!isflagset(FLT_DST_INTERNAL_IP)) { if (is_in_subnet($var(dst_ipv4), "INTERNAL_IP_NET")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV4); } #!ifdef WITH_IPV6 else if (is_in_subnet($var(dst_ipv4), "INTERNAL_IP6_NET")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV6); } #!endif } #!endif return; } # should only be called within request routes route[RTPENGINEOFFER] { #!ifdef WITH_RTPENGINE if (!isflagset(FLT_USE_RTPE)) { return; } # - Web to web if (isflagset(FLT_SRC_WS) && isbflagset(FLB_WS_DEVICE)) { $var(reflags) = "trust-address replace-origin replace-session-connection SDES-off ICE=force " + $avp(dst_media_tp); } # - Web to SIP else if (isflagset(FLT_SRC_WS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove " + $avp(dst_media_tp); } # - SIP to web else if (isbflagset(FLB_WS_DEVICE)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=force transcode-PCMU transcode-G722 SDES-off " + $avp(dst_media_tp); } # - MSTEAMS to SIP using RTP/AVP else if (isbflagset(FLB_SRC_MSTEAMS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=remove " + $avp(dst_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=remove RTP/AVP original-sendrecv"; } # - MSTEAMS to SIP ONHOLD using RTP/AVP else if (isbflagset(FLB_SRC_MSTEAMS_ONHOLD)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-accept ICE=remove " + $avp(dst_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-accept ICE=remove RTP/AVP original-sendrecv"; } # - SIP to MSTEAMS else if (isbflagset(FLB_DST_MSTEAMS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=force transcode-PCMU transcode-G722 " + $avp(dst_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=force codec-transcode=PCMU codec-transcode=PCMA codec-transcode=G722 codec-transcode=G729 RTP/SAVP original-sendrecv"; } # - SIP to SIP else { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove " + $avp(dst_media_tp); } # for serverside NAT we may need to use one of the internal IPs as the media address if (isflagset(FLT_DST_INTERNAL_IP)) { if (isflagset(FLT_DST_IPV6)) { $var(reflags) = $var(reflags) + " media-address=INTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=INTERNAL_IP_ADDR"; } } else { if (isflagset(FLT_DST_IPV6)) { $var(reflags) = $var(reflags) + " media-address=EXTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=EXTERNAL_IP_ADDR"; } } #!ifdef WITH_DMZ if (isflagset(FLT_SRC_INTERNAL_IP) && !isflagset(FLT_DST_INTERNAL_IP)) { $var(reflags)= $var(reflags) + " direction=private direction=public"; } else if (!isflagset(FLT_SRC_INTERNAL_IP) && isflagset(FLT_DST_INTERNAL_IP)) { $var(reflags)= $var(reflags) + " direction=public direction=private"; } #!else #!ifdef WITH_IPV6 # select interface within rtpengine based on IP versions (by default will use the IPv4 interface) # only needed when not using the default interface (1st listen interface for rtpengine) if (isflagset(FLT_SRC_IPV4) && isflagset(FLT_DST_IPV6)) { $var(reflags)= $var(reflags) + " direction=ipv4 direction=ipv6"; } else if (isflagset(FLT_SRC_IPV6) && isflagset(FLT_DST_IPV4)) { $var(reflags)= $var(reflags) + " direction=ipv6 direction=ipv4"; } else if (isflagset(FLT_SRC_IPV6) && isflagset(FLT_DST_IPV6)) { $var(reflags)= $var(reflags) + " direction=ipv6 direction=ipv6"; } else { $var(reflags)= $var(reflags) + " direction=ipv4 direction=ipv4"; } #!endif #!endif xlog("L_INFO", "reflags: $var(reflags)\n"); rtpengine_offer("$var(reflags)"); #!endif return; } route[RTPENGINEANSWER] { #!ifdef WITH_RTPENGINE if (!isflagset(FLT_USE_RTPE)) { return; } # - Web to web if (isflagset(FLT_SRC_WS) && isbflagset(FLB_WS_DEVICE)) { $var(reflags) = "trust-address replace-origin replace-session-connection SDES-off ICE=force " + $avp(src_media_tp); } # - Web to SIP else if (isflagset(FLT_SRC_WS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force " + $avp(src_media_tp); } # - MSTEAMS to SIP using RTP/AVP else if (isbflagset(FLB_DST_MSTEAMS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=remove " + $avp(src_media_tp); } # - SIP to MSTEAMS else if (isbflagset(FLB_SRC_MSTEAMS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force transcode-PCMU transcode-G722 SDES-off " + $avp(src_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force codec-transcode=PCMU codec-transcode=PCMA codec-transcode=G722 codec-transcode=G729 SDES-off RTP/SAVP original-sendrecv"; } # - SIP to MSTEAMS ONHOLD else if (isbflagset(FLB_SRC_MSTEAMS_ONHOLD)) { xlog("L_DBG", "ONHOLD - ANSWER\n"); $var(reflags) = "trust-address replace-origin replace-session-connection ICE=remove transcode-PCMU transcode-G722 SDES-off " + $avp(src_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force codec-transcode=PCMU codec-transcode=PCMA codec-transcode=G722 codec-transcode=G729 SDES-off RTP/SAVP original-sendrecv"; } # - SIP to SIP else { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove " + $avp(src_media_tp); } # NOTE: no need to set direction= here, direction will be determined from the offer # for serverside NAT we may need to use one of the internal IPs as the media address if (isflagset(FLT_SRC_INTERNAL_IP)) { if (isflagset(FLT_SRC_IPV6)) { $var(reflags)= $var(reflags) + " media-address=INTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=INTERNAL_IP_ADDR"; } } else { if (isflagset(FLT_SRC_IPV6)) { $var(reflags)= $var(reflags) + " media-address=EXTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=EXTERNAL_IP_ADDR"; } } xlog("L_INFO", "reflags: $var(reflags)\n"); rtpengine_answer("$var(reflags)"); #!endif return; } route[RTPENGINEDELETE] { #!ifdef WITH_RTPENGINE if (!isflagset(FLT_USE_RTPE)) { return; } rtpengine_delete(); #!endif return; } # RTPProxy control and signaling updates for NAT traversal route[NATMANAGE] { #!ifdef WITH_NAT if (is_request()) { if (has_totag() && check_route_param("nat=yes")) { setbflag(FLB_NATB); } } else if (has_body("application/sdp") && is_reply()) { if (nat_uac_test("1")) { fix_nated_sdp("10"); setbflag(FLB_NATB); } } if (!isbflagset(FLB_NATB)) { return; } # for initial request without nat="yes" we set it for the rest of this dialog if (is_request() && !has_totag() && t_is_branch_route()) { add_rr_param(";nat=yes"); } # for dialog NAT traversal: # - only forward requests if there is an existing connection to the destination # - only set contact alias on replies if B-Leg of call is NATed as well if (is_request() && has_totag()) { set_forward_no_connect(); } else if (is_reply() && is_first_hop() && nat_uac_test("1")) { set_contact_alias(); } #!endif return; } # URI update for dialog requests route[DLGURI] { #!ifdef WITH_NAT if(!isdsturiset()) { handle_ruri_alias(); } #!endif if (check_route_param("rwdst")) { if (is_direction("downstream")) { $du = $dlg_var(dst_uri); } else { $du = $dlg_var(src_uri); } } } # Routing to foreign domains route[SIPOUT] { if (uri == myself) { return; } append_hf("P-hint: outbound\r\n"); route(RELAY); exit; } # PSTN GW routing route[PSTN] { #!ifdef WITH_PSTN # check if PSTN GW IP is defined if (strempty($sel(cfg_get.pstn.gw_ip))) { xlog("L_ERR", "pstn routing enabled but pstn.gw_ip not defined\n"); return; } # route to PSTN dialed numbers starting with '+' or '00' (international format) # - update the condition to match your dialing rules for PSTN routing if (!($rU=~"^(\+|00)[1-9][0-9]{3,20}$")) return; # only local users allowed to call if (from_uri!=myself) { sl_send_reply("403", "Not Allowed"); exit; } if (strempty($sel(cfg_get.pstn.gw_port))) { $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip); } else { $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip) + ":" + $sel(cfg_get.pstn.gw_port); } route(RELAY); exit; #!endif return; } # XMLRPC routing #!ifdef WITH_XMLRPC route[XMLRPC] { # allow XMLRPC from localhost if ((method=="POST" || method=="GET") && (src_ip==127.0.0.1)) { # close connection only for xmlrpclib user agents (there is a bug in # xmlrpclib: it waits for EOF before interpreting the response). if ($hdr(User-Agent) =~ "xmlrpclib") set_reply_close(); set_reply_no_connect(); dispatch_rpc(); exit; } send_reply("403", "Forbidden"); exit; } #!endif # Routing to voicemail server route[TOVOICEMAIL] { #!ifdef WITH_VOICEMAIL if (!is_method("INVITE|SUBSCRIBE")) { return; } # check if VoiceMail server IP is defined if (strempty($sel(cfg_get.voicemail.srv_ip))) { xlog("L_ERR", "VoiceMail routing enabled but IP not defined\n"); return; } if (is_method("INVITE")) { if ($avp(oexten) == $null) { return; } $ru = "sip:" + $avp(oexten) + "@" + $sel(cfg_get.voicemail.srv_ip) + ":" + $sel(cfg_get.voicemail.srv_port); } else { if ($rU == $null) { return; } $ru = "sip:" + $rU + "@" + $sel(cfg_get.voicemail.srv_ip) + ":" + $sel(cfg_get.voicemail.srv_port); } route(RELAY); exit; #!endif return; } # Populate CDRs Table #!ifdef WITH_CDRS route[CDRS] { sql_query("kam","call kamailio_cdrs()","rb"); # we are not using billing features #sql_query("kam","call kamailio_rating('default')","rb"); } #!endif # send async http request for notifications # required: $avp(notification_type) # required: $avp(notification_gwgroupid) # optional: $avp(notification_gwid) route[SEND_NOTIFICATION] { if ($avp(notification_type) != $null && $avp(notification_gwgroupid) != $null) { $http_req(method) = "POST"; $http_req(hdr) = "User-Agent: http_async_client"; $http_req(hdr) = "Authorization: Bearer " + $sel(cfg_get.server.api_token); $http_req(body) = '{"gwgroupid":' + $avp(notification_gwgroupid) + ', "type":' + $avp(notification_type) + ', "gwid":' + $avp(notification_gwid) + ', "text_body":"Gateway Group [' + $avp(notification_gwgroupid) + '] triggered the following notification for Gateway [' + $avp(notification_gwid) + ']"}'; $http_req(suspend) = 0; xlog("L_INFO", "Sending request to $sel(cfg_get.server.api_server)/api/v1/notification/gwgroup for type $avp(notification_type)\n"); http_async_query("$sel(cfg_get.server.api_server)/api/v1/notification/gwgroup", "HTTP_REPLY"); } else { xlog("L_ERR", "avp 'notification_type' and 'notification_gwgroupid' are required for notification sending\n"); } $avp(notification_type) = $null; $avp(notification_gwgroupid) = $null; $avp(notification_gwid) = $null; } route[HTTP_REPLY] { if ($http_ok) { xlog("L_INFO", "status: $http_rs\n"); xlog("L_DBG", "body: $http_rb\n"); } else { xlog("L_ERR", "error: $http_err)\n"); } } # executed when 200 OK reply for INVITE is processed event_route[dialog:start] { #!ifdef WITH_CALL_SETTINGS # increment the concurrent calls htable if ($avp(src_gwgroupid) != $avp(dst_gwgroupid)) { sht_lock("concurrent_calls=>$avp(src_gwgroupid)"); $sht(concurrent_calls=>$avp(src_gwgroupid)) = $sht(concurrent_calls=>$avp(src_gwgroupid)) + 1; sht_unlock("concurrent_calls=>$avp(src_gwgroupid)"); } sht_lock("concurrent_calls=>$avp(dst_gwgroupid)"); $sht(concurrent_calls=>$avp(dst_gwgroupid)) = $sht(concurrent_calls=>$avp(dst_gwgroupid)) + 1; sht_unlock("concurrent_calls=>$avp(dst_gwgroupid)"); #!endif return; } # executed when dialog is not completed (300 or greater reply code to INVITE) #event_route[dialog:failed] { # return; #} # executed when BYE is processed or dialog timed out event_route[dialog:end] { #!ifdef WITH_CALL_SETTINGS # decrement the concurrent calls htable if ($avp(src_gwgroupid) != $avp(dst_gwgroupid)) { sht_lock("concurrent_calls=>$avp(src_gwgroupid)"); $sht(concurrent_calls=>$avp(src_gwgroupid)) = $sht(concurrent_calls=>$avp(src_gwgroupid)) - 1; sht_unlock("concurrent_calls=>$avp(src_gwgroupid)"); } sht_lock("concurrent_calls=>$avp(dst_gwgroupid)"); $sht(concurrent_calls=>$avp(dst_gwgroupid)) = $sht(concurrent_calls=>$avp(dst_gwgroupid)) - 1; sht_unlock("concurrent_calls=>$avp(dst_gwgroupid)"); #!endif return; } event_route[uac:reply] { xlog("L_DBG", "Request sent to $uac_req(ruri) with event code $uac_req(evcode)\n"); } event_route[xhttp:request] { if ($hu =~ "^/api/kamailio" && dst_ip==127.0.0.1) { jsonrpc_dispatch(); } #!ifdef WITH_WEBSOCKETS else if ($Rp == "WSS_PORT") { if ($hdr(Upgrade) =~ "websocket" && $hdr(Connection) =~ "Upgrade" && $rm =~ "GET") { if (ws_handle_handshake()) { # Optional... cache some information about the # successful connection exit; } } } else { xhttp_reply("403", "Forbidden", "text/html", "Will only communicate on the local interface or WebSocket Port WSS_PORT"); exit; } #!endif return; } # executed for tm locally created requests event_route[tm:local-request] { #!ifdef WITH_MSTEAMS if (is_method("OPTIONS") && $ru =~ "pstnhub.microsoft.com") { append_hf("Contact: \r\n"); xlog("L_DBG", "Changed contact to $ct\n"); } #!endif # TODO: why are we changing the contact here? # Get destination IP $var(destIP)=$(du{s.select,1,:}); # Only change the contact if an Inound NLB is set and the Register is going to a carrier if (is_method("REGISTER") && ("INBOUND_NLB_FQDN" != "") && !allow_address(FLT_PBX, "$var(destIP)", 0)) { if (subst('/^Contact: /ig')) { xlog("L_DBG", "Changed REGISTER contact to load balancer address: $mb\n"); } } } # executed for tm locally created responses #event_route[tm:local-response] { # #} # executed for sl received (and ignored) ACK responses #event_route[sl:filtered-ack] { # #} # executed for sl locally created responses #event_route[sl:local-response] { # #} event_route[usrloc:contact-expired] { if (sql_xquery("kam", "SELECT rpid AS gwgroupid FROM subscriber WHERE username='$(ulc(exp=>addr){uri.user})'", "rows") == 1) { $var(src_gwgroupid) = $xavp(rows=>gwgroupid); $var(received_addr) = $(ulc(exp=>received){re.subst,/^(sip:|sips:)?(.*)$/\2/}); if (sql_xquery("kam", "SELECT gwid FROM dr_gateways WHERE address='$var(received_addr)' AND description REGEXP 'gwgroup:$var(src_gwgroupid)(,|$$)'", "rows") == 1) { $var(src_gwid) = $xavp(rows=>gwid); xlog("L_DBG", "found gateway $var(src_gwid) for address $var(received_addr), removing from gwgroup $var(src_gwgroupid)\n"); sql_query("kam", "DELETE FROM dr_gateways WHERE gwid='$var(src_gwid)'"); sql_query("kam", "UPDATE dr_gw_lists SET gwlist=REGEXP_REPLACE(REGEXP_REPLACE(gwlist, '([,;])?$var(src_gwid)', ''), '^([,;])', '') WHERE id=$var(src_gwgroupid)"); jsonrpc_exec('{"jsonrpc": "2.0", "method": "drouting.reload", "id": 1}'); } } } route[REMOVE_REFER] { if (subst_hf("Allow", "/(.+)(REFER,)\s(.+)/\1\3/", "f")) { xlog("L_INFO", "Removing REFER from Accepted Method to $du\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes to message\n"); } } } # Manage outgoing branches branch_route[MANAGE_BRANCH] { xlog("L_DBG", "new branch [$T_branch_idx] created to $ru via $du\n"); if (isbflagset(FLB_WS_DEVICE)) { $var(FLB_WS_DEVICE) = 1; } else { $var(FLB_WS_DEVICE) = 0; } if (has_body("application/sdp")) { route(RTPENGINEOFFER); } } # Manage incoming replies onreply_route[MANAGE_REPLY] { xlog("L_INFO", "incoming reply from source address $si:$sp\n"); # Rewrite the SDP on incoming replies if (t_check_status("183|180|200") && has_body("application/sdp")) { route(RTPENGINEANSWER); if ($avp(sdp_media_direction) != $null) { if (!msg_apply_changes()) { xlog("L_ERR", "could not update sdp\n"); } if (!subst("/^a=(sendrecv|recvonly|sendonly|inactive).*/a=$avp(sdp_media_direction)/")) { #search_append_body("^a=.+", "a=$avp(sdp_media_direction)"); xlog("L_ERR", "could not update sdp\n"); } if (!msg_apply_changes()) { xlog("L_ERR", "could not update sdp\n"); } } # TODO: why are we dropping 183 replies to MSTEAMS? - marked for review/validation if (t_check_status("183") && isbflagset(FLB_DST_MSTEAMS)) { drop(); } } # # TODO: is rewriting external/internal IP on reply necessary?? # we should only rewrite to internal on servernat ##!ifdef WITH_SERVERNAT # # TODO: Need to evaluate this when running in AWS with an External SIP UAC # if (status=="200" && isbflagset(FLB_SRC_CARRIER)) { # if (isflagset(FLT_SRC_INTERNAL_IP)) { # subst_hf("Record-Route","/EXTERNAL_IP_ADDR/INTERNAL_IP_ADDR/","f"); # } ##!ifdef WITH_IPV6 # else if (isflagset(FLT_SRC_INTERNAL_IP6)) { # subst_hf("Record-Route","/EXTERNAL_IP6_ADDR/INTERNAL_IP_ADDR/","f"); # } # } ##!endif # if (status=="200" && isbflagset(FLB_SRC_PBX)) { # if (isflagset(FLT_DST_INTERNAL_IP)) { # subst_hf("Record-Route","/INTERNAL_IP_ADDR/EXTERNAL_IP_ADDR/","f"); # } ##!ifdef WITH_IPV6 # else if (isflagset(FLT_DST_INTERNAL_IP6)) { # subst_hf("Record-Route","/INTERNAL_IP6_ADDR/EXTERNAL_IP6_ADDR/","f"); # } ##!endif # } ##!endif # if (t_check_status("100|180|181|183") && $avp(calltype) == "inbound") { # # Increase the lifetime of the current INVITE to pbx_invite_timeout_aftertry if endpoint returns 100/180/181/183. # # This means that the endpoint is at least trying to establish the call. So, we will extend the timeout. # # $var(pbx_invite_timeout) = (int)$sel(cfg_get.server.pbx_invite_timeout_aftertry); # t_set_max_lifetime($var(pbx_invite_timeout), 0); # xlog("L_DBG", "Increasing the Invite Timeout for <$ci> to <$var(pbx_invite_timeout)>\n"); # } } # Manage failure routing cases failure_route[MANAGE_FAILURE] { # Capture the Failure in the CDR setflag(FLT_ACCFAILED); route(NATMANAGE); if (t_is_canceled()) { exit; } if (t_branch_timeout()) { t_drop_replies(); } if (t_check_status("401|407") && !strempty($avp(carrier_groupid))) { t_drop_replies(); xlog("L_INFO", "PROXY_AUTH Remote asked for authentication\n"); #!ifdef WITH_RTPENGINE rtpengine_offer("ICE=remove"); #!endif uac_auth(); if (!t_relay()) { xlog("L_INFO", "PROXY_AUTH Authentication failed. Sending back 503 to UA\n"); t_reply("503","Service not available"); } exit; } # if using pass thru auth relay the reply if (t_check_status("401|407") && isflagset(FLT_PASSTHRU_AUTH)) { t_relay(); exit; } #!ifdef WITH_DROUTE if (t_check_status("[0-6][0-9][0-9]") || !t_any_replied()) { if (use_next_gw()) { # set du to match what drouting selected $du = $(ru{uri.duri}); # Set INVITE max lifetime to ensure Primary and Secondary PBX server feature works. if (isbflagset(FLB_SRC_CARRIER)) { t_set_fr((int)$sel(cfg_get.server.pbx_invite_timeout_aftertry), (int)$sel(cfg_get.server.pbx_invite_timeout)); } $du = $ru; route(RELAY); exit; } else { # Only intervene on a request from the Carrier if (isbflagset(FLB_SRC_CARRIER)) { # this route will check for failover and return false if it does not route the call # if we are not failover routing, then none of the routes were successful and we send back an error if (!route(NEXTHOP_FAILOVER)) { t_reply("503","Service not available"); # we can only send notification if mapped to endpoint group if ($avp(src_gwtype) == FLT_PBX) { $avp(notification_type) = NOTIFICATION_GWFAILURE; $avp(notification_gwgroupid) = $avp(src_gwgroupid); $avp(notification_gwid) = $avp(src_gwid); route(SEND_NOTIFICATION); } else if ($avp(dst_gwtype) == FLT_PBX) { $avp(notification_type) = NOTIFICATION_GWFAILURE; $avp(notification_gwgroupid) = $avp(dst_gwgroupid); $avp(notification_gwid) = $avp(dst_gwid); route(SEND_NOTIFICATION); } } } exit; } } #!endif #!ifdef WITH_BLOCK3XX # block call redirect based on 3xx replies. if (t_check_status("3[0-9][0-9]")) { t_reply("404","Not found"); exit; } #!endif #!ifdef WITH_VOICEMAIL # serial forking # - route to voicemail on busy or no answer (timeout) if (t_check_status("486|408")) { $du = $null; route(TOVOICEMAIL); exit; } #!endif } onreply_route[WS_REPLY] { if (nat_uac_test("64")) { # Do NAT traversal stuff for replies to a WebSocket connection # - even if it is not behind a NAT! # This won't be needed in the future if Kamailio and the # WebSocket client support Outbound and Path. add_contact_alias(); } } # TODO: FLB_SRC_PBX is not set within in-dialog requests route[PBX_TO_ENDPOINT_LOOKUP] { # Lookup the actual location of endpoint if # coming from a PBX and the request domain is a local ip address if (isbflagset(FLB_SRC_PBX) && is_ip_rfc1918($rd)) { if (lookup("location","sip:$rU@$fd")) { xlog("L_DBG", "Looking up the domain and getting domain: $fd\n"); } } } # TODO: dynamically get user_tn / pilot_tn from user configs # Carrier Enrichment for CenturyLink # validated SIP carriers: # voip.centurylink.com # 65.149.22.7, 65.149.23.7, 65.149.24.7, 65.149.25.7 # 216.206.64.7, 216.206.64.71, 216.206.64.91 # 216.206.66.7, 216.206.66.71, 216.206.66.91 route[ENRICH_CARRIER_CENTURYLINK_OUTBOUND] { $var(domain) = "voip.centurylink.com"; $var(user_tn) = "6467687570"; $var(pilot_tn) = "6467687572"; if ($rd =~ "$var(domain).*|65.149.22.7.*|65.149.23.7.*|65.149.24.7.*|65.149.25.7.*|216.206.64.7.*|216.206.64.71.*|216.206.64.91.*|216.206.66.7.*|216.206.66.71.*|216.206.66.91.*") { xlog("L_INFO", "centurylink carrier match\n"); $du = $ru; $rd = $var(domain); # Remove the port $rp = ""; # Change the from and to domain to match the carrier domain name $td = $var(domain); $fd = $var(domain); # Uncomment this line if you need to change the from user to the user_tn #$fU = $var(user_tn); # Uncomment this line if you need to change the domain of the contact - this is not recommended #subst('/^Contact: /ig') # Add P-Asserted-Identity per the carriers requirement append_hf("P-Asserted-Identity: \r\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes\n"); } } } route[ENRICH_CARRIER_SIGNALWIRE_INBOUND] { $var(domain_lookup) = ".+sip.signalwire.com"; xlog("L_DBG", "before transform:\n$mb\n"); if ($td =~ $var(domain_lookup)) { xlog("L_INFO", "signalwire carrier match\n"); # Change the "request username" to "to username" $rU = $tU; if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes\n"); } xlog("L_DBG", "after transform:\n$mb\n"); } } route[ENRICH_CARRIER_SIGNALWIRE_OUTBOUND] { $var(domain) = "sip.signalwire.com"; $var(domain_lookup) = ".+sip.signalwire.com"; $var(user) = $fU; xlog("L_DBG", "before transform:\n$mb\n"); if ($rd =~ $var(domain_lookup)) { xlog("L_INFO", "signalwire carrier match\n"); # Change the from domain to the request domain $fd = $rd; # Change the from user to the authenticated user $fU = $avp(auser); # Add the callerid append_hf("P-Asserted-Identity: \r\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes\n"); } xlog("L_DBG", "after transform:\n$mb\n"); } } route[ENRICH_CARRIER_INBOUND] { route(ENRICH_CARRIER_SIGNALWIRE_INBOUND); } # Carrier Enrichment route[ENRICH_CARRIER_OUTBOUND] { route(ENRICH_CARRIER_CENTURYLINK_OUTBOUND); route(ENRICH_CARRIER_SIGNALWIRE_OUTBOUND); } ####### CUSTOM_ROUTING_START ######### # add custom routes here ####### CUSTOM_ROUTING_END #########