Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/soft-webs-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"github.com/livekit/protocol": patch
"@livekit/protocol": patch
---

SIP authentication: compare IP masks against remote address rather than From header.
27 changes: 18 additions & 9 deletions sip/sip.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,19 +428,23 @@ func filterInvalidAddrMasks(masks []string) []string {
return out
}

func matchAddrMask(ip netip.Addr, mask string) bool {
if !strings.Contains(mask, "/") {
expIP, err := netip.ParseAddr(mask)
func parseIPMask(mask string) (netip.Prefix, bool) {
addr, bits, hasBits := strings.Cut(mask, "/")
if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
addr = addr[1 : len(addr)-1]
}
if !hasBits {
ip, err := netip.ParseAddr(addr)
if err != nil {
return false
return netip.Prefix{}, false
}
return ip == expIP
return netip.PrefixFrom(ip, ip.BitLen()), true
}
pref, err := netip.ParsePrefix(mask)
pref, err := netip.ParsePrefix(fmt.Sprintf("%s/%s", addr, bits))
if err != nil {
return false
return netip.Prefix{}, false
}
return pref.Contains(ip)
return pref, true
}

func matchAddrMasks(addr string, host string, masks []string) bool {
Expand All @@ -453,7 +457,12 @@ func matchAddrMasks(addr string, host string, masks []string) bool {
return true
}
for _, mask := range masks {
if mask == host || matchAddrMask(ip, mask) {
if pref, ok := parseIPMask(mask); ok {
// IP masks must only match the source IP, never the (spoofable) From host.
if pref.Contains(ip) {
return true
}
} else if mask == host {
return true
}
}
Expand Down
95 changes: 79 additions & 16 deletions sip/sip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1163,27 +1163,37 @@ func TestEvaluateDispatchRule_RespectsRuleMediaTimeout(t *testing.T) {
require.Equal(t, 5*time.Minute, res.Media.MediaTimeout.AsDuration())
}

func TestMatchIP(t *testing.T) {
func TestParseIPMask(t *testing.T) {
cases := []struct {
addr string
mask string
valid bool
exp bool
mask string
exp string // empty if not an IP mask
}{
{addr: "192.168.0.10", mask: "192.168.0.10", valid: true, exp: true},
{addr: "192.168.0.10", mask: "192.168.0.11", valid: true, exp: false},
{addr: "192.168.0.10", mask: "192.168.0.0/24", valid: true, exp: true},
{addr: "192.168.0.10", mask: "192.168.0.10/0", valid: true, exp: true},
{addr: "192.168.0.10", mask: "192.170.0.0/24", valid: true, exp: false},
{mask: "192.168.0.10", exp: "192.168.0.10/32"},
{mask: "192.168.0.0/24", exp: "192.168.0.0/24"},
{mask: "2001:db8::1", exp: "2001:db8::1/128"},
{mask: "[2001:db8::1]", exp: "2001:db8::1/128"},
{mask: "[2001:db8::]/32", exp: "2001:db8::/32"},
{mask: "[::]", exp: "::/128"},
{mask: "[::]/0", exp: "::/0"},

// Not IP masks: treated as hostnames.
{mask: "sip.example.com"},
{mask: "sip.example.com/24"},
{mask: "192.168.0.10/33"},
{mask: "192.168.0.10/"},
{mask: "[192.168.0.10"},
{mask: "[2001:db8::1]:5060"},
{mask: "[[2001:db8::1]]"},
}
for _, c := range cases {
t.Run(c.mask, func(t *testing.T) {
ip, err := netip.ParseAddr(c.addr)
require.NoError(t, err)
got := isValidMask(c.mask)
require.Equal(t, c.valid, got)
got = matchAddrMask(ip, c.mask)
require.Equal(t, c.exp, got)
pref, ok := parseIPMask(c.mask)
if c.exp == "" {
require.False(t, ok)
return
}
require.True(t, ok)
require.Equal(t, netip.MustParsePrefix(c.exp), pref)
})
}
}
Expand Down Expand Up @@ -1281,6 +1291,59 @@ func TestMatchMasks(t *testing.T) {
},
exp: true,
},
{
name: "ip spoofed in from host",
addr: "203.0.113.5",
host: "192.168.0.10",
masks: []string{
"192.168.0.10",
},
exp: false,
},
{
name: "ip range spoofed in from host",
addr: "203.0.113.5",
host: "192.168.0.0/24",
masks: []string{
"192.168.0.0/24",
},
exp: false,
},
{
name: "ipv6 spoofed in from host",
addr: "203.0.113.5",
host: "[2001:db8::1]",
masks: []string{
"[2001:db8::1]",
},
exp: false,
},
{
name: "ipv6 bracketed",
addr: "2001:db8::1",
host: "sip.example.com",
masks: []string{
"[2001:db8::1]",
},
exp: true,
},
{
name: "ipv6 bracketed range",
addr: "2001:db8::1",
masks: []string{
"[2001:db8::]/32",
},
exp: true,
},
{
name: "ip matches source, from host differs",
addr: "192.168.0.10",
host: "sip.example.com",
masks: []string{
"192.168.0.10",
},
exp: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
Expand Down
Loading