mirror of
https://github.com/livekit/livekit.git
synced 2026-03-29 09:19:53 +00:00
185 lines
6.5 KiB
Go
185 lines
6.5 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
|
|
"github.com/dennwc/iters"
|
|
"github.com/twitchtv/twirp"
|
|
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/protocol/logger"
|
|
"github.com/livekit/protocol/rpc"
|
|
"github.com/livekit/protocol/sip"
|
|
)
|
|
|
|
// matchSIPTrunk finds a SIP Trunk definition matching the request.
|
|
// Returns nil if no rules matched or an error if there are conflicting definitions.
|
|
func (s *IOInfoService) matchSIPTrunk(ctx context.Context, trunkID string, call *rpc.SIPCall) (*livekit.SIPInboundTrunkInfo, error) {
|
|
if s.ss == nil {
|
|
return nil, ErrSIPNotConnected
|
|
}
|
|
if trunkID != "" {
|
|
// This is a best-effort optimization. Fallthrough to listing trunks if it doesn't work.
|
|
if tr, err := s.ss.LoadSIPInboundTrunk(ctx, trunkID); err == nil {
|
|
result, err := sip.MatchTrunkDetailed(iters.Slice([]*livekit.SIPInboundTrunkInfo{tr}), call)
|
|
if err == nil && result.MatchType != sip.TrunkMatchNone {
|
|
return result.Trunk, nil
|
|
}
|
|
}
|
|
}
|
|
it := s.ListAllSIPInboundTrunks(ctx, call.To.User)
|
|
result, err := sip.MatchTrunkDetailed(it, call)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// If trunks exist but none matched, return a specific error
|
|
if result.MatchType == sip.TrunkMatchNone {
|
|
return nil, ErrSIPTrunkNotFound
|
|
}
|
|
// If no trunks were defined at all, return nil (this is different from TrunkMatchNone)
|
|
if result.MatchType == sip.TrunkMatchEmpty {
|
|
return nil, nil
|
|
}
|
|
return result.Trunk, nil
|
|
}
|
|
|
|
func (s *IOInfoService) SelectSIPInboundTrunk(ctx context.Context, called string) iters.Iter[*livekit.SIPInboundTrunkInfo] {
|
|
it := livekit.ListPageIter(s.ss.ListSIPInboundTrunk, &livekit.ListSIPInboundTrunkRequest{
|
|
Numbers: []string{called},
|
|
})
|
|
return iters.PagesAsIter(ctx, it)
|
|
}
|
|
|
|
func (s *IOInfoService) ListAllSIPInboundTrunks(ctx context.Context, called string) iters.Iter[*livekit.SIPInboundTrunkInfo] {
|
|
it := livekit.ListPageIter(s.ss.ListSIPInboundTrunk, &livekit.ListSIPInboundTrunkRequest{
|
|
Numbers: nil,
|
|
})
|
|
return iters.PagesAsIter(ctx, it)
|
|
}
|
|
|
|
// matchSIPDispatchRule finds the best dispatch rule matching the request parameters. Returns an error if no rule matched.
|
|
// Trunk parameter can be nil, in which case only wildcard dispatch rules will be effective (ones without Trunk IDs).
|
|
func (s *IOInfoService) matchSIPDispatchRule(ctx context.Context, trunk *livekit.SIPInboundTrunkInfo, req *rpc.EvaluateSIPDispatchRulesRequest) (*livekit.SIPDispatchRuleInfo, error) {
|
|
if s.ss == nil {
|
|
return nil, ErrSIPNotConnected
|
|
}
|
|
var trunkID string
|
|
if trunk != nil {
|
|
trunkID = trunk.SipTrunkId
|
|
}
|
|
// Trunk can still be nil here in case none matched or were defined.
|
|
// This is still fine, but only in case we'll match exactly one wildcard dispatch rule.
|
|
it := s.SelectSIPDispatchRule(ctx, trunkID)
|
|
return sip.MatchDispatchRuleIter(trunk, it, req)
|
|
}
|
|
|
|
func (s *IOInfoService) SelectSIPDispatchRule(ctx context.Context, trunkID string) iters.Iter[*livekit.SIPDispatchRuleInfo] {
|
|
var trunkIDs []string
|
|
if trunkID != "" {
|
|
trunkIDs = []string{trunkID}
|
|
}
|
|
it := livekit.ListPageIter(s.ss.ListSIPDispatchRule, &livekit.ListSIPDispatchRuleRequest{
|
|
TrunkIds: trunkIDs,
|
|
})
|
|
return iters.PagesAsIter(ctx, it)
|
|
}
|
|
|
|
func (s *IOInfoService) EvaluateSIPDispatchRules(ctx context.Context, req *rpc.EvaluateSIPDispatchRulesRequest) (*rpc.EvaluateSIPDispatchRulesResponse, error) {
|
|
call := req.SIPCall()
|
|
log := logger.GetLogger()
|
|
log = log.WithValues("toUser", call.To.User, "fromUser", call.From.User, "src", call.SourceIp)
|
|
if call.SourceIp == "" {
|
|
log.Warnw("source address is not set", nil)
|
|
// TODO: return error in the next release
|
|
}
|
|
_, err := netip.ParseAddr(call.SourceIp)
|
|
if call.SourceIp != "" && err != nil {
|
|
log.Errorw("cannot parse source IP", err)
|
|
return nil, twirp.WrapError(twirp.NewError(twirp.InvalidArgument, err.Error()), err)
|
|
}
|
|
trunk, err := s.matchSIPTrunk(ctx, req.SipTrunkId, call)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
trunkID := ""
|
|
if trunk != nil {
|
|
trunkID = trunk.SipTrunkId
|
|
}
|
|
log = log.WithValues("sipTrunk", trunkID)
|
|
if trunk != nil {
|
|
log.Debugw("SIP trunk matched")
|
|
} else {
|
|
log.Debugw("No SIP trunk matched")
|
|
}
|
|
best, err := s.matchSIPDispatchRule(ctx, trunk, req)
|
|
if err != nil {
|
|
if e := (*sip.ErrNoDispatchMatched)(nil); errors.As(err, &e) {
|
|
return &rpc.EvaluateSIPDispatchRulesResponse{
|
|
SipTrunkId: trunkID,
|
|
Result: rpc.SIPDispatchResult_DROP,
|
|
}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
log.Debugw("SIP dispatch rule matched", "sipRule", best.SipDispatchRuleId)
|
|
resp, err := sip.EvaluateDispatchRule("", trunk, best, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp.SipTrunkId = trunkID
|
|
return resp, err
|
|
}
|
|
|
|
func (s *IOInfoService) GetSIPTrunkAuthentication(ctx context.Context, req *rpc.GetSIPTrunkAuthenticationRequest) (*rpc.GetSIPTrunkAuthenticationResponse, error) {
|
|
call := req.SIPCall()
|
|
log := logger.GetLogger()
|
|
log = log.WithValues("toUser", call.To.User, "fromUser", call.From.User, "src", call.SourceIp)
|
|
if call.SourceIp == "" {
|
|
log.Warnw("source address is not set", nil)
|
|
// TODO: return error in the next release
|
|
}
|
|
_, err := netip.ParseAddr(call.SourceIp)
|
|
if call.SourceIp != "" && err != nil {
|
|
log.Errorw("cannot parse source IP", err)
|
|
return nil, twirp.WrapError(twirp.NewError(twirp.InvalidArgument, err.Error()), err)
|
|
}
|
|
trunk, err := s.matchSIPTrunk(ctx, "", call)
|
|
if err != nil {
|
|
// Check if this is the specific "no SIP trunk matched" error
|
|
if err == ErrSIPTrunkNotFound {
|
|
err := twirp.NotFoundError(fmt.Sprintf("sip trunk not found for destination %q", req.Call.To))
|
|
log.Errorw("No SIP trunk matched for auth", err, "sipTrunk", "", "to", req.Call.To)
|
|
return nil, err
|
|
}
|
|
return nil, err
|
|
}
|
|
if trunk == nil {
|
|
// This case is for TrunkMatchEmpty (no trunks defined at all)
|
|
// We don't return an error in this case
|
|
return nil, nil
|
|
}
|
|
log.Debugw("SIP trunk matched for auth", "sipTrunk", trunk.SipTrunkId)
|
|
return &rpc.GetSIPTrunkAuthenticationResponse{
|
|
SipTrunkId: trunk.SipTrunkId,
|
|
Username: trunk.AuthUsername,
|
|
Password: trunk.AuthPassword,
|
|
}, nil
|
|
}
|