前提条件
- Microsoft Teamsが有効なMicrosoft 365アカウント
- Microsoft 電話システムのライセンスが必要
- example.onmicrosoft.comのようなドメインではなく、独自ドメインを利用している必要あり
- 例:example.com
- openSIPS 3.3
- 標準モジュールに追加でdomain/permissions/TLS(proto_tls, tls_openssl,tls_mgm)/droutingが有効になっていること
- openSIPSが動作しているホストのIPアドレスが引けるホスト名があること
- 例:sbc1.example.com(このドメインはMicrosoft 365で利用しているドメインと一致している必要あり)
- SSL証明書(例:sbc1.example.com)
- ダイレクト ルーティングを計画する - SBC 用の公開された信頼できる証明書 - Microsoftに記載されているルート証明機関である必要あり
- 2022/8/20 現在は、Let's Encryptionの証明書のルート証明機関も含まれている(ISRG Root X1 )
当然ながら、Microsoftの認定を受けたSBCでは無いため、試すときは自己責任でお願いします。
別記事で、AsteriskとTeamsを繋げてますが、Asteriskはソースコードの書き換えが必要で、毎回手間なので
openSIPSで試してみました。始めてopenSIPSを使っているので、本当にとりあえず動いているレベルです。
設定(Microsoft 365側)
別記事にまとめていますので、そちらを参照してください
事前準備(openSIPS側)
openSIPSを導入しておきます。
openSIPSは設定ファイルと別に構成情報を保存するデータベースが必須のようです。
(とりあえず、MySQLで動くことは確認済みで、時間が有ったらSQLiteで試したい)
設定(openSIPS側)
既存システムともTLSで接続をしているので、そのあたりは適当に読み替えてください。
opensips.cfg
## UDPソケットは使わないのでコメントアウトします。(既存システムと繋ぐときに使う残す)
#socket=udp:127.0.0.1:5060 # CUSTOMIZE ME
## 同様に proto_udp のモジュールもコメントアウトします。
#loadmodule "proto_udp.so"
## TLSソケットを開ける 下記はプライベートIPアドレスだけど、NAT越えは面倒なのでグローバル前提
socket=tls:192.168.0.1:5061
### 各種モジュール
## データベースにMySQLを使う
loadmodule "db_mysql.so"
## domainモジュール(リクエストが自分のSIPドメインかチェック)
loadmodule "domain.so"
modparam("domain", "db_url", "mysql://dbuser:dbpass@localhost/opensips")
## permissionモジュール(ACL関係)
loadmodule "permissions.so"
modparam("permissions", "db_url", "mysql://dbuser:dbpass@localhost/opensips")
## drouting(dynamic routing)
loadmodule "drouting.so"
modparam("drouting", "db_url","mysql://dbuser:dbpass@localhost/opensips")
## TLS関係
loadmodule "proto_tls.so"
# この辺は細かく調整してない
modparam("proto_tls", "tls_handshake_timeout", 1000)
modparam("proto_tls", "tls_send_timeout", 1000)
modparam("proto_tls", "tls_max_msg_chunks", 8)
loadmodule "tls_openssl.so"
loadmodule "tls_mgm.so"
# server_domain/client_domainのあたりもよく理解してない
# verify_certを1にすると上手く動かない
modparam("tls_mgm","server_domain", "sbc1.example.com")
modparam("tls_mgm","match_ip_address", "[sbc1.example.com]*")
modparam("tls_mgm","verify_cert", "[sbc1.example.com]0")
modparam("tls_mgm","require_cert", "[sbc1.example.com]0")
modparam("tls_mgm","tls_method", "[sbc1.example.com]TLSv1_2")
modparam("tls_mgm","certificate", "[sbc1.example.com]/etc/letsencrypt/live/sbc1.example.com/cert.pem")
modparam("tls_mgm","private_key", "[sbc1.example.com]/etc/letsencrypt/live/sbc1.example.com/privkey.pem")
modparam("tls_mgm","ca_list", "[sbc1.example.com]/etc/letsencrypt/live/sbc1.example.com/fullchain.pem")
modparam("tls_mgm", "ca_dir", "[sbc1.example.com]/etc/ssl/certs/")
modparam("tls_mgm", "client_domain", "defaultdom")
modparam("tls_mgm", "match_ip_address", "[defaultdom]*")
modparam("tls_mgm", "match_sip_domain", "[defaultdom]*")
modparam("tls_mgm", "verify_cert", "[defaultdom]0")
modparam("tls_mgm", "require_cert", "[defaultdom]0")
modparam("tls_mgm", "tls_method", "[defaultdom]tlsv1_2")
modparam("tls_mgm", "certificate", "[defaultdom]/etc/letsencrypt/live/sbc1.example.com/cert.pem")
modparam("tls_mgm", "private_key", "[defaultdom]/etc/letsencrypt/live/sbc1.example.com/privkey.pem")
modparam("tls_mgm","ca_list", "[defaultdom]/etc/letsencrypt/live/sbc1.example.com/fullchain.pem")
modparam("tls_mgm", "ca_dir", "[defaultdom]/etc/ssl/certs/")
## 「route {」の上に追加した
## Teamsから送信されるOPTIONメッセージの返答に必要なContactヘッダーを追加
##「($(ru{s.index, $var(dst)}) != NULL)」の部分の調査必要
local_route {
$var(dst) = "pstnhub.microsoft.com";
if (is_method("OPTIONS") && ($(ru{s.index, $var(dst)}) != NULL))
append_hf("Contact: <sip:sbc1.example.com:5061;transport=tls>\r\n");
}
## 「route {」の中に追記・「if (has_totag()) {」の前
## Teamsから送信されるOPTIONメッセージへ200 OKで返す
## check_source_addressの中の2は後で出てくる、addressテーブルのgrpと紐付いてる
# Checks from MS Teams
if(is_method("OPTIONS") && is_domain_local("$rd") && check_source_address(2)) {
xlog("L_INFO", "[MS TEAMS] OPTIONS In\r\n");
send_reply(200, "OK");
exit;
}
## 「 # record routing」の下
if (!is_method("REGISTER|MESSAGE")){
# 初期値 コメントアウト
# record_route();
# 多分この書き方だと、Teams以外に影響ありそう(専用ならいいんだけろうけど)
record_route_preset("sbc1.example.com:5061;transport=tls");
}
##「 # do lookup with method filtering」の前
# これもいまいちだけど、とりあえずTeamsからはE.164形式で飛んでくるので、それと一致したらTeams関係とする
if ($rU=~"^\+[0-9]+") {
xlog("L_INFO", "[LOGGER] INVITE\r\n");
if (!is_from_local()) {
# ローカル宛でないのは処理しない 403で返す
send_reply(403,"Forbidden access to media service");
exit;
}else if(check_source_address(2)){
# ソースアドレスチェックし、グループ2(Teams側)だったら既存にRelay
xlog("L_INFO", "[LOGGER] FROM TEAMS\r\n");
t_relay(2,"tls:relay-to-sip-proxy.example.com:5061");
exit;
}else if(check_source_address(1)){
# ソースアドレスチェックし、グループ1(既存側)だったらContactヘッダー追加してTeamsにRelay
xlog("L_INFO", "[LOGGER] FROM LOCAL\r\n");
append_hf("Contact: <sip:sbc1.example.com:5061;transport=tls>\r\n");
t_relay(2,"tls:sip.pstnhub.microsoft.com:5061");
exit;
}
}
データベースに登録が必要なレコード
とりあえず、MySQLのクエリのまま
## drouting関係(Teams側でOPTIONメッセージによるSBC監視を行うため)
## 最後のパラメタはListenに書いた内容と合わせる
insert into dr_gateways (gwid,address,probe_mode,state,socket) VALUES ('ms1','sip.pstnhub.microsoft.com',2,0,'tls:192.168.0.1:5061');
insert into dr_gateways (gwid,address,probe_mode,state,socket) VALUES ('ms2','sip2.pstnhub.microsoft.com',2,0,'tls:192.168.0.1:5061');
insert into dr_gateways (gwid,address,probe_mode,state,socket) VALUES ('ms3','sip3.pstnhub.microsoft.com',2,0,'tls:192.168.0.1:5061');
## ACL 既存システム側
insert into address set grp=1, ip='192.168.2.1'
## ACL Teams側(実際は下記URLを参照し登録するサブネット確認してください)
## https://docs.microsoft.com/ja-jp/microsoftteams/direct-routing-plan#sip-signaling-fqdns
insert into address set grp=2, ip='52.112.0.0', mask=14;
insert into address set grp=2, ip='52.120.0.0', mask=14;
## 自分が処理すべきSIPドメイン
## Teams側
insert into domain set domain='sip.pstnhub.microsoft.com';
## 既存側
insert into domain set domain='sbc1.example.com';
パケットフィルタ
ここも、Asteriskで接続している記事を参照してください。
ただ、openSIPSはRTP中継しないのでSIPだけ開ければ大丈夫です。
参考にしたサイト
- OpenSIPS as MS Teams SBC
-
Kamailio as SBC for MS Teams
- Kamailioでも同じ事が出来るようだったけど、とりあえずopenSIPSで試した