UPnP/SSDP: prefer link-local addresses if available

This commit is contained in:
Sybren A. Stüvel 2022-03-08 11:03:48 +01:00
parent fca628b325
commit bb91c2e6d6
2 changed files with 133 additions and 20 deletions

View File

@ -78,16 +78,10 @@ func networkInterfaces(includeLinkLocal, includeLocalhost bool) ([]net.IP, error
Str("iface", iface.Name). Str("iface", iface.Name).
Logger() Logger()
switch { switch {
case ip.IsMulticast():
logger.Debug().Msg(" - skipping multicast")
case ip.IsMulticast(): case ip.IsMulticast():
logger.Debug().Msg(" - skipping multicast") logger.Debug().Msg(" - skipping multicast")
case ip.IsUnspecified(): case ip.IsUnspecified():
logger.Debug().Msg(" - skipping unspecified") logger.Debug().Msg(" - skipping unspecified")
case !includeLinkLocal && ip.IsLinkLocalUnicast():
logger.Debug().Msg(" - skipping link-local")
case !includeLocalhost && ip.IsLoopback():
logger.Debug().Msg(" - skipping localhost")
default: default:
logger.Debug().Msg(" - usable") logger.Debug().Msg(" - usable")
ifaceAddresses = append(ifaceAddresses, ip) ifaceAddresses = append(ifaceAddresses, ip)
@ -104,26 +98,69 @@ func networkInterfaces(includeLinkLocal, includeLocalhost bool) ([]net.IP, error
return usableAddresses, nil return usableAddresses, nil
} }
// filterAddresses removes "privacy extension" addresses. // filterAddresses reduces the number of IPv6 addresses.
// It assumes the list of addresses belong to the same network interface, and // It prefers link-local addresses; if these are in the list, all the other IPv6
// that the OS reports preferred (i.e. private/random) addresses before // addresses will be removed. Link-local addresses are stable and meant for
// non-random ones. // same-network connections, which is exactly what Flamenco needs.
// Loopback addresses (localhost) are always filtered out, unless they're the only addresses available.
func filterAddresses(addrs []net.IP) []net.IP { func filterAddresses(addrs []net.IP) []net.IP {
keep := make([]net.IP, 0) keepAddrs := make([]net.IP, 0)
var lastSeenIP net.IP if hasOnlyLoopback(addrs) {
return addrs
}
var keepLinkLocalv6 = hasLinkLocalv6(addrs)
var keepLinkLocalv4 = hasLinkLocalv4(addrs)
var keep bool
for _, addr := range addrs { for _, addr := range addrs {
if addr.To4() != nil { if addr.IsLoopback() {
// IPv4 addresses are always kept.
keep = append(keep, addr)
continue continue
} }
lastSeenIP = addr isv4 := isIPv4(addr)
} if isv4 {
if len(lastSeenIP) > 0 { keep = keepLinkLocalv4 == addr.IsLinkLocalUnicast()
keep = append(keep, lastSeenIP) } else {
keep = keepLinkLocalv6 == addr.IsLinkLocalUnicast()
} }
return keep if keep {
keepAddrs = append(keepAddrs, addr)
}
}
return keepAddrs
}
func isIPv4(addr net.IP) bool {
return addr.To4() != nil
}
func hasLinkLocalv6(addrs []net.IP) bool {
for _, addr := range addrs {
if !isIPv4(addr) && addr.IsLinkLocalUnicast() {
return true
}
}
return false
}
func hasLinkLocalv4(addrs []net.IP) bool {
for _, addr := range addrs {
if isIPv4(addr) && addr.IsLinkLocalUnicast() {
return true
}
}
return false
}
func hasOnlyLoopback(addrs []net.IP) bool {
for _, addr := range addrs {
if !addr.IsLoopback() {
return false
}
}
return true
} }

View File

@ -0,0 +1,76 @@
package own_url
// SPDX-License-Identifier: GPL-3.0-or-later
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
var (
globalIPv6 = net.ParseIP("2a10:3780:2:52:185:93:175:46")
lanIPv6 = globalIPv6
linkLocalIPv6 = net.ParseIP("fe80::5054:ff:fede:2ad7")
localhostIPv6 = net.ParseIP("::1")
globalIPv4 = net.ParseIP("8.8.8.8")
lanIPv4 = net.ParseIP("192.168.0.1")
linkLocalIPv4 = net.ParseIP("169.254.47.42")
localhostIPv4 = net.ParseIP("127.0.0.1")
)
func Test_filterAddresses(t *testing.T) {
tests := []struct {
name string
expect []net.IP
input []net.IP
}{
// IPv6 tests:
// Not a link-local address present, then use all but localhost
{"IPv6 without link-local",
[]net.IP{globalIPv6, lanIPv6},
[]net.IP{globalIPv6, lanIPv6, localhostIPv6}},
// Link-local address present, just use that one.
{"IPv6 with link-local",
[]net.IP{linkLocalIPv6},
[]net.IP{linkLocalIPv6, lanIPv6, localhostIPv6}},
// Only loopback
{"IPv6 with only loopback",
[]net.IP{localhostIPv6},
[]net.IP{localhostIPv6}},
// IPv4 tests:
// Not a link-local address present, then use all but localhost
{"IPv4 without link-local",
[]net.IP{globalIPv4, lanIPv4},
[]net.IP{globalIPv4, lanIPv4, localhostIPv4}},
// Link-local address present, just use that one.
{"IPv4 with link-local",
[]net.IP{linkLocalIPv4},
[]net.IP{linkLocalIPv4, lanIPv4, localhostIPv4}},
// Only loopback
{"IPv4 with only loopback",
[]net.IP{localhostIPv4},
[]net.IP{localhostIPv4}},
// Mixed IPv4/IPv6 tests:
// IPv4 no link-local, but IPv6 with link-local:
{"IPv4 w/o, IPv6 w/ link-local",
[]net.IP{lanIPv4, linkLocalIPv6},
[]net.IP{lanIPv4, localhostIPv4, lanIPv6, linkLocalIPv6}},
// IPv4 link-local, IPv6 without:
{"IPv4 w/, IPv4 w/o link-local",
[]net.IP{linkLocalIPv4, lanIPv6},
[]net.IP{linkLocalIPv4, lanIPv4, lanIPv6}},
// Only loopback
{"IPv4 + IPv6 with only loopback",
[]net.IP{localhostIPv4, localhostIPv6},
[]net.IP{localhostIPv4, localhostIPv6}},
}
for _, tt := range tests {
got := filterAddresses(tt.input)
assert.EqualValues(t, tt.expect, got, "for test %q", tt.name)
}
}