This commit is contained in:
onysd 2026-07-20 15:08:53 +03:00
parent 89496b3558
commit 406a8805d8
5 changed files with 139 additions and 9 deletions

View file

@ -50,8 +50,22 @@ func (r *Router) phoneCallConnections(callerID int64) []domain.PhoneCallConnecti
// stun flag——单条目 stun+turn 在 Android 上只会产出 TURN server、丢失 STUN
//org_telegram_messenger_voip_Instance.cpp:848-884。TDesktop 两种写法都认。
// TURN username 是 REST 格式 "<expiry>:<uid>",天然避开 "reflector" 劫持禁区。
return []domain.PhoneCallConnection{
{ID: 1, IP: t.IP(), Port: t.Port(), Stun: true},
{ID: 2, IP: t.IP(), Port: t.Port(), Username: username, Password: password, Turn: true},
//
// 每个可达 IPAdvertiseIP + ExtraIPs都下发一对 STUN/TURN 候选ICE 逐一
// 尝试并选可达者LAN 客户端走 LAN IP、外网客户端走公网 IP。id 必须全局唯一
// 且从 1 递增DrKLO 用 id 做 reflector 映射)。凭据与 IP 无关TURN REST 只
// 校验 HMAC同一份 username/password 对所有 IP 有效。
ips := append([]string{t.IP()}, t.ExtraIPs()...)
conns := make([]domain.PhoneCallConnection, 0, len(ips)*2)
id := int64(1)
for _, ip := range ips {
if ip == "" {
continue
}
conns = append(conns, domain.PhoneCallConnection{ID: id, IP: ip, Port: t.Port(), Stun: true})
id++
conns = append(conns, domain.PhoneCallConnection{ID: id, IP: ip, Port: t.Port(), Username: username, Password: password, Turn: true})
id++
}
return conns
}

View file

@ -0,0 +1,98 @@
package rpc
import (
"testing"
"github.com/iamxvbaba/td/clock"
"go.uber.org/zap"
)
// fakeTURN is a minimal turnsrv.Service for exercising phoneCallConnections.
type fakeTURN struct {
ip string
port int
extra []string
enabled bool
credUser string
credPass string
}
func (f *fakeTURN) Enabled() bool { return f.enabled }
func (f *fakeTURN) Credentials(string) (string, string, error) {
return f.credUser, f.credPass, nil
}
func (f *fakeTURN) IP() string { return f.ip }
func (f *fakeTURN) Port() int { return f.port }
func (f *fakeTURN) ExtraIPs() []string { return f.extra }
func (f *fakeTURN) Close() error { return nil }
func newTURNRouter(t *testing.T, turn *fakeTURN) *Router {
t.Helper()
return New(Config{}, Deps{TURN: turn}, zap.NewNop(), clock.System)
}
func TestPhoneCallConnectionsDisabledTURNReturnsNil(t *testing.T) {
r := newTURNRouter(t, &fakeTURN{enabled: false})
if conns := r.phoneCallConnections(1); conns != nil {
t.Fatalf("disabled TURN: want nil, got %+v", conns)
}
}
func TestPhoneCallConnectionsSingleIP(t *testing.T) {
r := newTURNRouter(t, &fakeTURN{
enabled: true, ip: "89.28.58.29", port: 12400,
credUser: "u", credPass: "p",
})
conns := r.phoneCallConnections(1)
if len(conns) != 2 {
t.Fatalf("single IP: want 2 conns (stun+turn), got %d: %+v", len(conns), conns)
}
if !conns[0].Stun || conns[0].Turn || conns[0].ID != 1 || conns[0].IP != "89.28.58.29" {
t.Fatalf("conn[0] should be STUN id1 on public IP, got %+v", conns[0])
}
if !conns[1].Turn || conns[1].Stun || conns[1].ID != 2 || conns[1].Username != "u" || conns[1].Password != "p" {
t.Fatalf("conn[1] should be TURN id2 with creds, got %+v", conns[1])
}
}
func TestPhoneCallConnectionsExtraIPsAddCandidatesWithUniqueIDs(t *testing.T) {
r := newTURNRouter(t, &fakeTURN{
enabled: true, ip: "89.28.58.29", port: 12400,
extra: []string{"192.168.0.20", ""}, // empty entry must be skipped
credUser: "u", credPass: "p",
})
conns := r.phoneCallConnections(1)
// public (stun+turn) + LAN (stun+turn) = 4; the empty extra IP is skipped.
if len(conns) != 4 {
t.Fatalf("want 4 conns, got %d: %+v", len(conns), conns)
}
seenIDs := map[int64]bool{}
for _, c := range conns {
if seenIDs[c.ID] {
t.Fatalf("duplicate connection id %d: %+v", c.ID, conns)
}
seenIDs[c.ID] = true
if c.IP == "" {
t.Fatalf("empty IP leaked into connections: %+v", conns)
}
}
// IDs must be a contiguous 1..4 run (DrKLO maps reflectors by id).
for id := int64(1); id <= 4; id++ {
if !seenIDs[id] {
t.Fatalf("missing contiguous id %d: %+v", id, conns)
}
}
// The LAN IP must appear as both a STUN and a TURN candidate.
var lanStun, lanTurn bool
for _, c := range conns {
if c.IP == "192.168.0.20" && c.Stun {
lanStun = true
}
if c.IP == "192.168.0.20" && c.Turn {
lanTurn = true
}
}
if !lanStun || !lanTurn {
t.Fatalf("LAN IP must yield both STUN and TURN candidates, got %+v", conns)
}
}