Friday, August 29, 2014

Звонок с сайта. Таки доделали.

Вот примерно такой сервис сделали....
Сейчас звонит только на мою голосовую почту, не так много у меня денег, чтобы напрямую на мобильный мне звонило, а SIP я с собой не таскаю постоянно :)
Можете оставлять сообщения, они мне на почту будут приходить :)

позвонить

Monday, June 23, 2014

Примитивный L7 DPI на iptables

Оказывается, начиная с ядра 2.6.14 в Debian (Ubuntu) iptables умеет анализировать пакет текстово с помощью модуля string.
Поэтому можно сделать некий примитивный DPI. У меня есть задача не пропускать на сервер только пакеты SIP REGISTER и INVITE (INVITE допускается только с определенных адресов). Поэтому родилось что-то типа

iptables -A INPUT -m string --string "INVITE sip:" --algo bm -i eth0 -s X.X.X.X/255.255.255.255 -p udp --dport 5060 -j ACCEPT
iptables -A INPUT -m string --string "INVITE sip:" --algo bm -i eth0 -p udp --dport 5060 -j DROP
iptables -A INPUT -m string --string "INVITE sip:" --algo bm -i eth0 -p tcp --dport 5060 -j DROP
iptables -A INPUT -m string --string "REGISTER sip:" --algo bm -i eth0 -p udp --dport 5060 -j DROP
iptables -A INPUT -m string --string "REGISTER sip:" --algo bm -i eth0 -p tcp --dport 5060 -j DROP



По мотивам: http://www.linuxquestions.org/questions/linux-networking-3/iptables-rules-against-udp-flood-and-ddos-attack-789950/

Wednesday, April 2, 2014

Попытки сделать полноценный SIP SBC или OpenSIPS в руках....

Итак.... История не стоит на месте и я тоже. Выпала задачка сделать масштабируемый и (возможно в будущем) высоконагруженный SBC для всяческих сервисов.
Схема, к которой я пришел не отличается оптимизированностью, зато довольно проста в масштабировании (я надеюсь) и эксплуатации.
Схема:

WAN <->  OpenSIPS (1.8) - - Asterisk (any)
                          |                 |
WAN <-> FreeSwitch (1.2) - ---------

Роли:
OpenSIPS - регистрация пользователей, регистрация на внешних сервисах, балансировка между FreeSwitch и Asterisk
FreeSwitch - Meдиа-прокси, при необходимости - транскодинг, скрытие топологии. Количество ограничено внешними IP-адресами.
Asterisk - Собственно, сервер приложений типа голосовой почты и прочего. Количество - в разумных пределах :). Почему, собственно, Asterisk? Потому что человек, который пишет под него приложения, знает AGI.
Схема работы примерно такая:
Пользователь набирает какой-то номер -> OpenSIPS проверяет авторизации и прочее и перенаправляет звонок по WAN на один из FreeSwith'ей -> FreeSwitch просто делает "заворот" WAN - LAN и отправляет звонок обратно на OpenSIPS - > OpenSIPS, видя, что звонок пришел от одного из FreeSwith'ей, балансирует его на один из Астерисков.

Собственно, сигнализация при таком звонке ходит так:
Пользователь (NAT/WAN) - (WAN) OpenSIPS (WAN) - (WAN) FreeSwitch (LAN) - (LAN) OpenSIPS (LAN) - (LAN) Asterisk.
Медиа же ходит так:
Пользователь (NAT/WAN) - (WAN) FreeSwitch (LAN) - (LAN) Asterisk.
 Т.е. в минимальной комплектации нужно 2 внешних IP адреса.

Далее пойдет файлик opensips.cfg с некоторыми комментариями:

#
# $Id: opensips.cfg 8758 2012-02-29 11:59:26Z vladut-paiu $
#
# OpenSIPS residential configuration script
#     by OpenSIPS Solutions
#
# This script was generated via "make menuconfig", from
#   the "Residential" scenario.
# You can enable / disable more features / functionalities by
#   re-generating the scenario with different options.#
#
# Please refer to the Core CookBook at:
#      http://www.opensips.org/Resources/DocsCookbooks
# for a explanation of possible statements, functions and parameters.
#


####### Global Parameters #########

debug=3
log_stderror=no
log_facility=LOG_LOCAL7

fork=yes
children=4

auto_aliases=yes

listen=udp:172.16.3.8:5060
listen=udp:XXX:5060
listen=udp:XXX:5060

#port=5060
disable_tcp=yes

####### Modules Section ########

#set module path
mpath="/usr/lib/opensips/modules/"


#### SIGNALING module
loadmodule "signaling.so"

#### StateLess module
loadmodule "sl.so"

#### MySQL module
loadmodule "db_mysql.so"


loadmodule "regex.so"
loadmodule "textops.so"
loadmodule "auth.so"
loadmodule "auth_db.so"
loadmodule "domain.so"
loadmodule "alias_db.so"

#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timeout", 5)
modparam("tm", "fr_inv_timeout", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)


#### Record Route Module
loadmodule "rr.so"
modparam("rr", "append_fromtag", 1)

#### MAX ForWarD module
loadmodule "maxfwd.so"

#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"

#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)

#### URI module
loadmodule "uri.so"
modparam("uri", "use_uri_table", 0)

#### USeR LOCation module
loadmodule "usrloc.so"
modparam("usrloc", "nat_bflag", "NAT_BFLAG")
modparam("usrloc", "db_mode",   2)
modparam("usrloc", "matching_mode", 1)
modparam("usrloc", "cseq_delay", 5)
modparam("usrloc", "accept_replicated_contacts", 0)
modparam("usrloc", "skip_replicated_db_ops", 1)

#### User Agent Client module
loadmodule "uac_auth.so"
loadmodule "uac.so"
loadmodule "uac_registrant.so"
modparam("uac","restore_mode","auto")
modparam("uac_registrant", "timer_interval", 120)
modparam("uac_registrant", "hash_size", 2)


#### REGISTRAR module
loadmodule "registrar.so"

modparam("registrar", "received_avp", "$avp(42)")


/* uncomment the next line not to allow more than 10 contacts per AOR */
#modparam("registrar", "max_contacts", 10)

# ----- auth_db params -----
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "load_credentials", "")

# ----- domain params -----

#modparam("domain", "db_url",DBURL)
modparam("domain", "db_mode", 1)   # Use caching

#modparam("alias_db", "db_url", DBURL)

# ----- multi-module params -----
/* uncomment the following line if you want to enable multi-domain support
   in the modules (dafault off) */

# ----- Dialog params ----
loadmodule "dialog.so"
modparam("dialog", "enable_stats", 0)


# ------ NAT Traversal Module ----

loadmodule "nat_traversal.so"
modparam("nat_traversal", "keepalive_interval", 60)
modparam("nat_traversal", "keepalive_method", "OPTIONS")
modparam("nat_traversal", "keepalive_from", "sip:pinger@XXX")
modparam("nat_traversal", "keepalive_extra_headers", "X-KeepAliveHeader: Is there any out there?\r\n")
modparam("nat_traversal", "keepalive_state_file", "/tmp/opensips_keepalive_state")

# ----- Load-Balancer params ---- Switched to stateless dispatcher
#loadmodule "load_balancer.so"
#modparam("load_balancer", "db_url", DBURL)
#modparam("load_balancer", "probing_interval", 60)
#modparam("load_balancer", "probing_reply_codes", "404, 200, 481")
#modparam("load_balancer", "probing_method", "INFO")


# ----- DISPATCHER Module ------
loadmodule "dispatcher.so"
modparam("dispatcher", "flags", 3)
modparam("dispatcher", "attrs_avp", "$avp(attrs_avp)")
modparam("dispatcher", "ds_ping_method", "INFO")
modparam("dispatcher", "ds_ping_from", "sip:dispatcher@XXX")
modparam("dispatcher", "ds_ping_interval", 30)
modparam("dispatcher", "ds_probing_threshhold", 10)
modparam("dispatcher", "options_reply_codes", "404, 200, 481")


# ------AVPops Module params ------
loadmodule "avpops.so"


# ------Stun Module -------

loadmodule "stun.so"
modparam("stun", "primary_ip", "XXX")
modparam("stun", "alternate_ip", "172.16.3.8")

# ------Presence User Agent Module -----

loadmodule "xcap.so"
loadmodule "xcap_client.so"

loadmodule "pua.so"
loadmodule "pua_mi.so"
loadmodule "pua_dialoginfo.so"
loadmodule "presence.so"
loadmodule "presence_dialoginfo.so"
loadmodule "presence_xml.so"
loadmodule "presence_mwi.so"
loadmodule "presence_xcapdiff.so"
loadmodule "rls.so"
loadmodule "mi_xmlrpc.so"

modparam("xcap", "integrated_xcap_server", 1)
modparam("xcap_client", "periodical_query", 0)

modparam("mi_xmlrpc", "port", 8888)

modparam("presence", "server_address", "sip:precense@XXX:5060")
modparam("presence", "fallback2db", 1)
modparam("presence", "clean_period",  30)

modparam("presence_xml", "force_active", 0)
modparam("presence_xml", "pidf_manipulation", 1)

modparam("rls", "server_address", "sip:rls@XXX:5060")
modparam("rls", "to_presence_code", 5)

modparam("pua_dialoginfo", "presence_server", "sip:precense@XXX:5060")


# ---- DBURL for all modules ------

modparam("avpops|alias_db|domain|auth_db|uac_registrant|usrloc|dialog|pua|presence|xcap|dispatcher", "db_url", "mysql://opensips:XXX@db1.local/opensips")

####### Routing Logic ########
# main request routing logic

route{

# ### Here will be list of proxies. Yes, it's duplicating with MySQL base, but I've not figured what to be best.
# may be here to make db queries.... Ok, let's make some tests later;

# Group 1
        $var(external_media_proxy) = "XXX";
# Group 2
        $var(internal_media_proxy) = "172.16.3.2";
# Group 3
        $var(media_server) = "172.16.3.16";
# Group 4
    $var(webrtc_proxy) = "172.16.3.7";

# ### Var ends


Небольшая защита от сканеров




        if (search("friendly-scanner|sipvicious|sipcli*")) {
        xlog("L_INFO", "IS_FRIENDLYSCANNER_MESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        exit;
    }

        if (search_body("friendly-scanner|sipvicious|sipcli")) {
        xlog("L_INFO", "IS_FRIENDLYSCANNER_BODY: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        exit;
    }


   

    if (!mf_process_maxfwd_header("10")) {
                xlog("L_INFO", "TOO_MANY_HOPS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                exit;
        }


Этот кусок работает как-то странно и часто отвечает 503. Особенно в случае doubango webrtc2sip, хотя он уже не используется. На период тестов он закомменчен.


#        if (msg:len >= 4096 ) {
#                xlog("L_INFO", "MESSAGE_TOO_BIG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
#                sl_send_reply("513", "Message too big");
#                exit;
#        }

        force_rport();

        if (client_nat_test("3")) {
                xlog("L_INFO", "CLIENT_NAT_TEST: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        fix_contact();
                if (is_method("REGISTER|SUBSCRIBE")) {
                        xlog("L_INFO", "CLIENT_NAT_TEST_REGISTERSUBSCRIBEINVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        nat_keepalive();
                }
        }   


    #Precense and BLF part....

    if (uri==myself) {
        if( is_method("PUBLISH|SUBSCRIBE|NOTIFY")) {
            route(PRESENCE);
        }
    }

        #Client behind NAT
   
        route(TO_TAG);

        route(CANCEL);

    route(IS_FROM_MEDIA_INTERNAL);

    route(IS_FROM_MEDIA_EXTERNAL);

    route(IS_FROM_MEDIASERVER);
   
    route(IS_FROM_WEB);

    route(IS_FROM_PROVIDER);

  
        # authenticate if from local subscriber. Works for local users, but not for REGISTER
        if (!(method =="REGISTER") && is_from_local()) {
                xlog("L_INFO", "NOTREGISTRATIONANDLOCAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (!proxy_authorize("", "subscriber")) {
                        proxy_challenge("", "0");
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_NOTPROXYAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        exit;
                } else {
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_PROXYAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                if (!db_check_from()) {
                        send_reply("403","Forbidden auth ID");
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_NOTDBCHECKFROM: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        exit;
                } else {
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_DBCHECKFROM: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_CONSUMECREDENTIALS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                consume_credentials();
                # caller authenticated
        }

        # preloaded route checking
        route(LOOSE_ROUTE);

        # record routing
        if (!is_method("REGISTER|MESSAGE")) {
                xlog("L_INFO", "NOTREGISTERORMESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                record_route();
        }

        # account only INVITEs
        #if (is_method("INVITE")) {
        #        xlog("L_INFO", "INVITEACCOUNTING: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        #        setflag(1); # do accounting
        #}

        # if not a targetting a local SIP domain, just send it out
        # based on DNS (calls to foreign SIP domains)
        if (!is_uri_host_local()) {
                xlog("L_INFO", "NOTLOCALURI: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                append_hf("P-hint: outbound\r\n");
                t_on_failure("1");
                resetflag(8);
                route(1);
        }


        # requests for my domain
        # Register users on OpenSIPS

        if (is_method("REGISTER")) {
                xlog("L_INFO", "REGISTER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                # authenticate the REGISTER requests
                if (!www_authorize("", "subscriber")) {
                        xlog("L_INFO", "REGISTER_NOTWWWAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        www_challenge("", "0");
                        exit;
                } else {
                        xlog("L_INFO", "REGISTER_WWWAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                if (!db_check_to()) {
                        xlog("L_INFO", "REGISTER_NOTDBCHECKTO: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("403","Forbidden auth ID");
                        exit;
                } else {
                        xlog("L_INFO", "REGISTER_DBCHECKTO: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }

                if (!save("location")) {
                        xlog("L_INFO", "REGISTER_NOTSAVELOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        sl_reply_error();
                } else {
                        xlog("L_INFO", "REGISTER_SAVELOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                exit;
        }

        # Some error processing
        route(ERROR);
        #Change aliases
        if(alias_db_lookup("dbaliases")) {
                xlog("L_INFO", "ALIASDBLOOKUP: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        }


        # Nave we found our user?
        if (!lookup("location")) {
                xlog("L_INFO", "NOTLOOKUPLOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                # Let's try here to loadbalance it to mediaproxy :)
                if (is_method("INVITE")) {
                        xlog("L_INFO", "LOOKUPLOCATION_INVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        ds_select_dst("1", "4");
                } else {
                        xlog("L_INFO", "LOOKUPLOCATION_NOTINVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        if (is_method("OPTIONS")) {
                                #Answer OPTIONS anyway
                                xlog("L_INFO", "LOOKUPLOCATION_NOTINVITE_OPTIONS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                sl_send_reply("200", "ALIVE OK");
                        }
                        exit;
                }
        } else {
                xlog("L_INFO", "LOOKUPLOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        }

        # when routing via usrloc, log the missed calls also
        #setflag(2);

        # arm a failure route in order to catch failed calls
        # targeting local subscribers; if we fail to deliver
        # the call to the user, we send the call to voicemail
        t_on_failure("1");
        resetflag(8);

        route(1);
}

route[1] {
        # Just relay it forward....
        if (!t_relay()) {
                xlog("L_INFO", "NOTTRELAY: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                sl_reply_error();
        }
    if (is_method("OPTIONS")) {
        xlog("L_INFO", "OPTIONS_ANSWER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        sl_send_reply("200", "ALIVE OK");
    }
        exit;
}


route[TO_TAG] {
        if (has_totag()) {
                xlog("L_INFO", "HAS_TO_TAG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                # sequential request withing a dialog should
                # take the path determined by record-routing
                if (loose_route()) {
                        route(1);
                } else {
                        if ( is_method("ACK") ) {
                                xlog("L_INFO", "HAS_TO_TAG_NOTLR_ACK: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                if ( t_check_trans() ) {
                                        # non loose-route, but stateful ACK; must be an ACK after
                                        # a 487 or e.g. 404 from upstream server
                                        xlog("L_INFO", "HAS_TO_TAG_NOTLR_ACK_TRANS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                        t_relay();
                                        exit;
                                } else {
                                        # ACK without matching transaction ->
                                        # ignore and discard
                                        xlog("L_INFO", "HAS_TO_TAG_NOTLR_ACK_NOTTRANS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                        exit;
                                }
                        }
                        xlog("L_INFO", "HAS_TO_TAG_NOTLR: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("404","Not here");
                }
                exit;
        }
}


route[CANCEL] {
        # CANCEL processing
        if (is_method("CANCEL")) {
                xlog("L_INFO", "CANCEL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (t_check_trans())
                        t_relay();
                exit;
        }

        if (t_check_trans()) {
                xlog("L_INFO", "TRANSACTION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        }

}

route[LOOSE_ROUTE] {
        if (loose_route()) {
                xlog("L_INFO", "LOOSEROUTE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (!is_method("ACK"))
                        xlog("L_INFO", "LOOSEROUTE_ACK: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("403","Preload Route denied");
                exit;
        }
}

route[ERROR] {
        if ($rU==NULL) {
                # request with no Username in RURI
                xlog("L_INFO", "RURIISNULL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("OPTIONS")) {
                        xlog("L_INFO", "RURIISNULL_OPTIONS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("200","ALIVE OK");
                }
                else {
                        send_reply("484","Address Incomplete");
                }
                exit;
        }
}


failure_route[1] {
        # authentication reply received?
        if ( t_check_status("40[17]") ) {
                # have we already tried to authenticate?
                if (isflagset(8)) {
                        t_reply("503","Authentication failed");
                        exit;
                }

Кусок, который отвечает за регистрацию на внешних сервисах. Т.е. ту последовательность REGISTER - 401 - REGISTER (digest) - 200
                if (uac_auth()) {
                        # mark that auth was performed
                        setflag(8);
                        # trigger again the failure route
                        t_on_failure("1");
                        # repeat the request with auth response this time
                        append_branch();
                        t_relay();
                }
        }

        if (t_was_cancelled()) {
                xlog("L_INFO", "FAILUREROUTE_TWASCANCELLED: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                exit;
        }

        # if the failure code is "408 - timeout" or "486 - busy",
        if (t_check_status("486|408")) {
                xlog("L_INFO", "FAILUREROUTE_TCHECKSTATUS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                exit;
        }
}


route[ROUTINE] {

        route(CANCEL);
        route(LOOSE_ROUTE);
        route(ERROR);
}


route[IS_FROM_MEDIA_INTERNAL] {
        if ($(var(internal_media_proxy){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_MEDIA_INTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("REGISTER|MESSAGE")) {
                        sl_send_reply("500", "Method not allowed");
                }
                route(ROUTINE);

                if (is_method("INVITE")) {
                        ds_select_dst("3", "4");
                }
                route(1);
                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_MEDIA_INTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}

route[IS_FROM_MEDIA_EXTERNAL] {
        if ($(var(external_media_proxy){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_MEDIA_EXTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci] + DD = $dd / $ds");
                if (is_method("REGISTER|MESSAGE")) {
                        sl_send_reply("500", "Method not allowed");
            exit;
                }
                route(ROUTINE);

        if (is_method("INVITE")) {
            if (!registered("location") && is_uri_host_local()) {
                xlog("L_INFO", "IS_FROM_MEDIA_EXTERNAL_INVITE_USER_NOTREG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                sl_send_reply("404", "User not found");
                exit;
            } else {
                xlog("L_INFO", "IS_FROM_MEDIA_EXTERNAL_INVITE_USERREG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            }
        }
   

                route(1);

                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_MEDIA_EXTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}

route[IS_FROM_MEDIASERVER] {
        if ($(var(media_server){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_MEDIASERVER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("REGISTER|MESSAGE")) {
                        sl_send_reply("500", "Method not allowed");
            exit;
                }

                route(ROUTINE);

Тут разделение. В качестве фронта для webrtc выступает FreeSwitch, и на нем регаются клиенты с префиксом web_

                if (is_method("INVITE")) {
            if (pcre_match("$oU","^web_")) {
                            ds_select_dst("4", "4");
            } else {
                            ds_select_dst("2", "4");
            }
                }
                route(1);

                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_MEDIASERVER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}


Для провайдеров заведена отдельная табличка.

route[IS_FROM_PROVIDER] {
    avp_db_query("SELECT IF( EXISTS( SELECT name FROM providers WHERE ip_address = INET_ATON('$si')), 1, 0)","$avp(is_provider)");
    if ($avp(is_provider)) {
        xlog("L_INFO", "IS_FROM_PROVIDER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        # Here will be some ideas about providers not handling DID. I really don't know what to do so. Test will tell us
        if (is_method("REGISTER|MESSAGE")) {
            xlog("L_INFO", "IS_FROM_PROVIDER_REGISTERORMESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            sl_send_reply("500", "Method not allowed");
        }
        route(ROUTINE);
        if (is_method("INVITE")) {
            xlog("L_INFO", "IS_FROM_PROVIDER_INVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            ds_select_dst("1", "4");
        }
        route(1);
        exit;
    }
    xlog("L_INFO", "IS_NOTFROM_PROVIDER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}


route[IS_FROM_WEB] {
        if ($(var(webrtc_proxy){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_WEB: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("REGISTER|MESSAGE")) {
            xlog("L_INFO", "IS_FROM_WEB_REGISTERORMESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        sl_send_reply("500", "Method not allowed");
            exit;
                }

                route(ROUTINE);

                if (is_method("INVITE")) {
            xlog("L_INFO", "IS_FROM_WEB_INVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        ds_select_dst("3", "4");
                }
                route(1);

                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_WEB: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");

}

route[PRESENCE] {
        if (!t_newtran()) {
            xlog("L_INFO", "PRESENCE_NOTNEWTRAN: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            sl_reply_error();
            exit;
        };

        if(is_method("PUBLISH")) {
            xlog("L_INFO", "PRESENCE_PUBLISH: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            handle_publish();
        } else {
            if(is_method("SUBSCRIBE")) {
                xlog("L_INFO", "PRESENCE_SUBSCRIBE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                rls_handle_subscribe();
                handle_subscribe();
            }
        }
        exit;
}



Да, тут много лишнего и некрасивого, но работа идет. В перспективе - интеграция с OpenXCAP для пристутствия и поддержка SIP_MESSAGE.
Но топология скрывается и звук ходит, что меня не может не радовать.

https://code.google.com/p/opensips-freeswitch-sbc/source/browse/ - Тут мои конфиги для opensips и freeswitch

-- upd.
Залита новая версия конфига. Добавлена авторизация провайдера по IP, добавлена некая защита от сканеров. Пока нету теста при регистрациях на внешних серверах. Также добавлена поддержка BLF (не проверено)
-- upd 2
Залит новый конфиг. Миграция на 1.11,  load_balancer сменен на dispatcher (потому что stateless), nathelper -> nat_traversal (потому что rtpproxy не используется), выпилен модуль accounting. Ну и мелкие изменения по ходу дела.
-- upd 3
На Гуглокод залит только конфиг OpenSIPS 1.11, теперь он может работать как  proxy-auth, т.е. полностью на себя берет вопрос аутентификации при звонке на внешние сервисы. Для этого пришлось сделать еще одну табличку в mysql с параметрами username, password, realm.

Thursday, February 21, 2013

LiveStreaming для тех, кого там нет

Возникла немного нетипичная задачка - устроить людям вещание в интернете. Для этого можно использовать много средств, но мы будем использовать Flussonic - в бесплатной версии он умеет все, что нам надо. Запись - это дело десятое.
После долгих мучений и репозиториев был выбран дистрибутив Ubuntu Server 12.04.2 LTS.

Устанавливается крайне просто:

echo "deb http://binaries.erlang-solutions.com/debian precise contrib"  >> /etc/apt/sources.list
echo "deb http://debian.erlyvideo.org binary/" >> /etc/apt/sources.list
wget -O - http://binaries.erlang-solutions.com/debian/erlang_solutions.asc | sudo apt-key add -
wget -q -O - http://debian.erlyvideo.org/binary/gpg.key | apt-key add -
apt-get update
apt-get -y install esl-erlang flussonic

Итак, источников вещания у нас 2.

1. IP-камера Axis 210 (Такое себе старье без H.264).

 Будем вещать через RTSP. Ее надо перекодировать. Для этого будем использовать VLC

apt-get install vlc x264

VLC выкладывает http поток на свой порт 9090, оттуда  его забирает Flussonic и отдает JwPlayer'у RTSP поток.

файл startvlc.sh

#!/bin/sh

TR='transcode{venc=x264{cabac=yes,bframes=0,keyint=125,qpmin=20,qpmax=50,ref=5,merange=24,mixed-refs=yes,direct=auto,me=umh,subme=7,trellis=2,weightb=yes,partitions=all,vbv-maxrate=920,vbv-bufsize=9000,ratetol=100.0,scenecut=60},vb=820,,width=720,height=576,hurry-up,audio-sync,deinterlace=yes,acodec=aac,aenc=avcodec{strict=-2},ab=64,channels=2,samplerate=48000}'

OUT="std{access=http,mux=ts{use-key-frames=1},dst=:9090}"

cvlc -vvvv rtsp://root:passw@CAMERA-IP/mpeg4/media.amp --intf dummy --sout '#'${TR}:${OUT}


echo "{stream, "vlc.ts", "tshttp://VLC-IP:9090/"}."  >> /etc/flussonic/flussonic.conf
/etc/init.d/flussonic start

После этого в панели по адресу FLUSSONIC-IP:8080 убедимся, что поток виден и его можно посмотреть.

Примерный код для index.html

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js">
  </script>
  <script type="text/javascript">
  var flashvars = {
    file : "vlc.ts",
    streamer:'rtmp://FLUSSONIC-IP:1935',
    'rtmp.tunneling':false,
    autostart: true
  };
  var paramObj = {allowScriptAccess : "always", allowFullScreen : "true", allowNetworking : "all"};
  swfobject.embedSWF("http://FLUSSONIC-IP:8080/flu/jwplayer.swf", "video", 720, 576,
    "10.3", "/flu/expressInstall.swf",
    flashvars, paramObj, {name: "jwplayer"});
  </script>

 2. Adobe FLME.

Будем забирать по HDS (что бы это ни значило :) )
Тут немного проще, указываем FMLE в адресе сервера наш flussonic в виде
Адрес: rtmp://FLUSSONIC-IP/live
Поток: livestream

AFLME выкладывает RTMP поток на Flussonic, оттуда он рестримит по HDS плееру.

В панели по адресу FLUSSONIC-IP:8080 убедимся, что поток виден и его можно посмотреть.

 Примерный код для index.html

 <div id="video"></div>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js">
  </script>
  <script type="text/javascript">
  var flashvars = {
    src : "http://FLUSSONIC-IP:8080/live/livestream/manifest.f4m",
    autoPlay: true
  };
  var paramObj = {allowScriptAccess : "always", allowFullScreen : "true", allowNetworking : "all"};
  swfobject.embedSWF("http://FLUSSONIC-IP:8080/flu/StrobeMediaPlayback.swf", "video", 720, 576,
    "10.3", "/flu/expressInstall.swf",
    flashvars, paramObj, {name: "StrobeMediaPlayback"});
</script>


 Вообще, советую приобщиться к инструкциям на сервере проекта :) Для знающих людей они будут полезнее моих :)

Wednesday, February 20, 2013

Экзотика с кодеками или silk+amrnb в Asterisk

Возникла задачка гонять голос по 3G сетям. После долгих и не особенно продуктивных мучений были выбраны кодеки speex и g729, как кандидаты на релиз. amr-nb патч существовал для версии 1.8, у меня же по разным причинам используется 10ка.
Но выбравшись из консоли и взглянув на мир, выяснилось, что для 10ки можно использовать кодек silk, он же используется для Skype и с недавнего времени стал open-source. Плюс, добрый человек в Одессе сделал патч для поддержки amrnb в 10й версии Астериска.

Все собиралось и тестировалось на Asterisk 10.12.1, Debian 6.0 x64 (OpenVZ)

1. SILK
Собирался по мотивам https://github.com/mordak/codec_silk
К стандартному набору для компилирования необходимо добавить curl. Это для работы скрипта, но никто не мешает скачать все руками.

Из отличий - в install_silk.sh заменил 


ln -sf "SILK_SDK_SRC_FIX_v$SKYPE_SILK_VER" silk
на  ln -sf "SILK_SDK_SRC_FLP_v$SKYPE_SILK_VER" silk Почему-то версия с фиксированной точкой у меня собираться отказывалась.
Также в astdir/codecs/silk/Makefile заменить
CFLAGS  += -Wall -enable-threads -O3
на
CFLAGS  += -Wall -enable-threads -O3 -fPIC
После этого все как обычно собирается.
После инсталляции в файл /etc/asterisk/codecs.conf добавить

[silk8]
type=silk
samprate=8000
fec=true
packetloss_percentage=10
maxbitrate=20000
dtx=false

[silk12]
type=silk
samprate=12000
fec=true
packetloss_percentage=10
maxbitrate=25000
dtx=false

[silk16]
type=silk
samprate=16000
fec=true
packetloss_percentage=10
maxbitrate=30000
dtx=false

[silk24]
type=silk
samprate=24000
fec=true
packetloss_percentage=10
maxbitrate=40000
dtx=false

Ну и в консоли.: asterisk -rx "core restart now"
И да, я знаю o бинарниках от Digium, но это не путь истинного джедая. Плюс, тот же добрый человек из Одессы упрекает их в глючности и не верить ему у меня оснований нет.

2. AMR-NB
Качаем патч с этой странички. Единственное что, если silk не компилируется вручную, то падает 2й hunk при патче. Просто в /codecs/Makefile необходимо добавить строчку
$(if $(filter codec_amr,$(EMBEDDED_MODS)),modules.link,codec_amr.so): $(LIBAMR)
После этого разархивируем в папку /codecs/amr содержимое следующего архива.
Переходим в папку /codecs/amr

./configure CFLAGS = "-g -O2 -fPIC"
make

После этого все как обычно собирается.
После инсталляции в файл /etc/asterisk/codecs.conf добавить

[amr]
octet-aligned=0
;codec_amr: Must be one of MR475, MR515, MR59, MR67, MR74, MR795, MR102, MR122
mode = MR475
dtx=0
cng = 1
vad = 0

octet-aligned=0 - Это следствие того, что CSipSimple (точнее, pjsip) умеет  AMR только в Bandwidth-Efficient режиме. Что это такое, можно почитать в RFC.
Ну и в консоли.: asterisk -rx "core restart now"
Теперь по core show translation  можно увидеть, что у нас появился целый набор silk'ов и amr.

Friday, December 14, 2012

A2Billing. Хочу быть провайдером.

Итак... Захотелось мне стать провайдером, благо дешевую международку я нашел. Теперь с каждой станцией буду давать немного трафика покупателям! И мне несложно, и плюшка приятная. Используется версия 2.0.1
1. LAMP! (+php-gd)
2. Ставим Asterisk с поддержкой realtime (для этого поставить дополнительно mysclclient-dev)
3. Качаем A2Billing и внимательно читаем файл INSTALL.rst и выполняем инструкции (только думая, некоторые пути имеет смысл заменить). Также обратите внимание, в скрипте установки баз MySQL могут быть неправильно прописаны пути для Debian 6. Пароли тоже не стоит ставить дефолтные :)
4. vi /etc/asterisk/res_config_mysql.conf
[general]
dbhost = 127.0.0.1
dbname = mya2billing
dbuser = a2billinguser
dbpass =
dbport = 3306

5. vi /etc/asterisk/extconfig.conf
[settings]
iaxusers => mysql,general,cc_iax_buddies
iaxpeers => mysql,general,cc_iax_buddies
sipusers => mysql,general,cc_sip_buddies
sippeers => mysql,general,cc_sip_buddies

6. Настраиваем провайдеров в /etc/asterisk/sip.conf
[MainProvider](!)
context=NoContext ; DID пока не настраивал
disallow=all
allow=alaw
dtmf=rfc2833
type=peer
canreinvite=no

[Trunk1](MainProvider)
host=XXXXX

[Trunk2](MainProvider)
host=XXXXXX

[Trunk3](MainProvider)
host=XXXXXX


7. Настраиваем A2Billing (По мотивам http://sysadminman.net/documentation.html)
7.1 Настраиваем Провайдера (PROVIDERS)
7.2 Настраиваем Транки
7.3 Создаем Rate Card (RATES)
7.4 Создаем/Импортируем Rates, привязывая его к Rate Card
7.5 Создаем Сall Plan, привязывая его к Rate Card (Делается при повторном выборе Call Plan) (Тут можно вырезать префикс международки)
7.5 Создаем покупателя, приписываем ему Call Plan (CUSTOMERS)
7.6 Создаем VoIP Profile для пользователя, можно автоматом, только необходимо потом проставить CallerID. Оттуда же делаем Asterisk reload
Работает так:
Покупатель аутентифицируется на нашей платформе. Потом просто звонит на необходимый ему номер. Профит!
Несколько параметров, на которые стоит обратить внимание:

global-base_currency (базовая валюта), после ее смены надо в разделе Billing-Currency List сделать update
agi-conf1-asterisk_version 
global-asterisk_version
agi-conf1-answer_call 
agi-conf1-play_audio
agi-conf1-say_rateinitial 
agi-conf1-say_timetocall
agi-conf1-use_dnid Если YES, то система не будет просить ввести номер, на который звонить, будет использовать присланный.

Monday, October 29, 2012

GnuGk, как замена VBP-E и частично VBP-ST

Итак, зачем покупать VBP, если есть замечательный продукт GnuGk, который умеет все, что серия E и также H.480, что умеет ST-серия.
Далее немного подчищенной копипасты с сорцфорджа.

Собирается с поддержкой H.480.18/.19

=======================================================
Final Method - Installing GNUGK v3.1+ on Debian Squeeze
=======================================================

cd /usr/src
apt-get update
apt-get install flex bison build-essential pkg-config automake libmysqlclient-dev  libmysql++-dev&nbsp; 

libsnmp-base libsnmp-dev libssl-dev
wget http://backports.debian.org/debian-backports/pool/main/libs/libssh/libssh-4_0.5.2-1~bpo60+1_i386.deb
dpkg -i libssh-4_0.5.2-1~bpo60+1_i386.deb
wget http://backports.debian.org/debian-backports/pool/main/libs/libssh/libssh-dev_0.5.2-1~bpo60+1_i386.deb
dpkg -i libssh-dev_0.5.2-1~bpo60+1_i386.deb 
 
(Тут естественно, выбрать свою архитектуру) 
 
 # Get static version download - do not use SVN:

wget 
http://sourceforge.net/projects/opalvoip/files/v3.10%20Luyten/Stable%207/ptlib-2.10.7.tar.bz2
tar -jxf ptlib-2.10.7.tar.bz2
mv ptlib-2.10.7 ptlib

export PTLIBDIR=/usr/src/ptlib
cd /usr/src/ptlib
cat version.h
=======
#define MAJOR_VERSION 2
#define MINOR_VERSION 10
#define BUILD_TYPE ReleaseCode
#define BUILD_NUMBER 7
=======
./configure
make optnoshared

cd /usr/src

# Get from CVS Web Snapshot download:

wget http://h323plus.cvs.sourceforge.net/viewvc/h323plus/h323plus/?view=tar -O h323plus.tar.gz
export OPENH323DIR=/usr/src/h323plus
cd h323plus
cat version.h
====
#define MAJOR_VERSION 1
#define MINOR_VERSION 24
#define BUILD_TYPE    ReleaseCode
#define BUILD_NUMBER 1
====

./configure
make optnoshared


cd /usr/src

# Get from CVS Web Snapshot download:

wget http://openh323gk.cvs.sourceforge.net/viewvc/openh323gk/openh323gk/?view=tar -O gnugk.tar.gz
tar -xzf gnugk.tar.gz
cd openh323gk
cat version.h
===
# define GNUGK_MAJOR_VERSION 3
# define GNUGK_MINOR_VERSION 2
# define GNUGK_BUILD_TYPE ReleaseCode
# define GNUGK_BUILD_NUMBER 0
===

./configure --enable-h46018 --with-large-fdset=8192
make optnoshared 
  
Далее пример моего конфига gatekeeper.ini
 
[Gatekeeper::Main]
Fortytwo=42
Name=GnuGk
TimeToLive=600
StatusPort=7000
StatusTraceLevel=2
UseBroadcastListener=0 

[RoutedMode]
GKRouted=1
H245Routed=1
RemoveH245AddressOnTunneling=0
AcceptNeighborsCalls=1
AcceptUnregisteredCalls=1
SupportNATedEndpoints=1
DropCallsByReleaseComplete=1
CallSignalPort=1720
EnableH46018=1
EnableH46017=1
Enable46018=1

[Proxy]
Enable=1
InternalNetwork=10.10.0.0/255.255.0.0,127.0.0.0/8
T120PortRange=40000-40999
RTPPortRange=50000-59999
ProxyForNAT=1

[RasSrv::RRQFeatures]
AliasTypeFilter=terminal;dialeddigits
AliasTypeFilter=gateway;h323id ; Это для того, чтобы могли регистрироваться Поликомы

[RasSrv::ARQFeatures]
ArjReasonRouteCallToGatekeeper=1
;; RemoveTrailingChar=#
RoundRobinGateways=1

 
 
[RoutingPolicy]
default=explicit,internal,catchall


[Routing::CatchAll]
CatchAllAlias=inbound ; Сюда будут попадать все звонки напрямую на IP гейткипера.
 
;Другой вариант прописывания дефолтного маршрута:
;[RoutingPolicy]
;default=explicit,internal

;[Routing::Explicit]

;внутренний IP адрес=адрес перенаправления
;внешний IP адрес = адрес перенаправления
;тут вообще можно менять звонимые IP на все, что угодно
 

[Gatekeeper::Auth]
default=allow

[RasSrv::RRQAuth]
default=confirm

[GkStatus::Auth]
rule=password
username= some_hash; Тут используется утилита addpasswd. В примере она не собирается,
но можно использовать версию из репозитория

Запускается все командой вида

/usr/src/openh323gk/obj_linux_x86_64_s/gnugk -c gatekeeper.ini -o /tmp/gnugk.log -tt &

Telnet на 7000 порту обеспечит просмотр статуса.


 Небольшое дополнение:
В целях облегчения жизни себе и прочим поднял данный вариант на 92.249.69.203. Так что можете смело регистрироваться. Абсолютно бесплатно :)