diff --git a/.changeset/sip-allowed-addresses-source-only.md b/.changeset/sip-allowed-addresses-source-only.md new file mode 100644 index 000000000..f333bc5b2 --- /dev/null +++ b/.changeset/sip-allowed-addresses-source-only.md @@ -0,0 +1,6 @@ +--- +"github.com/livekit/protocol": patch +"@livekit/protocol": patch +--- + +Match an inbound trunk's allowed_addresses against the source address only diff --git a/sip/sip.go b/sip/sip.go index f74653b4b..1cebec130 100644 --- a/sip/sip.go +++ b/sip/sip.go @@ -443,17 +443,23 @@ func matchAddrMask(ip netip.Addr, mask string) bool { return pref.Contains(ip) } -func matchAddrMasks(addr string, host string, masks []string) bool { +// matchAddrMasks reports whether the call's source address is inside one of the masks. Only the +// transport address is consulted: a From host is written by the caller, so matching a mask against it +// would let any INVITE satisfy the allowlist by claiming the right host. A hostname entry therefore +// matches nothing, and a list made only of hostnames admits no call. +func matchAddrMasks(addr string, masks []string) bool { ip, err := netip.ParseAddr(addr) if err != nil { return true } + // A dual-stack listener reports IPv4 peers as ::ffff:a.b.c.d; the masks are written as IPv4. + ip = ip.Unmap() masks = filterInvalidAddrMasks(masks) if len(masks) == 0 { return true } for _, mask := range masks { - if mask == host || matchAddrMask(ip, mask) { + if matchAddrMask(ip, mask) { return true } } @@ -543,7 +549,7 @@ func MatchTrunkDetailed(it iters.Iter[*livekit.SIPInboundTrunkInfo], call *rpc.S continue } } - if !matchAddrMasks(call.SourceIp, call.From.Host, tr.AllowedAddresses) { + if !matchAddrMasks(call.SourceIp, tr.AllowedAddresses) { if !opt.Filtered(tr, TrunkFilteredSourceAddressDisallowed) { continue } diff --git a/sip/sip_test.go b/sip/sip_test.go index 57e3a8691..5b38aace5 100644 --- a/sip/sip_test.go +++ b/sip/sip_test.go @@ -205,13 +205,44 @@ var trunkCases = []struct { exp: -1, }, { - name: "inbound with host mask", + name: "inbound with host mask is not matched by the From host", trunks: []*livekit.SIPTrunkInfo{ {SipTrunkId: "bbb", OutboundNumber: sipNumber2, InboundAddresses: []string{ "10.10.10.0/24", "sip.example.com", }}, }, + exp: -1, + }, + { + name: "inbound with address mask is not matched by a From host naming that address", + trunks: []*livekit.SIPTrunkInfo{ + {SipTrunkId: "bbb", OutboundNumber: sipNumber2, InboundAddresses: []string{ + "192.76.120.10", + }}, + }, + host: "192.76.120.10", + exp: -1, + }, + { + name: "inbound with address mask is matched by an IPv4-mapped source address", + trunks: []*livekit.SIPTrunkInfo{ + {SipTrunkId: "bbb", OutboundNumber: sipNumber2, InboundAddresses: []string{ + "192.76.120.10", + }}, + }, + src: "::ffff:192.76.120.10", + exp: 0, + }, + { + name: "inbound with host mask is matched by the source address", + trunks: []*livekit.SIPTrunkInfo{ + {SipTrunkId: "bbb", OutboundNumber: sipNumber2, InboundAddresses: []string{ + "10.10.10.0/24", + "sip.example.com", + }}, + }, + src: "10.10.10.7", exp: 0, }, { @@ -1192,7 +1223,6 @@ func TestMatchMasks(t *testing.T) { cases := []struct { name string addr string - host string masks []string exp bool }{ @@ -1235,18 +1265,32 @@ func TestMatchMasks(t *testing.T) { exp: false, }, { - name: "hostname", + name: "address mask is not the From host", + addr: "1.1.1.1", + masks: []string{ + "192.76.120.10", + }, + exp: false, + }, + { + name: "mapped address", + addr: "::ffff:192.168.0.10", + masks: []string{ + "192.168.0.0/24", + }, + exp: true, + }, + { + name: "hostname matches nothing", addr: "192.168.0.10", - host: "sip.example.com", masks: []string{ "sip.example.com", }, - exp: true, + exp: false, }, { name: "invalid hostname", addr: "192.168.0.10", - host: "sip.example.com", masks: []string{ "some.domain", }, @@ -1271,20 +1315,19 @@ func TestMatchMasks(t *testing.T) { exp: false, }, { - name: "domain name", + name: "domain name beside a wrong range", addr: "192.168.0.10", - host: "sip.example.com", masks: []string{ "some.domain", "192.168.1.0/24", "sip.example.com", }, - exp: true, + exp: false, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - got := matchAddrMasks(c.addr, c.host, c.masks) + got := matchAddrMasks(c.addr, c.masks) require.Equal(t, c.exp, got) }) }