最近流量卡越來越便宜了,看看自己手里的“坑不死老用戶”的聯通卡,頓時感覺到深深的惡意,但是iPhone沒有雙卡功能,所以只好自己動手打造一個網絡電話系統托管聯通卡,iPhone使用流量卡,系統轉移聯通卡的呼叫到iPhone上,其實也沒什么人給我打電話了[捂臉🤦♂️],主要是轉發短信,方便接受驗證碼。當然,反過來也可以,在iPhone上通過互聯網使用系統中的聯通卡撥號,和發短信,不過發短信基本上用不到了。
其實實現起來很簡單,就是使用是呀的各種現成的工具,累積木一樣搭出來。硬件清單如下:
- Raspberry pi 3代B 一枚
- 2A電源(我用的是iPad的充電器)
- 16GB的SD卡+可讀寫SD卡的讀卡器
- 華為的上網卡托E169
開始
已經有封裝了Asterisk和FreePBX的系統:RasPBX
-
首先下載RasPBX系統,燒錄到SD卡中,通電啟動樹莓派,接上顯示器和鍵盤(或者不用外接硬件),連上無線網。
配置ssh登錄。
-
按照RasPBX的安裝教程首先應該
raspbx-upgrade
,但是在我大天朝就別想那么順利,需要那個啥你懂的,至於如何那個啥大家就各顯神通吧,如果你和我一樣使用ss那么可以參考這篇文章如果一切順利,那么
proxychains raspbx-upgrade
,大概等個也許10多分鍾就能更新完畢 -
FreePBX提供了一個友好的Web界面供我們使用,在瀏覽器中輸入
http://raspbx
,mac輸入http://raspbx.local
登錄,選擇Administrator,使用默認初始賬號admin,密碼admin進行登錄。 -
進入管理控制台后,首先映入眼簾的是儀表盤
先在局域網中測試一下能否正常使用,然后在到公網上去。由於是在局域網中測試,所以配置很簡單,直接在
Applications
->Extensions
->Add new Chan_SIP Extension
新建一個分機,在General
tab頁里面填好第一個SIP賬戶的信息,例如
然后在
Advanced
中設置NAT Mode
為Yes - (force_rport,comedia)
,最后點擊右下角的
submit
,再去
Settings
->Asterisk SIP Settings
里面的Chan SIP Settings
tab頁下面的第一個NAT
設置未Never
,然后Submit
。最后的最后點擊右上角的
Apply Config
應用剛才的配置。 -
接下來去下載一個SIP客戶端,我選擇的是zoiper,安裝完成之后,在
Account
中添加剛才在FreePBX中添加的賬戶,記住密碼是secret中的值,千萬別填下面的,那個是該用戶進入管理控制台的密碼,如果用戶名或者密碼不正確只會返回403而不會提示用戶名或者密碼錯誤。這里的Domain就填寫RasPBX在局域網中的ip地址。填好之后,點擊上方的
Register
-
如果只有一個手機的話,那么可以在電腦上再裝一個SIP軟件,我在Mac上安裝了
Telephone
,然后重復上面的步驟再注冊另一個賬戶,在Mac上登錄。打一個電話測試一下吧,是不是很激動:)
內網測試通過之后,就可以開始搭建外網訪問了
- 現在可以嘗試通過外網訪問了,一般常見的有三種外網訪問情況。
能力 責任? 狗屁? 愛誰誰
1. 如果你有公網IP,那么這是最省事的,可惜大多數人沒有。
1. 用動態域名進行訪問
1. 沒有路由器權限或者路由器沒有被分配公網IP,這種情況只能內網穿透。
我沒有公網IP,所以1不行,本來我家里的路由器也沒有被分配公網IP,因為路由器連接電信的光貓使用DHCP聯網的,公網IP被分配到了電信的光貓上了,所以選擇3,進行內網穿透,使用ssh反向代理,但是發現ssh可以轉發TCP,對於UDP就無能為力了,於是打算把SIP的協議改成TCP,但是發現通信用的RTP只能使用UDP,沒法改。因此,又琢磨着使用IAX,結果發現IAX好像也改不成UDP,或許是我的姿勢不對。不得已只能試試2了,本來想破解電信的光貓,然后將其改成橋接模式,讓路由器進行PPOE撥號上網,看了很多破解教程,發現網上流傳的漏洞都被堵死了,至此打算放棄了,最后抱着試試看的心態,聯系了電信客服,客服說已為我報修,之后會有工程師聯系我,過了不久,工程師給我來電了,我說要把光貓改成橋接模式,工程師很爽快的答應了,立馬就遠程改好了,叫我過20分鍾重啟,重啟之后果然變成了橋接模式。接着我在路由器中使用PPPOE進行撥號上網,路由器就被分配了公網IP,真是踏破鐵鞋無覓處,得來全不費功夫:)
-
使用動態域名進行訪問
先拿到路由器被分配的公網IP,可以直接在路由器中查看,或者在路由器下面的樹莓派中通過終端:
curl ip.cn
查看該IP。打開freepbx的web界面,登錄管理員界面,
Settings
->Asterisk SIP Settings
中Detect Network Settings
,自動檢測IP,應該和上一步中拿到的IP相同,再檢查下面的內網地址是否正確。
然后去Chan SIP Settings
tab頁下面的NAT
設置Static IP
,默認值就是之前檢測到的結果,如果沒錯就不用改,然后submit
,applyconfig
。
再到路由器中設置端口轉發,因為那個公網IP是路由器的地址。每種路由器的設置都不同,請自行摸索一下,其實很簡單,就是把路由器的5060端口轉發到樹莓派的5060端口,協議最好選擇TCP/UDP,還有10001~20000端口也要轉發到樹莓派的10001~20000,這個是RTP通信端口可以選擇轉發UDP.這里沒必要轉發一萬個端口,一次通信只需要4個端口,所以轉4個端口10001~10004用來測試就可以了。
現在去客戶端中把Domain改成公網IP,應該能夠撥通分機。
用手機的4G網絡再試一次,還是正常那就通過了。
-
因為這個IP是電信運營商動態分配的,隨時都有可能變化,所以需要不斷檢測當前IP地址,然后通過DNS服務更改解析。最簡單的做法可以直接用花生殼等服務商的服務,不過我不喜歡這些服務商,打算自己寫個腳本來實現,這樣既有成就感又能完全掌握,用花生殼的客戶端給我一種感覺:總有刁民想害朕。
雲計算井噴式的發展,我等小P民也能美美的用上了,去阿里雲買個便宜的ip地址和dns雲解析一年也才50多塊,阿里雲的雲解析的TTL最快達到1s。
樹莓派默認已經安裝了python環境,那就用python吧,調用阿里雲的sdk就可以了,分分鍾的事情。腳本參考連接,原作者已經寫得很好了,只是有一個異常情況作者沒有遇到,那就是
ip.cn
宕機了,所以我在腳本中簡單處理了一下這種情況。
在樹莓派中,新建vim aliyun_ddns.py
,把下面的代碼復制進去,部分地方根據實際情況修改。# -*- coding: UTF-8 -*- import json import os import re import sys from datetime import datetime from aliyunsdkalidns.request.v20150109 import UpdateDomainRecordRequest, DescribeDomainRecordsRequest, \ DescribeDomainRecordInfoRequest from aliyunsdkcore import client #請填寫你的Access Key ID access_key_id = "你的keyID" #請填寫你的Access Key Secret access_Key_secret = "你的Secret" #請填寫你的賬號ID account_id = "你的賬號ID" #如果選擇yes,則運行程序后僅現實域名信息,並不會更新記錄,用於獲取解析記錄ID。 #如果選擇NO,則運行程序后不顯示域名信息,僅更新記錄 i_dont_know_record_id = 'yes' #請填寫你的一級域名 rc_domain = '域名' #請填寫你的解析記錄 rc_rr = '你的解析記錄' #請填寫你的記錄類型,DDNS請填寫A,表示A記錄 rc_type = 'A' #請填寫解析記錄ID rc_record_id = '解析記錄ID' #請填寫解析有效生存時間TTL,單位:秒 rc_ttl = '1' #請填寫返還內容格式,json,xml rc_format = 'json' def my_ip(): get_ip_method = os.popen('curl -s ip.cn') get_ip_responses = get_ip_method.readlines()[0] get_ip_pattern = re.compile(r'\d+\.\d+\.\d+\.\d+') get_ip_value = get_ip_pattern.findall(get_ip_responses)[0] return get_ip_value def check_records(dns_domain): clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou') request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest() request.set_DomainName(dns_domain) request.set_accept_format(rc_format) result = clt.do_action_with_exception(request) return result def old_ip(): clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou') request = DescribeDomainRecordInfoRequest.DescribeDomainRecordInfoRequest() request.set_RecordId(rc_record_id) request.set_accept_format(rc_format) result = clt.do_action_with_exception(request) result = json.JSONDecoder().decode(result) result = result['Value'] return result def update_dns(dns_rr, dns_type, dns_value, dns_record_id, dns_ttl, dns_format): clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou') request = UpdateDomainRecordRequest.UpdateDomainRecordRequest() request.set_RR(dns_rr) request.set_Type(dns_type) request.set_Value(dns_value) request.set_RecordId(dns_record_id) request.set_TTL(dns_ttl) request.set_accept_format(dns_format) result = clt.do_action_with_exception(request) return result def write_to_file(): time_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') current_script_path = sys.path[7] print current_script_path log_file = current_script_path + '/' + 'aliyun_ddns_log.txt' write = open(log_file, 'a') write.write(time_now + ' ' + str(rc_value) + '\n') write.close() return if i_dont_know_record_id == 'yes': print check_records(rc_domain) elif i_dont_know_record_id == 'no': try: rc_value = my_ip() except: rc_value = old_ip() rc_value_old = old_ip() if rc_value_old == rc_value: print 'The specified value of parameter Value is the same as old' else: print update_dns(rc_rr, rc_type, rc_value, rc_record_id, rc_ttl, rc_format) write_to_file()
然后導入阿里雲的python sdk:
pip install aliyun-python-sdk-alidns
。先去阿里雲控制台手動解析一下,生成一個記錄就OK了。
首次運行腳本前,把上面的腳本中的
i_dont_know_record_id
填寫yes
,然后運行python /path/of/aliyun_ddns.py
,拿到結果中就包含了你的解析記錄ID,{ "PageNumber": 1, "TotalCount": 1, "PageSize": 1, "RequestId": "xxxxx-xxxx-xxxx-xxxx-xxxxxx", "DomainRecords": { "Record": [ { "RR": "xxxx", "Status": "xxxxx", "Value": "xxxxxx", "RecordId": "xxxxxxx", "Type": "A", "DomainName": "xxxx", "Locked": false, "Line": "default", "TTL": "1" } ] } }
把
RecordId
的值填到腳本中rc_record_id
,然后把i_dont_know_record_id
改為no
,
再次執行,如果沒有異常那就通過了。然后用cron做成定時任務。
輸入crontab -e
,添加:*/1 * * * * /usr/bin/python2.7 ~/aliyun_ddns.py > /dev/null 1>/dev/null
注意腳本的路徑。
-
去FreePBX的管理控制台中,
Settings
->Asterisk SIP Settings
中Detect Network Settings
,將其刪除,這個輸入框留空,因為我們要用動態域名,所以這里不能填IP,到Chan SIP Settings
tab頁下面的NAT
設置Dynamic IP
,輸入買來的域名,下面的檢測間隔設置為60s。Submit
->Apply Config
-
注意:最好在路由器中設置給樹莓派一個靜態ip,防止樹莓派的ip地址變化了,導致路由器的端口轉發實效。 -
在用客戶端中,把Domain改成域名,測試一遍,分機之間可以相互通信。
使用上網卡托接打電話
-
這時候就需要3G chan_gongle了,我是在某寶上買的華為E169。chan_dongle兼容的產品列表
先安裝驅動
install-dongle
,安裝過程中會詢問幾個問題,認真作答:)Please enter the phone number of your SIM card (defaults to +1234567890 if left blank): 輸入U棒里的 sim卡手機號碼,直接回車則默認為 +1234567890 Send incoming SMS to email address (leave empty to disable SMS forwarding): 設置郵箱,以便將U棒收到的短信內容轉發過去,直接回車可取消該功能 Forward incoming SMS to mobile phone number (via dongle0) (leave empty to disable): 設置一個手機號碼,以便將U棒收到的短信通過 dongle0 轉發至該號碼,直接回車可取消該功能 Would you like to install a webpage for sending SMS with chan_dongle? (http://raspbx/sms/) [y/N] 是否安裝發送短信的web頁面,可回答 y 並按提示設置一個登錄密碼。
-
增加一個trunk:
connectivity
->Trunks
填上dongle/dongle0/$OUTNUM$
Submit
->ApplyConfig
-
編寫撥號路由:
connectivity
->Outbound Routes
,命名Route Name
,選擇剛才新建的Trunk
設置撥號規則,匹配號碼,如果你的號碼匹配這個規則,那么就使用咱們的Trunk撥號,這里我添加了兩個規則:
1.
匹配1開頭的所有號碼,prefix=9
即第一數字是9的所有號碼。
Submit
->ApplyConfig
-
編寫接聽路由:
connectivity
->Inbound Routes
,填寫Description描述一下,Set Destination
目標選擇801分機
Submit
->ApplyConfig
-
現在在客戶端中撥號1**********,應該就可以撥通了,再發條短信到我的號碼上,應該也會自動轉發到目標號碼上。不過短信轉發支持不完善,經常缺失內容,所以干脆禁用該功能,使用郵件轉發收到的短信。
通過郵件轉發收到的短信
-
配置exim4:
dpkg-reconfigure exim4-config
或者直接修改文件/etc/exim4/update-exim4.conf.conf,例如我使用自己的QQ郵箱,其他郵箱自行在郵箱設置中找到對應的配置dc_eximconfig_configtype='smarthost' dc_other_hostnames='raspberrypi' dc_local_interfaces='127.0.0.1' dc_readhost='' dc_relay_domains='' dc_minimaldns='false' dc_relay_nets='' dc_smarthost='smtp.qq.com' CFILEMODE='644' dc_use_split_config='false' dc_hide_mailname='false' dc_mailname_in_oh='true' dc_localdelivery='mail_spool'
-
smtp的帳號密碼設置
/etc/exim4/passwd.client
smtp.qq.com:郵箱賬號:郵箱密碼
我這里的郵箱密碼使用的是騰訊的授權碼。
-
系統郵箱地址
/etc/email-addresses
root: 郵箱賬號 asterisk: 郵箱賬號
這里一定要填寫
asterisk
,因為chan_dongle使用帳戶asterisk
調用sendemil
命令,所以如果不寫,郵箱服務端不認可。添加root,是為了測試用。update-exim4.conf
更新一下。命令
send_test_email your_email@someisp.com
,測試一下是否能夠發送成功。 -
注意日志文件是/var/log/exim4/mainlog,如果有任何問題先去日志里看看。
-
配置/etc/asterisk/dongle.conf
context=from-trunk ; context for incoming calls group=0 ; calling group rxgain=0 ; increase the incoming volume; may be negative txgain=0 ; increase the outgoint volume; may be negative autodeletesms=yes ; auto delete incoming sms resetdongle=yes ; reset dongle during initialization with ATZ command u2diag=0 ; set ^U2DIAG parameter on device (0 = disable everything except modem function) ; -1 not use ^U2DIAG command
繼續往下翻,配置dongle0,ttyUSB*和你系統中的保持一致,底下的
imei
和imsi
可以通過命令:asterisk -rx "dongle show devices"
找到。; dongle required settings [dongle0] audio=/dev/ttyUSB1 ; tty port for audio connection; no default value data=/dev/ttyUSB2 ; tty port for AT commands; no default value ; or you can omit both audio and data together and use imei=123456789012345 and/or imsi=123456789012345 ; imei and imsi must contain exactly 15 digits ! ; imei/imsi discovery is available on Linux only imei=xxxx imsi=xxxx
-
設置chan_dongle的/etc/asterisk/extensions_custom.conf
[from-trunk] exten => s,n,goto(from-trunk,${DONGLEIMEI},1) exten => sms,1,Verbose(Incoming SMS from ${CALLERID(num)} ${SMS}) exten => sms,n,System(echo '${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)} - ${DONGLENAME} - ${CALLERID(num)}: ${SMS}' >> /var/log/asterisk/sms.txt) exten => sms,n,System( echo "Receiver:${DONGLENUMBER}\nDate:${STRFTIME(${EPOCH},, %Y-%m-%d %H:%M:%S)}\nContent:${SMS}" | /usr/bin/mail -s '[SMS]From:${CALLERID(num)}' 郵箱地址) exten => sms,n,Hangup() exten => ussd,1,Verbose(Incoming USSD: ${USSD}) exten => ussd,n,System(echo '${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)} - ${DONGLENAME} : ${USSD}' >> /var/log/asterisk/ussd.txt) exten => ussd,n,Hangup()
-
輸入命令
amportal admin reload
,更新配置。 -
輸入
asterisk -vvvvvr
進入調試模式,觀察調試信息,這時候發送一條短信到你的號碼上,Incoming SMS from 對方號碼 test -- Executing [sms@from-trunk:2] System("Local/sms@from-trunk-00000011;1", "echo '2017-07-06 18:08:18 - dongle0 - 對方號碼: test' >> /var/log/asterisk/sms.txt") in new stack -- Executing [sms@from-trunk:3] System("Local/sms@from-trunk-00000011;1", " echo "Receiver:+我的號碼\nDate:2017-07-06 18:08:18\nContent:test" | /usr/bin/mail -s '[SMS]From:對方號碼' 我的郵箱") in new stack -- Executing [sms@from-trunk:4] Hangup("Local/sms@from-trunk-00000011;1", "") in new stack == Spawn extension (from-trunk, sms, 4) exited non-zero on 'Local/sms@from-trunk-00000011;1' -- Executing [h@from-trunk:1] Macro("Local/sms@from-trunk-00000011;1", "hangupcall,") in new stack -- Executing [s@macro-hangupcall:1] GotoIf("Local/sms@from-trunk-00000011;1", "1?theend") in new stack -- Goto (macro-hangupcall,s,3) -- Executing [s@macro-hangupcall:3] ExecIf("Local/sms@from-trunk-00000011;1", "0?Set(CDR(recordingfile)=)") in new stack -- Executing [s@macro-hangupcall:4] Hangup("Local/sms@from-trunk-00000011;1", "") in new stack == Spawn extension (macro-hangupcall, s, 4) exited non-zero on 'Local/sms@from-trunk-00000011;1' in macro 'hangupcall' == Spawn extension (from-trunk, h, 1) exited non-zero on 'Local/sms@from-trunk-00000011;1'
-
如果郵件發送失敗,注意觀察日志
tail /var/log/exim4/mainlog
尾聲
-
使用TCP優化通信
其實很簡單,
Settings
->Asterisk SIP Settings
里面的Chan SIP Settings
這個tab頁下面的Enable TCP
選擇Yes
,然后Submit
,別忘了Apply Config
.然后再把之前建好的分機
Extensions
改一下協議。Applications
->Extensions
,Advanced
中設置Transport
為TCP Only
,這里先改一個zoiper客戶端使用的分機Extension
。至此可以用TCP協議了,那么在客戶端中賬戶的
Network Settings
里面的Transport
設置為TCP
,而Telephone
只能用UDP,因此上一步中不要改Telephone上用的分機Extension
。OK,再用手機上的
Zoiper
打電話給Telephone
,如果通了那就OK。 -
錦上添花
為了在DNS解析不及時等異常情況下,能夠遠程登錄到樹莓派上進行操作,可以用ssh進行反向代理控制樹莓派,其實動手比較簡單,但是原理比較難懂。使用ssh開啟反向代理隧道,讓訪問服務器的某個端口都被ssh轉發到本地的5060端口。
這一步比較難懂,但是不難實現,參考下面的教程:
-
然后做成服務使其能夠開機自動啟動,注意最好指定autossh的監控端口
這里面要注意服務器的防火牆要對以上所需的TCP端口打開。
-
通過ssh反向代理暴露FreePBX的WebUI
官方提供了3種安全的暴露FreePBX的WebUI的姿勢:
- 使用SSL
- 使用VPN
- 使用SSH反向代理
千萬不要直接暴露在公網上,即便管理員密碼設的很復雜,因為官方說攻擊者可以利用PHP的漏洞,切記!!!
有了上一步的經驗,這一步就很簡單了
ssh -fNR *:8800:localhost:80 服務器賬號@服務器地址
,這樣直接訪問服務器的8800端口就能進入WebUI管理界面了。
-
使用Fail2Ban保證安全
我的FreePBX剛暴露到公網就被人不斷的強行破解,真是惡心的不行。
參考這篇文章學習下怎么使用Fail2Ban
然后參考這個鏈接配置asterisk
The Last Thing
千萬別忘記備份一下單月沒有問題好了吧zhe這把應該不會有問題了吧,簡直了
再見也許不再見
離別或許成永別
仗義執言勇氣可嘉