mirror of
https://github.com/pion/ice.git
synced 2025-10-05 07:26:55 +08:00

In #475, import low-level API (ReadMsgUDP) to determine destination interface for packets received by UDPConn listen at any(unspecified) address, to fix msg received by incorrect candidate that shared same ufrags. But the api has compatibility issues, also not reliable in some special network cases like AWS/ECS. So this pr revert that change, and make UDPMuxDefault not accept Conn listen at unspecified address. Also provide a NewMultiUDPMuxFromPort helper function to create a MultiUDPMux to listen at all addresses. For ice gather, it will use UDPMux's listen address to generate canidates instead of create it from interfaces.
285 lines
6.5 KiB
Go
285 lines
6.5 KiB
Go
//go:build !js
|
|
// +build !js
|
|
|
|
package ice
|
|
|
|
//nolint:gosec
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"encoding/binary"
|
|
"net"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pion/stun"
|
|
"github.com/pion/transport/test"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestUDPMux(t *testing.T) {
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
conn4, err := net.ListenUDP(udp, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
|
|
require.NoError(t, err)
|
|
|
|
conn6, err := net.ListenUDP(udp, &net.UDPAddr{IP: net.IPv6loopback})
|
|
if err != nil {
|
|
t.Log("IPv6 is not supported on this machine")
|
|
}
|
|
|
|
for network, c := range map[string]net.PacketConn{"udp4": conn4, "udp6": conn6} {
|
|
if c == nil {
|
|
continue
|
|
}
|
|
conn := c
|
|
t.Run(network, func(t *testing.T) {
|
|
udpMux, err := NewUDPMuxDefault(UDPMuxParams{
|
|
Logger: nil,
|
|
UDPConn: conn,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = udpMux.Close()
|
|
_ = conn.Close()
|
|
}()
|
|
|
|
require.NotNil(t, udpMux.LocalAddr(), "udpMux.LocalAddr() is nil")
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
testMuxConnection(t, udpMux, "ufrag1", udp)
|
|
}()
|
|
|
|
// skip ipv6 test on i386
|
|
const ptrSize = 32 << (^uintptr(0) >> 63)
|
|
if ptrSize != 32 || network != "udp6" {
|
|
testMuxConnection(t, udpMux, "ufrag2", network)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
require.NoError(t, udpMux.Close())
|
|
|
|
// can't create more connections
|
|
_, err = udpMux.GetConn("failufrag", udpMux.LocalAddr())
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCantMuxUnspecifiedAddr(t *testing.T) {
|
|
conn, err := net.ListenUDP(udp, &net.UDPAddr{})
|
|
require.NoError(t, err)
|
|
|
|
_, err = NewUDPMuxDefault(UDPMuxParams{
|
|
Logger: nil,
|
|
UDPConn: conn,
|
|
})
|
|
|
|
require.Equal(t, errListenUnspecified, err)
|
|
|
|
_ = conn.Close()
|
|
}
|
|
|
|
func TestAddressEncoding(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
addr net.UDPAddr
|
|
}{
|
|
{
|
|
name: "empty address",
|
|
},
|
|
{
|
|
name: "ipv4",
|
|
addr: net.UDPAddr{
|
|
IP: net.IPv4(244, 120, 0, 5),
|
|
Port: 6000,
|
|
Zone: "",
|
|
},
|
|
},
|
|
{
|
|
name: "ipv6",
|
|
addr: net.UDPAddr{
|
|
IP: net.IPv6loopback,
|
|
Port: 2500,
|
|
Zone: "zone",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
addr := c.addr
|
|
t.Run(c.name, func(t *testing.T) {
|
|
buf := make([]byte, maxAddrSize)
|
|
n, err := encodeUDPAddr(&addr, buf)
|
|
require.NoError(t, err)
|
|
|
|
parsedAddr, err := decodeUDPAddr(buf[:n])
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, &addr, parsedAddr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testMuxConnection(t *testing.T, udpMux *UDPMuxDefault, ufrag string, network string) {
|
|
pktConn, err := udpMux.GetConn(ufrag, udpMux.LocalAddr())
|
|
require.NoError(t, err, "error retrieving muxed connection for ufrag")
|
|
defer func() {
|
|
_ = pktConn.Close()
|
|
}()
|
|
|
|
remoteConn, err := net.DialUDP(network, nil, pktConn.LocalAddr().(*net.UDPAddr))
|
|
require.NoError(t, err, "error dialing test udp connection")
|
|
|
|
testMuxConnectionPair(t, pktConn, remoteConn, ufrag)
|
|
}
|
|
|
|
func testMuxConnectionPair(t *testing.T, pktConn net.PacketConn, remoteConn *net.UDPConn, ufrag string) {
|
|
// initial messages are dropped
|
|
_, err := remoteConn.Write([]byte("dropped bytes"))
|
|
require.NoError(t, err)
|
|
// wait for packet to be consumed
|
|
time.Sleep(time.Millisecond)
|
|
|
|
// write out to establish connection
|
|
msg := stun.New()
|
|
msg.Type = stun.MessageType{Method: stun.MethodBinding, Class: stun.ClassRequest}
|
|
msg.Add(stun.AttrUsername, []byte(ufrag+":otherufrag"))
|
|
msg.Encode()
|
|
_, err = pktConn.WriteTo(msg.Raw, remoteConn.LocalAddr())
|
|
require.NoError(t, err)
|
|
|
|
// ensure received
|
|
buf := make([]byte, receiveMTU)
|
|
n, err := remoteConn.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, msg.Raw, buf[:n])
|
|
|
|
// start writing packets through mux
|
|
targetSize := 1 * 1024 * 1024
|
|
readDone := make(chan struct{}, 1)
|
|
remoteReadDone := make(chan struct{}, 1)
|
|
|
|
// read packets from the muxed side
|
|
go func() {
|
|
defer func() {
|
|
t.Logf("closing read chan for: %s", ufrag)
|
|
close(readDone)
|
|
}()
|
|
readBuf := make([]byte, receiveMTU)
|
|
nextSeq := uint32(0)
|
|
for read := 0; read < targetSize; {
|
|
n, _, err := pktConn.ReadFrom(readBuf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, receiveMTU, n)
|
|
|
|
verifyPacket(t, readBuf[:n], nextSeq)
|
|
|
|
// write it back to sender
|
|
_, err = pktConn.WriteTo(readBuf[:n], remoteConn.LocalAddr())
|
|
require.NoError(t, err)
|
|
|
|
read += n
|
|
nextSeq++
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
defer func() {
|
|
close(remoteReadDone)
|
|
}()
|
|
readBuf := make([]byte, receiveMTU)
|
|
nextSeq := uint32(0)
|
|
for read := 0; read < targetSize; {
|
|
n, _, err := remoteConn.ReadFrom(readBuf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, receiveMTU, n)
|
|
|
|
verifyPacket(t, readBuf[:n], nextSeq)
|
|
|
|
read += n
|
|
nextSeq++
|
|
}
|
|
}()
|
|
|
|
sequence := 0
|
|
for written := 0; written < targetSize; {
|
|
buf := make([]byte, receiveMTU)
|
|
// byte0-4: sequence
|
|
// bytes4-24: sha1 checksum
|
|
// bytes24-mtu: random data
|
|
_, err := rand.Read(buf[24:])
|
|
require.NoError(t, err)
|
|
h := sha1.Sum(buf[24:]) //nolint:gosec
|
|
copy(buf[4:24], h[:])
|
|
binary.LittleEndian.PutUint32(buf[0:4], uint32(sequence))
|
|
|
|
_, err = remoteConn.Write(buf)
|
|
require.NoError(t, err)
|
|
|
|
written += len(buf)
|
|
sequence++
|
|
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
|
|
<-readDone
|
|
<-remoteReadDone
|
|
}
|
|
|
|
func verifyPacket(t *testing.T, b []byte, nextSeq uint32) {
|
|
readSeq := binary.LittleEndian.Uint32(b[0:4])
|
|
require.Equal(t, nextSeq, readSeq)
|
|
h := sha1.Sum(b[24:]) //nolint:gosec
|
|
require.Equal(t, h[:], b[4:24])
|
|
}
|
|
|
|
func TestUDPMux_Agent_Restart(t *testing.T) {
|
|
oneSecond := time.Second
|
|
connA, connB := pipe(&AgentConfig{
|
|
DisconnectedTimeout: &oneSecond,
|
|
FailedTimeout: &oneSecond,
|
|
})
|
|
|
|
aNotifier, aConnected := onConnected()
|
|
require.NoError(t, connA.agent.OnConnectionStateChange(aNotifier))
|
|
|
|
bNotifier, bConnected := onConnected()
|
|
require.NoError(t, connB.agent.OnConnectionStateChange(bNotifier))
|
|
|
|
// Maintain Credentials across restarts
|
|
ufragA, pwdA, err := connA.agent.GetLocalUserCredentials()
|
|
require.NoError(t, err)
|
|
|
|
ufragB, pwdB, err := connB.agent.GetLocalUserCredentials()
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, err)
|
|
|
|
// Restart and Re-Signal
|
|
require.NoError(t, connA.agent.Restart(ufragA, pwdA))
|
|
require.NoError(t, connB.agent.Restart(ufragB, pwdB))
|
|
|
|
require.NoError(t, connA.agent.SetRemoteCredentials(ufragB, pwdB))
|
|
require.NoError(t, connB.agent.SetRemoteCredentials(ufragA, pwdA))
|
|
gatherAndExchangeCandidates(connA.agent, connB.agent)
|
|
|
|
// Wait until both have gone back to connected
|
|
<-aConnected
|
|
<-bConnected
|
|
|
|
require.NoError(t, connA.agent.Close())
|
|
require.NoError(t, connB.agent.Close())
|
|
}
|