Monday, July 6, 2020

FusionPBX - VTiger 7 Integration

Every telecom engineer should build a PBX, CallCenter, and CRM Integration.

So, my time for CRM integration came. The idea is quite simple.
  1. Calls are registering in CRM with call recording
  2. Calls are being notified with popup while using CRM
  3. Calls from CRM directly. Click 2 Call actually
Integration is done for popular opensource CRM VTiger 7 and FusionPBX. Have to say, not plain, reworked, my fork actually.

This project consists of 3 parts.
  1. PBXManager  - module for VTiger CRM, built on top of the original PBXManager module made for Asterisk.
  2. VFusion Daemon - Docker Image that is running with FusionPBX (FreeSwitch) and listening to FreeSwitch events and making requests to VTiger CRM. Made on NodeJS
  3. FusionPBX - Actually my fork of it. Version 4.4

I actually tried to avoid daemons or so, resides on FusionPBX server, but really can't avoid it totally. So, architecture is not perfect. Some of the requests are made from FreeSwitch via mod_curl, some - in v_xml_cdr app by Fusion, some - in VFusion daemon. Not clean, but as of now it's working.
So, how it looks like?

1. Call from unknown contact


2. Call from a known contact

 
3. Call details records

yes, you can listen to the recordings directly from CRM.


Click 2 Call is made quite simple. Just click on a phone number inside CRM and you will have a callback to your extension. After picking up, a call to that number would be established.

As it's intended to be a commercial product, code is open. But documentation is still not. Maybe in the future documentation also would be open.

If you have any interest in deploying this product - pls contact me at support <at> consertis.at. Or here.

Tuesday, June 9, 2020

Janus Audio/Video plays around

WebRTC got to me. I can't say it was the first time, but most deep at the moment.

Small disclaimer. I'm not a web developer and only now got, that browser is OS by itself.

And the way to get media from the browser is WebRTC. So, let's make one more bicycle to have WebRTC - VoIP bridge.

There are many projects around to fill this gap. FreeSwitch (mode_verto or mod_sofia), Kamailio + RTPEngine are just to mention some.

And Janus was chosen. To try something new and get some skills in bright'n'shiny world of web development. And I like, that Janus has already built-in SIP plugin.

The idea of this small proof of concept - get audio and video streams separated and treated separately. Why - to have audio part handled by SIP (passed to Asterisk, more precisely) and video (or screen sharing part in the future) - by Janus SFU unit (VideoRoom plugin)

Project is just a set of 3 docker containers running in network host mode (just don't want to play around with NAT and all this stuff) and glued together with docker-compose. Nginx as a web server, Janus as Janus WebRTC server, and Asterisk as SIP application server.


Web part is just reworked and gloriously copy-pasted from original Janus demos.


This project is intended to be used as a playground for future experiments and donor of code for other projects.


Repo with project on GitHub.

Some of the pictures not to be so boring:


Audio is passed to Asterisk via SIP plugin, and Video is handled by EchoTest plugin.




Audio is mixing by Asterisk (SIP plugin as well), Video is handled by VideoRoom plugin.


Point, this one is tested on a VPS with a Let'sEncrypt certs to test it mostly "in the wild" and also there are many restrictions in modern browsers about WebRTC and security, which means even over LAN you need to have pure HTTPS.



Friday, March 13, 2020

Periodic signals with FusionPBX (School Bells)

A small app designed actually for schools. When you have PBX connected to your announcement system, it would be logical if PBX can handle periodic signals as well. So, the name is also obvious in this case - School Bells

Now it's a part of my FusionPBX fork and fusionpbx-apps repo as well.

The idea is quite simple - to manage signals from GUI in cron-like style. Not super friendly, but most of us know what cron is.


Limitation - it will allow you to choose only from Recordings, not like tone_stream or so.

Installation:


# cd /usr/src/
# git clone https://github.com/fusionpbx/fusionpbx-apps
# cd fusionpbx-apps/
# cp -R school_bells /var/www/fusionpbx/app/


Add

* * * * * php /var/www/fusionpbx/app/school_bells/school_bells_cron.php 

to your crontab

Go to GUI

Upgrades -> Schema; App Defaults; Menu Defaults; Permission Defaults

Log out and back in

Apps -> School Bells

Enjoy!

Saturday, February 29, 2020

SIP Monitoring based on SIP3 and Heplify

Few words upfronts.

 - What is SIP3?

Follow the link :) If you don't know why you need it (or similar system) - you don't.

 - Why SIP3 and not Homer
Homer 3 was really good. It actually covers all my needs in SIP traffic monitoring. Than Homer 5 with new GUI was introduced. And things slowly start getting worse. Mostly cause of really strange changes in UI, which can stop working at any moment. No, it will respond, show something, but sometimes not the real picture. The usual answer - log out and back in. With Homer 7 things went even worse. Recommendation from developers is using the latest alpha version. If set up the system with a recommended setup (set of docker containers), it's not a "set and forget" solution. No. It will stop processing traffic at some time without any warning. For monitoring system which I need not every day (actually I need mostly post-mortem traces), it always ends up with log into the system and finds out it stops working like a week ago. Not the best option.
But it's really my experience. Maybe I'm not that lucky at all.

 - Why heplify and not captain (which is a part of SIP3)?
The answer is quite simple. Heplify - single binary, captain (at the moment) - a full zoo of tools, like docker, ansible, python, etc. Yes, I'm aware, that captain also can provide RTCP stats, but overhead is too big.

So, getting to the good part.

Installation and setting up

Central server

I'm gonna use CentOS 8 (minimal). Not like I'm a big fan of it, but developers like it. So, most tested one

1. Install docker

# yum install -y yum-utils   device-mapper-persistent-data  lvm2
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# containerdSource=https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.6-3.3.el7.x86_64.rpm
# dnf install $containerdSource -y
# dnf install docker-ce docker-ce-cli -y
# systemctl disable firewalld
# systemctl start docker
# systemctl enable --now docker

 

2. Install ansible, pip, git


# yum install -y epel-release
# yum install -y ansible git python3-pip
# pip3 install docker-py


3. Install SIP3 backend


# cd /usr/src/
# git clone https://github.com/sip3io/sip3-ansible.git


Note on taking metrics from public network services.

/usr/src/sip3-ansible/roles/sip3-salto/templates/application.yml.j2
#! Server
server:
  uri: udp://0.0.0.0:15060


#! Management socket
management:
  uri: udp://0.0.0.0:15090

#! Metrics
metrics:
  logging:
    step: 1000
  influxdb:
    uri: http://sip3-influxdb:8086
    db: sip3
    step: 1000

#! MongoDB
mongo:
  uri: mongodb://sip3-mongodb:27017
  db: sip3
  bulk-size: 1
  collections:
    - prefix: attributes
      indexes:
        ascending: [name]
      max-collections: 7
    - prefix: sip_register_index
      indexes:
        ascending: [created_at, terminated_at, src_addr, src_host, dst_addr, dst_host, caller, callee, state]
        hashed: [call_id, x_call_id]
      max-collections: 7
    - prefix: sip_register_raw
      indexes:
        ascending: [created_at]
        hashed: [call_id]
      max-collections: 7
    - prefix: sip_call_index
      indexes:
        ascending: [created_at, terminated_at, src_addr, src_host, dst_addr, dst_host, caller, callee, state, error_code, error_type, duration, setup_time, establish_time]
        hashed: [call_id, x_call_id]
      max-collections: 7
    - prefix: sip_call_raw
      indexes:
        ascending: [created_at]
        hashed: [call_id]
      max-collections: 7
    - prefix: sip_message_index
      indexes:
        ascending: [created_at, terminated_at, src_addr, src_host, dst_addr, dst_host, caller, callee, state]
        hashed: [call_id, x_call_id]
      max-collections: 7
    - prefix: sip_message_raw
      indexes:
        ascending: [created_at]
        hashed: [call_id]
      max-collections: 7
    - prefix: sip_options_index
      indexes:
        ascending: [created_at, terminated_at, src_addr, src_host, dst_addr, dst_host, caller, callee, state]
        hashed: [call_id, x_call_id]
      max-collections: 7
    - prefix: sip_options_raw
      indexes:
        ascending: [created_at]
        hashed: [call_id]
      max-collections: 7

#! Application
sip:
  message:
    x-correlation-header: X-Call-ID
    exclusions: [MESSAGE, SUBSCRIBE, NOTIFY, OPTIONS, REGISTER]
  calls:
    aggregation-timeout: 120000

attributes:
  record-ip-addresses: false
  record-call-users: false


It's not to index in the database all IP addresses and usernames are seen on the system, cause the production system usually resides on public networks, where are tons of spam/attack calls and packets. Also retention period for database is set to 7 days, which is ok for me.

# ansible-playbook /usr/src/sip3-ansible/playbooks/trial/sip3-backend.yml

Relax and wait for system installs.

4. Add autostart


 /etc/systemd/system/sip3-backend.service

[Unit]
Description=Start Ansible Playbook SIP3
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/ansible-playbook /usr/src/sip3-ansible/playbooks/trial/sip3-backend.yml
TimeoutStartSec=10

[Install]
WantedBy=default.target




# systemctl daemon-reload
# systemctl enable sip3-backend


5. (Optional )Add hosts aliases to db

To get more friendly trances to read. Point, no spaces in names are allowed.


# docker exec -it sip3-mongodb mongo
# use sip3
# db.hosts.insert({"name" : "my_softswitch", "sip": ["X.X.X.X", "10.10.0.1"]})

Installing heplify (capture agent)

Log in to the server you want to collect SIP traffic from

# cd /usr/local/bin/
# wget https://github.com/sipcapture/heplify/releases/download/1.56/heplify
# chmod +x heplify


/etc/systemd/system/heplify.service
[Unit]
Description=Captures packets from wire and sends them to SIP3
After=network.target

[Service]
WorkingDirectory=/usr/local/bin
ExecStart=/usr/local/bin/heplify -i any -m SIP -hs SIP3_SERVER_ADDRESS:15060
ExecStop=/bin/kill ${MAINPID}
Restart=on-failure
RestartSec=10s
Type=simple

[Install]
WantedBy=multi-user.target




# systemctl daemon-reload
# systemctl enable heplify
# systemctl start heplify


System using

Log into your newly installed system on
http://SIP3_SEVER_ADDRESS with admin:admin credentials.

Sometimes, just after login, you will need to refresh your page. Actually, make sure Go! button is active
It's your search string. Simple, but yet powerful. It really looks like Wireshark one.
You can add search critera there based on sip.
or ip.
criteria
ip.src_host and ip.dst_host are hosts we added at step 5 installing Central Server paragraph.

Changing password is quite tricky, cause SIP3 relies on Grafana auth (Grafana is shipped as well) and to change password, head to

 http://SIP3_SEVER_ADDRESS/grafana

and use it interface to change password. As a bonus, you have Grafana dashboard with some useful metrics. And for sure you can add one you like. How - read Grafana manual :)

Adding batteries

One is a key feature of SIP3 is the ability to glue call legs in a 1 call flow. Usual algorithm is based on a strict match From and To numbers, but it's really rare case (at least, in my scenarios). There is a possibility to write user-defined functions, but I'll leave it to official manual. Another way to match call is to use custom SIP headers to match call legs. The default SIP3 header is X-Call-ID. So, if one leg holds Call-ID standart header matching other call X-Call-ID header, they would consider as one call and glue together.

In my case, I'm using FusionPBX, the solution is really simple. Add dialplan entry:
It will add X-Call-ID header to call equal to current Call-ID, if it's not present. Also, FreeSwitch preserves X-Headers over call legs. which is perfect for us.

So, we will have a picture like this

Upgrading

# cd /usr/src
# git pull
# ansible-playbook /usr/src/sip3-ansible/playbooks/trial/sip3-backend.yml


Happy using!

Monday, October 28, 2019

One more SIP firewall based on Kamailio

Sometimes it's impossible to put Kamailio in a front of third-party SIP software and it's nice to have sort of SIP firewall upfront.

One of solutions - use iptables (like I did a long ago here), but overall, IPTables is not a SIP processing engine. But Kamailio is.

So, idea is quite simple. We have our main PBX software sitting on ethX (or ensXX), port 5060. We're mirroring SIP traffic on this interface:port to localhost, where Kamailio is listens to them in promiscuous mode. And writing a log file, Fail2Ban can analyze. 
Target here - is to protect main PBX software from consuming a lot of resources on fake INVITE's and REGISTER's from not-too-friendly-scanners.

First - install Kamailio.
I prefer to put syslog messages to separate file.

Configure Kamailio with file

/etc/kamailio/kamailio.cfg
 
#!KAMAILIO

#!define WITH_ANTIFLOOD
#!define WITH_DOMAIN_NAMES
#!define WITH_SANITY_CHECK
#!define WITH_SCANNER_BODY
#!define WITH_SCANNER_MESSAGE

debug=2
log_stderror=no

memdbg=5
memlog=5

log_facility=LOG_LOCAL7

children=6

auto_aliases=no

listen=udp:127.0.0.1:5060


loadmodule "sl.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "regex.so"
loadmodule "sipcapture.so"
loadmodule "db_text.so"
# ----------------- setting module-specific parameters ---------------
modparam("sipcapture", "db_url", "text:///tmp/")
modparam("sipcapture", "raw_socket_listen", "127.0.0.1:5060")
modparam("sipcapture", "raw_moni_capture_on", 1)
modparam("sipcapture", "raw_interface", "lo")
modparam("sipcapture", "promiscious_on", 1)

#!ifdef WITH_ANTIFLOOD
loadmodule "pike.so"
# ----- pike params -----
modparam("pike", "sampling_time_unit", 2)
modparam("pike", "reqs_density_per_unit", 16)
modparam("pike", "remove_latency", 4)

loadmodule "htable.so"
# ----- htable params -----
/* ip ban htable with autoexpire after 5 minutes */
modparam("htable", "htable", "ipban=>size=8;autoexpire=300;")
#!endif

####### Routing Logic ########

request_route {


#!ifdef WITH_ANTIFLOOD
    # flood detection 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
            xlog("L_NOTICE", "[SIP-FIREWALL][ANTIFLOOD-BANNED] [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]\n");
            exit;
        }
        if (!pike_check_req()) {
            $sht(ipban=>$si) = 1;
            xlog("L_NOTICE", "[SIP-FIREWALL][ANTIFLOOD-ADD] [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]\n");
            xlog("L_ALERT", "[SIP-FIREWALL][FAIL2BAN] $si\n");
            exit;
        }
    }
#!endif

#!ifdef WITH_SCANNER_MESSAGE
    if (search("friendly-scanner|sipvicious|sipcli*|vaxasip|sip-scan|iWar|sipsak")) {
        xlog("L_NOTICE", "[SIP-FIREWALL][FRIENDLYSCANNER_MESSAGE]: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        xlog("L_ALERT", "[SIP-FIREWALL][FAIL2BAN] $si\n");
        exit;
    }
#!endif
#!ifdef WITH_SCANNER_BODY
    if (search_body("friendly-scanner|sipvicious|sipcli*|vaxasip|sip-scan|iWar|sipsak")) {
        xlog("L_NOTICE", "[SIP-FIREWALL][FRIENDLYSCANNER_BODY]: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        xlog("L_ALERT", "[FAIL2BAN] $si\n");
        exit;
    }
#!endif
#!ifdef WITH_SANITY_CHECK
    if(!sanity_check("17895", "7")) {
        xlog("L_NOTICE", "[SIP-FIREWALL][MALFORMED] [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]\n");
        xlog("L_ALERT", "[SIP-FIREWALL][FAIL2BAN] $si\n");
        exit;
    }
#!endif
#!ifdef WITH_DOMAIN_NAMES
    if (!is_method("INVITE|REGISTER")) {
        exit;
    }

    if (pcre_match("$rd", "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$$")) {
        if (pcre_match("$fd", "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$$")) {
            xlog("L_NOTICE", "[SIP-FIREWALL][NOT_DOMAIN_BASED_URI]: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            xlog("L_ALERT", "[SIP-FIREWALL][FAIL2BAN] $si\n");
            exit;
        }
    }
#!endif
}



It's actually my file, but you can adopt rules to yours.

Next - set up Fail2Ban

/etc/fail2ban/filter.d/kamailio.conf

[Definition]
# filter for kamailio messages
failregex = [SIP-FIREWALL][FAIL2BAN] <HOST>


/etc/fail2ban/jail.conf
...
[kamailio-iptables]
enabled  = true
filter   = kamailio
action   = iptables-allports[name=KAMAILIO, protocol=all]
logpath  = /var/log/kamailio.log
maxretry = 5
bantime  = 1800
findtime = 60


Next - make sure you don't have net.ipv4.ip_forward set to 1. You will run into infinite loops in this case

# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0


And last - use iptables TEE target to mirror traffic to localhost

# iptables -A PREROUTING -t mangle -i eth0 -p udp --dport 5060 -j TEE --gateway 127.0.0.1

That's it.




Wednesday, October 23, 2019

Building RTPEngine with Docker

Idea is quite simple.
To build rtpengine packet on Debian system, but not having all build dependencies on target machine.
So, we'll use Docker to generate deb files for target system and than - just remove image. This way system will held only minimum needed packets.

As a target, Debian 10 (buster) will be used, as I succeed to install rtpengine mr8.0 (with G.729 support) on it and can't get it working on Debian 9 (maybe use lower version?)

So. All is done on Debian 10 netinst.
  • First step - install Docker

    apt update
    apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
    curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
    add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
    apt update
    apt install -y docker-ce
  • Prepare Dockerfile

    FROM debian:buster
    MAINTAINER Igor Olhovskiy <igorolhovskiy@gmail.com>

    ENV DEBIAN_FRONTEND noninteractive
    ENV RTPENGINE_VERSION mr8.0
    ENV BCG729_VERSION 1.0.4

    RUN apt-get update && \
        apt-get upgrade -y && \
        apt-get install -y git \
                    dpkg-dev \
                    cmake \
                    unzip \
                    wget \
                    debhelper-compat \
                    default-libmysqlclient-dev \
                    gperf \
                    iptables-dev \
                    libavcodec-dev \
                    libavfilter-dev \
                    libavformat-dev \
                    libavutil-dev \
                    libbencode-perl \
                    libcrypt-openssl-rsa-perl \
                    libcrypt-rijndael-perl \
                    libcurl4-openssl-dev \
                    libdigest-crc-perl \
                    libdigest-hmac-perl \
                    libevent-dev libglib2.0-dev \
                    libhiredis-dev libio-multiplex-perl \
                    libio-socket-inet6-perl libiptc-dev \
                    libjson-glib-dev libnet-interface-perl \
                    libpcap0.8-dev \
                    libpcre3-dev \
                    libsocket6-perl \
                    libspandsp-dev \
                    libssl-dev \
                    libswresample-dev \
                    libsystemd-dev \
                    libxmlrpc-core-c3-dev \
                    markdown \
                    curl \
                    wget \
                    zlib1g-dev && \
        cd /usr/src && \
        curl https://codeload.github.com/BelledonneCommunications/bcg729/tar.gz/$BCG729_VERSION > bcg729_$BCG729_VERSION.orig.tar.gz && \
        tar zxf bcg729_$BCG729_VERSION.orig.tar.gz && \
        cd bcg729-$BCG729_VERSION && \
        git clone https://github.com/ossobv/bcg729-deb.git debian && \
        dpkg-buildpackage -us -uc -sa && \
        cd /usr/src && \
        dpkg -i *.deb && \
        cd /usr/src && \
        git clone -b $RTPENGINE_VERSION https://github.com/sipwise/rtpengine.git && \
        cd rtpengine && \
        dpkg-buildpackage && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/*

    RUN mkdir -p /opt/deb && \
        mv /usr/src/*.deb /opt/deb

    VOLUME ["/opt/deb"]
  • Build and get deb files

    docker build -t rtpengine:build .
    docker create --name rtpengine-build rtpengine:build
    docker cp rtpengine-build:/opt/deb .
  • Install rtpengine


    cd deb
    dpkg -i libbcg729-0*.deb
    dpkg -i ngcp-rtpengine-daemon_*.deb
    apt-get install -f
    dpkg -i ngcp-rtpengine-recording-daemon_*.deb

    apt-get install -f
    dpkg -i ngcp-rtpengine-utils_*.deb

    apt-get install -f
    dpkg -i ngcp-rtpengine-iptables_*.deb

    apt-get install -f
    dpkg -i ngcp-rtpengine-kernel-dkms_*.deb
    dpkg -i ngcp-rtpengine_*.deb
    Idea of repeating apt-get install -f is to apt to add missing packets.
    Next - configure rtpengine as usual

Thursday, April 18, 2019

Simple Albert laucher plugin for managing VPN's

For some reasons now switched to Linux laptop from Macbook. And as an amazing replacement for Alfred launcher found Albert.
It also have a powerful plugin system and simple workflows could be created really fast.
One of workflows I used to in Alfred is switch VPN's on/off directly from Alfred's interface. As it's all opensource, answer is quite simple - if you need something, write it.
So, as a small task during sickness wrote a plugin for toggling VPN's state, that are managed through NetworkManager.


Looks like this:


Code is simple and published here