Как стать автором
Обновить

SMS через SIP messaging в Asterisk

Время на прочтение 7 мин
Количество просмотров 11K

По немногочисленным просьбам трудящихся я расскажу как заворачивать GSM SMS из sip_dongle модуля для Asterisk в sip messaging с последующей маршрутизацией.

Немного ТЗ

Есть центральный узел Asterisk, на котором живет логика и пользователи (extensions). К нему подключаются через PJSIP другие оконечные Asterisk`и на которых висит по несколько GSM модемов.

  • Одна из задач: принимать и отправлять GSM SMS сообщения правильно маршрутизируя их по номеру получателя.

  • Вторая подзадача: дать нашим пользователям (extensions) возможность слать друг другу текстовые сообщения.

Итак. Что тут сложного и в чем подвох?

Когда у вас 1 модем и 1 extension все очень просто. Когда у вас 2 модема и 1 extension задача все еще простая.

Когда у вас “многое ко многим” — вот тут и кроется основная сложность. Т.е. нам нужно не просто понимать на какой конкретно модем какому конкретно Asterisk`у передать исходящее SMS, но и куда направить входящее из GSM сети сообщение. А так же что делать, если мы внутри себя не можем сообщения доставить.

Обозначения:

  • gsmru 10.8.0.11 — оконечный сервер Asterisk на котором живут модемы

  • 10.8.0.10 — центральный сервер Asterisk на котором крутится вся логика

  • 101;102;103 - пользователи (extensions)

  • sip - протокол ходит по 80му порту (так исторически сложилось)

Начнем разворачивать цепочку от оконечного Asterisk (gsmru) с модемами в сторону центрального сервера. Это канал приема сообщений и проще его объяснять именно так.

Работать с SMS можно по разному, но в моем случае я использую sqlite, который у меня /var/lib/asterisk/smsdb.sqlite3. Это сделано так чтобы можно было легко оперировать сообщениями, создавать очереди доставки в обе стороны и не переживать за всякие проблемы вида «я записал что-то в файл и мне нужно успеть это вычитать пока туда не записалось следующее, а заодно проверить что там нет «патча Бармина».

Поэтому в dongle.conf честно вписываем

smsdb=/var/lib/asterisk/smsdb
csmsttl=600


и радуемся результату. Еще не забываем выставить autodeletesms=yes что бы память модема не забивалась и очередь не застревала (мы ж и так все в базу положили).

Как нам организовать БД?

Все просто:

CREATE TABLE incoming (key VARCHAR(256), seqorder INTEGER, expiration TIMESTAMP DEFAULT CURRENT_TIMESTAMP, message VARCHAR(256), PRIMARY KEY(key, seqorder))

CREATE TABLE outgoing_msg (dev VARCHAR(256), dst VARCHAR(255), cnt INTEGER, expiration TIMESTAMP, srr BOOLEAN, payload BLOB)

CREATE TABLE outgoing_part (key VARCHAR(256), msg INTEGER, status INTEGER, PRIMARY KEY(key))

CREATE TABLE outgoing_ref (key VARCHAR(256), refid INTEGER, PRIMARY KEY(key))

CREATE INDEX incoming_key ON incoming(key)

CREATE INDEX outgoing_part_msg ON outgoing_part(msg

На том же самом оконечном Asterisk (gsmru) в extensions.conf создаем правила обработки входящих сообщений.

Напоминаю: 10.8.0.10 — центральный сервер Asterisk, gsmru — наш оконечный узл Asterisk

Прописывам на оконочном Asterisk (gsmru) в extensions.conf:

exten => sms,1,Verbose(Incoming SMS from ${CALLERID(num)} to ${DONGLENAME} ${BASE64_DECODE(${SMS_BASE64})})
same = n,Set(MESSAGE(body)=${BASE64_DECODE(${SMS_BASE64})})
same = n,Set(MSGSENDER=${CALLERID(num)})
same = n,GotoIf($["${DONGLENAME}"="dongle0"]?smsFromDongle0)
same = n,GotoIf($["${DONGLENAME}"="dongle1"]?smsFromDongle1)
same = n,GotoIf($["${DONGLENAME}"="dongle2"]?smsFromDongle2)
same = n(smsFromDongle0),NoOp("Forwarding to 101")
same = n,Set(NDEST=pjsip:gsmru/sip:101@10.8.0.10:80)
same = n,MessageSend(${NDEST},${MSGSENDER})
same = n,Hangup()
same = n(smsFromDongle1),NoOp("Forwarding to 102")
same = n,Set(NDEST=pjsip:gsmru/sip:102@10.8.0.10:80)
same = n,MessageSend(${NDEST},${MSGSENDER})
same = n,Hangup()
same = n(smsFromDongle2),NoOp("Forwarding to 103")
same = n,Set(NDEST=pjsip:gsmru/sip:103@10.8.0.10:80)
same = n,MessageSend(${NDEST},${MSGSENDER})
same = n,Hangup();

Тут все очень просто: сообщаем в консоль что у нас входящее сообщение, печатаем туда же его текст, смотрим от какого устройства и направляем в нужную сторону. Примерно так же можно направить на группу что бы сообщение «разлетелось всем».

Сообщене уже в виде sip message летит на центральный узел 10.8.0.10

Что же там с ним делают?

А там его ждет описание оконечного узла в pjsip.conf

[gsmru]
type=endpoint
transport=transport-tcp
context=incoming-calls
message_context=incoming-sms
disallow=all
allow=ulaw,alaw,opus
outbound_auth=gsmru-auth
aors=gsmru
language=ru

Ну и что бы этот контекст не пропадал даром, в extensions.conf имеем:

;incoming sms from Gsm Gateways
[incoming-sms]
exten =_1.,1,Verbose(1, "Incoming SMS from ${CALLERID(num)} GSM Gateway to ${EXTEN}")
same = n,NoOp(To ${MESSAGE(to)})
same = n,NoOp(From ${MESSAGE(from)})
same = n,NoOp(Body ${MESSAGE(body)})
same = n,MessageSend(${MESSAGE(to)},${MESSAGE(from)})
same = n,NoOp(Send status is ${MESSAGE_SEND_STATUS})
same = n,GotoIf($["${MESSAGE_SEND_STATUS}" != "SUCCESS"]?handlefailedmsg)
same = n,Hangup()

; Handle failed messaging
same = n(handlefailedmsg),NoOp(Sending error to user)
same = n,Set(SRC=${MESSAGE(from)})
same = n,Set(DST=${MESSAGE(to)})
same = n,Set(MSG=${MESSAGE(body)})
same = n,Set(MESSAGE(body)="[${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)}] Your message to ${EXTEN} has failed. Sending when available")
same = n,Set(ME_1=${CUT(MESSAGE(from),<,2)})
same = n,Set(ACTUALFROM=${CUT(ME_1,@,1)})
same = n,MessageSend(${ACTUALFROM},ServiceCenter)
; same = n,GotoIf($["${INQUEUE}" != "1"]?startq)
same = n,Hangup()

Все, магия случилась — сообщение улетело в виде sip mesage в клиентское ПО\Оборудование. Иными словами, если послать sms на номер модема, оно появится в программе. Я уже говорил что не использую аппаратные телефоны, так что тут «не копенгаген», а в Linphone глюк выводит пустое сообщение. Так что на Android я использую PortSip.

Как нам теперь отправлять SMS во внешний мир?

Тут все чуть сложнее и мы пойдем от центрального сервера к модему.

Прописываем на центральном сервере в pjsip.conf:

[endpoint-internal](!)
type = endpoint
context = default
message_context = outgoing-sms
allow = !all,alaw,ulaw,opus,h264,vp8,vp9
direct_media = no
trust_id_outbound = yes
device_state_busy_at = 1
dtmf_mode = rfc4733
media_encryption = sdes
rewrite_contact = yes

Теперь маршрутизация для центрального сервера в extensions.conf:

[outgoing-sms]
exten = _+7.,1,Verbose(1, "Outgoing SMS from ${CALLERID(num)} GSM Gateway to ${EXTEN}")
same = n,NoOp(To ${MESSAGE(to)})
same = n,NoOp(From ${MESSAGE(from)})
same = n,NoOp(Body ${MESSAGE(body)})
same = n,Set(TO= ${MESSAGE(to)})
same = n,Set(BUF= ${CUT(TO,@,1)})
same = n,NoOp(BUF ${BUF})
same = n,Set(NDEST=pjsip:gsmru/sip:${CUT(BUF,:,2)}@10.8.0.11:80)
same = n,NoOp(NDEST ${NDEST})
same = n,Set(BUF1=${CUT(TO,<sip:,2)})
same = n,NoOp(BUF1 ${BUF1})
same = n,Set(BUF2=${STRREPLACE(BUF1:4,>,)})
same = n,NoOp(BUF2 ${BUF2})
same = n,Set(BUF3=${CUT(BUF2,@,1)})
same = n,NoOp(BUF3 ${BUF3})
same = n,GotoIf(${LEN(${BUF3})}=0?:sendsms)
same = n,Set(NDEST=pjsip:gsmru/sip:${BUF3}@10.8.0.11:80)
same = n,NoOp(NDEST ${NDESTD})
same = n(sendsms),MessageSend(${NDEST},${MESSAGE(from)})
same = n,Hangup
exten = _8.,1,Verbose(1, "Outgoing SMS from ${CALLERID(num)} GSM Gateway to ${EXTEN}")
same = n,NoOp(To ${MESSAGE(to)})
same = n,NoOp(From ${MESSAGE(from)})
same = n,NoOp(Body ${MESSAGE(body)})
same = n,Set(TO= ${MESSAGE(to)})
same = n,Set(BUF= ${CUT(TO,@,1)})
same = n,NoOp(BUF ${BUF})
same = n,Set(NDEST=pjsip:gsmru/sip:${CUT(BUF,:,2)}@10.8.0.11:80)
same = n,NoOp(NDEST ${NDEST})
same = n,Set(BUF1=${CUT(TO,<sip:,2)})
same = n,NoOp(BUF1 ${BUF1})
same = n,Set(BUF2=${STRREPLACE(BUF1:4,>,)})
same = n,NoOp(BUF2 ${BUF2})
same = n,Set(BUF3=${CUT(BUF2,@,1)})
same = n,NoOp(BUF3 ${BUF3})
same = n,GotoIf(${LEN(${BUF3})}=0?:sendsms)
same = n,Set(NDEST=pjsip:gsmru/sip:${BUF3}@10.8.0.11:80)
same = n,NoOp(NDEST ${NDESTD})
same = n(sendsms),MessageSend(${NDEST},${MESSAGE(from)})
same = n,Hangup

Сообщение мы смаршрутизировали и даже забуферизировали если не смогли отправить (ну там канал лег или деньги на карточке кончились).

Теперь на нашем оконечном Asterisk gsmru в pjsip.conf создаем описание входящего аккаунта:

[gsmru]
type=endpoint
context=default
message_context=outgoing-sms
disallow=all
allow=ulaw,alaw
transport=transport-tcp
auth=gsmru
aors=gsmru

И на том же оконечном gsmru задаем маршрутизацию в extensions.conf:

[outgoing-sms]
exten = _+X.,1,Verbose(1, "Outgoing SMS from ${CALLERID(num)} to ${EXTEN}.")
same = n,NoOp(To ${MESSAGE(to)})
same = n,NoOp(From ${MESSAGE(from)})
same = n,NoOp(Body ${MESSAGE(body)})
same = n,Set(TO=${MESSAGE(to)})
same = n,Set(FROM=${CUT(MESSAGE(from),<,2)})
same = n,Set(BUF=${CUT(TO,@,1)})
same = n,Set(DEST=${CUT(BUF,:,2)})
same = n,NoOp(DEST=${DEST})
same = n,GotoIf($["${FROM}"="sip:101@SIPDOMAIN>"]?sendSmsDongle0)
same = n,GotoIf($["${FROM}"="sip:102@SIPDOMAIN>"]?sendSmsDongle1)
same = n,GotoIf($["${FROM}"="sip:103@SIPDOMAIN>"]?sendSmsDongle2)
same = n(sendSmsDongle0),DongleSendSMS(dongle0,${DEST},"${MESSAGE(body)}",1140,yes,"magicid")
same = n,Hangup()
same = n(sendSmsDongle1),DongleSendSMS(dongle1,${DEST},"${MESSAGE(body)}",1140,yes,"magicid")
same = n,Hangup()
same = n(sendSmsDongle2),DongleSendSMS(dongle2,${DEST},"${MESSAGE(body)}",1140,yes,"magicid")
same = n,Hangup()
exten = _X.,1,Verbose(1, "Outgoing SMS from ${CALLERID(num)} to ${EXTEN}.")
same = n,NoOp(To ${MESSAGE(to)})
same = n,NoOp(From ${MESSAGE(from)})
same = n,NoOp(Body ${MESSAGE(body)})
same = n,Set(TO=${MESSAGE(to)})
same = n,Set(FROM=${CUT(MESSAGE(from),<,2)})
same = n,Set(BUF=${CUT(TO,@,1)})
same = n,Set(DEST=${CUT(BUF,:,2)})
same = n,NoOp(DEST=${DEST})
same = n,GotoIf($["${FROM}"="sip:101@SIPDOMAIN>"]?sendSmsDongle0)
same = n,GotoIf($["${FROM}"="sip:102@SIPDOMAIN>"]?sendSmsDongle1)
same = n,GotoIf($["${FROM}"="sip:103@SIPDOMAIN>"]?sendSmsDongle2)
same = n(sendSmsDongle0),DongleSendSMS(dongle0,${DEST},"${MESSAGE(body)}",1140,yes,"magicid")
same = n,Hangup()
same = n(sendSmsDongle1),DongleSendSMS(dongle1,${DEST},"${MESSAGE(body)}",1140,yes,"magicid")
same = n,Hangup()
same = n(sendSmsDongle2),DongleSendSMS(dongle2,${DEST},"${MESSAGE(body)}",1140,yes,"magicid")

И вуаля — сообщение уйдет в GSM сеть!

Пару замечаний

  • Я использую PJSIP и ничего не знаю про sip. Ну как бы у нас 2к21 на дворе, пора бы уже двигаться в будущее.

  • Да, я использую транспорт TCP по 80му порту. Это не влияет на рассмотренный вопрос про SMS.

  • Если у вас много стран\городов или много префиксов номера - для них для всех нужно повторять блоки маршрутизации, так же как и для звонков.

  • Обязательна поддержка SRTP на клиенте (оно включено на сервере).

  • На клиенте DTMF нужно выставить в RFC2833.

Теги:
Хабы:
+1
Комментарии 5
Комментарии Комментарии 5

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн