diff --git a/README.md b/README.md index b6e8968..6fdfce5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ This project uses SIP.js in Node.js, and Kurento media server to enable SIP endp ![SIP Kurento architecture](https://raw.githubusercontent.com/havfo/Kurento-Nodejs-SIP/master/images/sipnode.png "SIP Kurento architecture") ## Installation -You need a SIP registrar/proxy that supports SIP over websockets. You need an account on this SIP server to register to. Configure credentials in `server.js`. The room it joins is specified by the `X-Room` SIP-header. +You need a SIP registrar/proxy that supports SIP over websockets. You need an account on this SIP server to register to. Configure credentials in `server.js`. The room it joins is specified by the `X-Room` SIP-header. You need a Kurento media server installation. Configure the settings in `server.js`. + +You can install Kamailio as the SIP registrar/proxy using the [WEBRTC-to-SIP](https://github.com/havfo/WEBRTC-to-SIP) and use the configuration file `config/kamailio.cfg` in this repository instead. Create the Node.js SIP user with `kamctl add mcu DFOdH1abdsTDCqp`. To install: ```bash diff --git a/config/kamailio.cfg b/config/kamailio.cfg new file mode 100644 index 0000000..297429a --- /dev/null +++ b/config/kamailio.cfg @@ -0,0 +1,744 @@ +#!KAMAILIO +# + +#!define WITH_MYSQL +#!define WITH_AUTH +#!define WITH_USRLOCDB +#!define WITH_TLS +#!define WITH_HOMER +#!define WITH_WEBSOCKETS +#!define WITH_ANTIFLOOD +#!define WITH_ALWAYS_BRIDGE + +#!substdef "!MY_IP_ADDR!1.1.1.1!g" +#!substdef "!MY_DOMAIN!meet.example.com!g" +#!substdef "!MY_WS_PORT!80!g" +#!substdef "!MY_WSS_PORT!443!g" +#!substdef "!MY_WS_ADDR!tcp:127.0.0.1:MY_WS_PORT!g" +#!substdef "!MY_WSS_ADDR!tls:127.0.0.1:MY_WSS_PORT!g" + +# *** Value defines - IDs used later in config +#!ifdef WITH_MYSQL +# - database URL - used to connect to database server by modules such +# as: auth_db, acc, usrloc, a.s.o. +#!ifndef DBURL +#!define DBURL "mysql://kamailio:kamailiorw@localhost/kamailio" +#!endif +#!endif + +# - flags +# FLT_ - per transaction (message) flags +# FLB_ - per branch flags +#!define FLT_NATS 5 + +#!define FLB_NATB 6 +#!define FLB_NATSIPPING 7 + +####### Global Parameters ######### + +### LOG Levels: 3=DBG, 2=INFO, 1=NOTICE, 0=WARN, -1=ERR +#!ifdef WITH_DEBUG +debug=4 +log_stderror=no +#!else +debug=2 +log_stderror=no +#!endif + +memdbg=5 +memlog=5 + +log_facility=LOG_LOCAL0 + +fork=yes +children=4 + +/* port to listen to + * - can be specified more than once if needed to listen on many ports */ +port=5060 + +#!ifdef WITH_TLS +enable_tls=yes +#!endif + +listen=MY_IP_ADDR +#!ifdef WITH_WEBSOCKETS +#listen=MY_WS_ADDR +#!ifdef WITH_TLS +listen=MY_WSS_ADDR +#!endif +#!endif + +use_dns_cache = on # Use KAMAILIO internal DNS cache +use_dns_failover = on # Depends on KAMAILIO internal DNS cache +dns_srv_loadbalancing = on # +dns_try_naptr = on # +dns_retr_time=1 # Time in seconds before retrying a DNS request +dns_retr_no=3 # Number of DNS retransmissions before giving up + +# Set protocol preference order - ignore target priority +dns_naptr_ignore_rfc= yes # Ignore target NAPTR priority +dns_tls_pref=50 # First priority: TLS +dns_tcp_pref=30 # Second priority: TCP +dns_udp_pref=10 # Third priority: UDP + +tcp_connection_lifetime=3604 +tcp_accept_no_cl=yes +tcp_rd_buf_size=16384 + + +# set paths to location of modules (to sources or installation folders) +#!ifdef WITH_SRCPATH +mpath="modules/" +#!else +mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules/" +#!endif + +#!ifdef WITH_MYSQL +loadmodule "db_mysql.so" +#!endif + +loadmodule "mi_fifo.so" +loadmodule "kex.so" +loadmodule "corex.so" +loadmodule "tm.so" +loadmodule "tmx.so" +loadmodule "sl.so" +loadmodule "rr.so" +loadmodule "pv.so" +loadmodule "maxfwd.so" +loadmodule "usrloc.so" +loadmodule "registrar.so" +loadmodule "textops.so" +loadmodule "siputils.so" +loadmodule "xlog.so" +loadmodule "sanity.so" +loadmodule "ctl.so" +loadmodule "cfg_rpc.so" +loadmodule "mi_rpc.so" +loadmodule "sdpops.so" +loadmodule "textopsx.so" + +#!ifdef WITH_AUTH +loadmodule "auth.so" +loadmodule "auth_db.so" +#!ifdef WITH_IPAUTH +loadmodule "permissions.so" +#!endif +#!endif + +#!ifdef WITH_PRESENCE +loadmodule "presence.so" +loadmodule "presence_xml.so" +#!endif + +#!ifdef WITH_TLS +loadmodule "tls.so" +#!endif + +#!ifdef WITH_HOMER +loadmodule "siptrace.so" +#!endif + +#!ifdef WITH_WEBSOCKETS +loadmodule "xhttp.so" +loadmodule "websocket.so" +loadmodule "nathelper.so" +loadmodule "rtpengine.so" +#!endif + +#!ifdef WITH_ANTIFLOOD +loadmodule "htable.so" +loadmodule "pike.so" +#!endif + +#!ifdef WITH_DEBUG +loadmodule "debugger.so" +#!endif + +# ----------------- setting module-specific parameters --------------- + + +# ----- mi_fifo params ----- +modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo") + +# ----- rr params ----- +# add value to ;lr param to cope with most of the UAs +modparam("rr", "enable_full_lr", 1) +# do not append from tag to the RR (no need for this script) +modparam("rr", "append_fromtag", 0) + + +# ----- registrar params ----- +modparam("registrar", "method_filtering", 1) +# max value for expires of registrations +modparam("registrar", "max_expires", 3600) + + +# ----- usrloc params ----- +/* enable DB persistency for location entries */ +#!ifdef WITH_USRLOCDB +modparam("usrloc", "db_url", DBURL) +modparam("usrloc", "db_mode", 2) +#!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") +modparam("auth_db", "load_credentials", "") +#!endif + +#!ifdef WITH_PRESENCE +# ----- presence params ----- +modparam("presence", "db_url", DBURL) + +# ----- presence_xml params ----- +modparam("presence_xml", "db_url", DBURL) +modparam("presence_xml", "force_active", 1) +#!endif + + +##!ifdef WITH_NAT +# ----- rtpproxy params ----- +modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:22222") + +# ----- nathelper params ----- +modparam("nathelper", "natping_interval", 30) +modparam("nathelper", "ping_nated_only", 1) +modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) +modparam("nathelper", "sipping_from", "sip:pinger@meeting.akademia.no") +modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") +modparam("usrloc", "nat_bflag", FLB_NATB) +##!endif + +# ----- corex params ----- +modparam("corex", "alias_subdomains", "MY_DOMAIN") + +#!ifdef WITH_TLS +# ----- tls params ----- +modparam("tls", "config", "/etc/kamailio/tls.cfg") +#!endif + +#!ifdef WITH_WEBSOCKETS +modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") +#!endif + +#!ifdef WITH_HOMER +#Siptrace +modparam("siptrace", "duplicate_uri", "sip:127.0.0.1:9060") +modparam("siptrace", "hep_mode_on", 1) +modparam("siptrace", "trace_to_database", 0) +modparam("siptrace", "trace_flag", 22) +modparam("siptrace", "trace_on", 1) +#!endif + +#!ifdef WITH_ANTIFLOOD +# ----- pike params ----- +modparam("pike", "sampling_time_unit", 2) +modparam("pike", "reqs_density_per_unit", 16) +modparam("pike", "remove_latency", 4) + +# ----- htable params ----- +# ip ban htable with autoexpire after 5 minutes +modparam("htable", "htable", "ipban=>size=8;autoexpire=300;") +#!endif + +#!ifdef WITH_DEBUG +# ----- debugger params ----- +modparam("debugger", "cfgtrace", 1) +#!endif + +####### Routing Logic ######## +request_route { + + #!ifdef WITH_HOMER + # start duplicate the SIP message here + sip_trace(); + setflag(22); + #!endif + + # per request initial checks + route(REQINIT); + +#!ifdef WITH_WEBSOCKETS + if (nat_uac_test(64)) { + # Do NAT traversal stuff for requests from 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. + force_rport(); + if (is_method("REGISTER")) { + fix_nated_register(); + } else { + if (!add_contact_alias()) { + xlog("L_ERR", "Error aliasing contact <$ct>\n"); + sl_send_reply("400", "Bad Request"); + exit; + } + } + } +#!endif + + route(POINT_TO_MCU); + + # NAT detection + route(NATDETECT); + + # CANCEL processing + if (is_method("CANCEL")) { + if (t_check_trans()) { + route(RELAY); + } + exit; + } + + # handle requests within SIP dialogs + route(WITHINDLG); + + ### only initial requests (no To tag) + + t_check_trans(); + + # authentication + route(AUTH); + + # record routing for dialog forming requests (in case they are routed) + # - remove preloaded route headers + remove_hf("Route"); + if (is_method("INVITE|SUBSCRIBE")) + record_route(); + + route(RTP_BRIDGE); + + # dispatch requests to foreign domains + route(SIPOUT); + + ### requests for my local domains + + # handle presence related requests + route(PRESENCE); + + # handle registrations + route(REGISTRAR); + + if ($rU==$null) { + # request with no Username in RURI + sl_send_reply("484","Address Incomplete"); + exit; + } + + # user location service + route(LOCATION); +} + +# Wrapper for relaying requests +route[RELAY] { + + # 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"); + } + + if (!t_relay()) { + sl_reply_error(); + } + exit; +} + +# Per SIP request initial checks +route[REQINIT] { +#!ifdef WITH_ANTIFLOOD + # flood dection from same IP and traffic ban for a while + # be sure you exclude checking trusted peers, such as pstn gateways + # - local host excluded (e.g., loop to self) + if(src_ip!=myself) { + if($sht(ipban=>$si)!=$null) { + # ip is already blocked + xdbg("request from blocked IP - $rm from $fu (IP:$si:$sp)\n"); + exit; + } + if (!pike_check_req()) { + xlog("L_ALERT","ALERT: pike blocking $rm from $fu (IP:$si:$sp)\n"); + $sht(ipban=>$si) = 1; + exit; + } + } +#!endif + + if (!mf_process_maxfwd_header("10")) { + sl_send_reply("483","Too Many Hops"); + exit; + } + + if(!sanity_check("1511", "7")) { + xlog("Malformed SIP message from $si:$sp\n"); + exit; + } +} + +# Handle requests within SIP dialogs +route[WITHINDLG] { + if (has_totag()) { + # sequential request withing a dialog should + # take the path determined by record-routing + if (loose_route()) { +#!ifdef WITH_WEBSOCKETS + if ($du == "") { + if (!handle_ruri_alias()) { + xlog("L_ERR", "Bad alias <$ru>\n"); + sl_send_reply("400", "Bad Request"); + exit; + } + } +#!endif + route(DLGURI); + if ( is_method("ACK") ) { + # ACK is forwarded statelessy + route(NATMANAGE); + } + else if ( is_method("NOTIFY") ) { + # Add Record-Route for in-dialog NOTIFY as per RFC 6665. + record_route(); + } + route(RELAY); + } else { + if (is_method("SUBSCRIBE") && uri == myself) { + # in-dialog subscribe requests + route(PRESENCE); + exit; + } + if ( is_method("ACK") ) { + if ( t_check_trans() ) { + # no loose-route, but stateful ACK; + # must be an ACK after a 487 + # or e.g. 404 from upstream server + route(RELAY); + exit; + } else { + # ACK without matching transaction ... ignore and discard + exit; + } + } + sl_send_reply("404","Not here"); + } + exit; + } +} + +route[POINT_TO_MCU] { + if (is_method("INVITE|SUBSCRIBE|UPDATE")) { + append_hf("X-Room: $rU\r\n", "Contact"); + $rU = "mcu"; + $tU = "mcu"; + } +} + +# Handle SIP registrations +route[REGISTRAR] { + if (is_method("REGISTER")) { + if(isflagset(FLT_NATS)) { + setbflag(FLB_NATB); + # uncomment next line to do SIP NAT pinging + ## setbflag(FLB_NATSIPPING); + } + if (!save("location")) + sl_reply_error(); + + exit; + } +} + +# USER location service +route[LOCATION] { + $avp(oexten) = $rU; + if (!lookup("location")) { + $var(rc) = $rc; + t_newtran(); + switch ($var(rc)) { + case -1: + case -3: + send_reply("404", "Not Found"); + exit; + case -2: + send_reply("405", "Method Not Allowed"); + exit; + } + } + + route(RELAY); + exit; +} + +# Presence server route +route[PRESENCE] { + if(!is_method("PUBLISH|SUBSCRIBE")) + return; + + if(is_method("SUBSCRIBE") && $hdr(Event)=="message-summary") { + # 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")) { + handle_publish(); + t_release(); + } else if(is_method("SUBSCRIBE")) { + 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; +} + +# Authentication route +route[AUTH] { +#!ifdef WITH_AUTH + if ($proto =~ "ws") { + if (is_method("REGISTER") || from_uri==myself) { + # authenticate requests + if (!auth_check("$fd", "subscriber", "1")) { + auth_challenge("$fd", "0"); + exit; + } + # user authenticated - remove auth header + if(!is_method("REGISTER|PUBLISH")) + consume_credentials(); + } + # if caller is not local subscriber, then check if it calls + # a local destination, otherwise deny, not an open relay here + if (from_uri!=myself && uri!=myself) { + sl_send_reply("403","Not relaying"); + exit; + } + } +#!endif + return; +} + +# Caller NAT detection route +route[NATDETECT] { + force_rport(); + if (nat_uac_test("19")) { + if (is_method("REGISTER")) { + fix_nated_register(); + } else { + if(is_first_hop()) + set_contact_alias(); + } + setflag(FLT_NATS); + } + return; +} + +# NAT handling +route[NATMANAGE] { + if (is_request()) { + if(has_totag()) { + if(check_route_param("nat=yes")) { + setbflag(FLB_NATB); + } + } + } + if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB))) + return; + + if (is_request()) { + if (!has_totag()) { + if(t_is_branch_route()) { + add_rr_param(";nat=yes"); + } + } + } + if (is_reply()) { + if(isbflagset(FLB_NATB)) { + if(is_first_hop()) + set_contact_alias(); + } + } + return; +} + +# URI update for dialog requests +route[DLGURI] { + if(!isdsturiset()) { + handle_ruri_alias(); + } + return; +} + +# Routing to foreign domains +route[SIPOUT] { + if (!uri==myself) { + append_hf("P-hint: outbound\r\n"); + route(RELAY); + } +} + +route[RTP_BRIDGE] { + #!ifdef WITH_ALWAYS_BRIDGE + if (is_method("INVITE")) { + xlog("L_INFO", "SIP -> WebRTC, bridging RTP->SRTP and adding ICE"); + rtpengine_manage("trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove RTP/AVP"); + t_on_reply("REPLY_FROM_WS"); + } + #!endif +} + +# manage outgoing branches +branch_route[MANAGE_BRANCH] { + xdbg("new branch [$T_branch_idx] to $ru\n"); + route(NATMANAGE); +} + +onreply_route[REPLY_TO_WS] { + xlog("L_INFO", "Reply from softphone: $rs"); + + if (t_check_status("183")) { + change_reply_status("180", "Ringing"); + remove_body(); + exit; + } + + if(!(status=~"[12][0-9][0-9]") || !(sdp_content())) + return; + + rtpengine_manage(); + + route(NATMANAGE); +} + +onreply_route[REPLY_FROM_WS] { + xlog("L_INFO", "Reply from webrtc client: $rs"); + + if(status=~"[12][0-9][0-9]") { + rtpengine_manage(); + route(NATMANAGE); + } +} + +# manage incoming replies +onreply_route[MANAGE_REPLY] { + xdbg("incoming reply\n"); + if(status=~"[12][0-9][0-9]") + route(NATMANAGE); +} + +# manage failure routing cases +failure_route[MANAGE_FAILURE] { + xlog("L_INFO", "Failure: $rs"); + #!ifndef WITH_ALWAYS_BRIDGE + if (t_check_status("488") && sdp_content()) { + if ($ru =~ "transport=ws") { + xlog("L_INFO", "WebRTC client responded 488 Not Supported Here, bridging RTP->SRTP and adding ICE"); + rtpengine_offer("trust-address replace-origin replace-session-connection ICE=force rtcp-mux-accept rtcp-mux-offer UDP/TLS/RTP/SAVPF"); + t_on_reply("REPLY_FROM_WS"); + } else if ($proto =~ "ws") { + xlog("L_INFO", "SIP client at the other end responded 488 Not Supported Here, bridging SRTP->RTP and removing ICE"); + rtpengine_offer("trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove RTP/AVP"); + t_on_reply("REPLY_TO_WS"); + } + + append_branch(); + route(RELAY); + } + #!endif +} + +#!ifdef WITH_WEBSOCKETS +onreply_route { + if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT) + && !(proto == WS || proto == WSS))) { + xlog("L_WARN", "SIP response received on $Rp\n"); + drop; + } + + 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(); + } +} + +event_route[xhttp:request] { + set_reply_close(); + set_reply_no_connect(); + + if ($Rp != MY_WS_PORT +#!ifdef WITH_TLS + && $Rp != MY_WSS_PORT +#!endif + ) { + xlog("L_WARN", "HTTP request received on $Rp\n"); + xhttp_reply("403", "Forbidden", "", ""); + exit; + } + + xlog("L_DBG", "HTTP Request Received\n"); + + if ($hdr(Upgrade)=~"websocket" + && $hdr(Connection)=~"Upgrade" + && $rm=~"GET") { + + # Validate Host - make sure the client is using the correct + # alias for WebSockets + if ($hdr(Host) == $null || !is_myself("sip:" + $hdr(Host))) { + xlog("L_WARN", "Bad host $hdr(Host)\n"); + xhttp_reply("403", "Forbidden", "", ""); + exit; + } + + # Optional... validate Origin - make sure the client is from an + # authorised website. For example, + # + # if ($hdr(Origin) != "https://example.com" + # && $hdr(Origin) != "https://example.com") { + # xlog("L_WARN", "Unauthorised client $hdr(Origin)\n"); + # xhttp_reply("403", "Forbidden", "", ""); + # exit; + # } + + # Optional... perform HTTP authentication + + # ws_handle_handshake() exits (no further configuration file + # processing of the request) when complete. + if (ws_handle_handshake()) + { + # Optional... cache some information about the + # successful connection + exit; + } + } + + xhttp_reply("404", "Not Found", "", ""); +} + +event_route[websocket:closed] { + xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n"); +} +#!endif