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
23 changes: 13 additions & 10 deletions internal/net/udp/packet_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var (

// listener augments a connection-oriented Listener over a UDP PacketConn.
type listener struct {
pConn *net.UDPConn
pConn net.PacketConn

accepting atomic.Value // bool
acceptCh chan *PacketConn
Expand Down Expand Up @@ -166,21 +166,24 @@ type ListenConfig struct {
//
//nolint:contextcheck
func (lc *ListenConfig) Listen(network string, laddr *net.UDPAddr) (dtlsnet.PacketListener, error) {
if lc.Backlog == 0 {
lc.Backlog = defaultListenBacklog
}

laddrStr := ":0"
if laddr != nil {
laddrStr = laddr.String()
}
innerConn, err := lc.ListenConfig.ListenPacket(context.Background(), network, laddrStr)
conn, err := lc.ListenConfig.ListenPacket(context.Background(), network, laddrStr)
if err != nil {
return nil, err
}
conn, ok := innerConn.(*net.UDPConn)
if !ok {
return nil, errors.New("listen packet not a *net.UDPConn") //nolint:err113

return lc.ListenPacketConn(conn), nil
}

// ListenPacketConn creates a new listener that accepts connections on the
// supplied packet connection. The listener takes ownership of the connection
// and closes it on Close.
func (lc *ListenConfig) ListenPacketConn(conn net.PacketConn) dtlsnet.PacketListener {
if lc.Backlog == 0 {
lc.Backlog = defaultListenBacklog
}

packetListener := &listener{
Expand All @@ -207,7 +210,7 @@ func (lc *ListenConfig) Listen(network string, laddr *net.UDPAddr) (dtlsnet.Pack
packetListener.readWG.Done()
}()

return packetListener, nil
return packetListener
}

// Listen creates a new listener using default ListenConfig.
Expand Down
42 changes: 42 additions & 0 deletions internal/net/udp/packet_conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,48 @@ func TestListenerCloseUnaccepted(t *testing.T) {
assert.NoError(t, listener.Close())
}

func TestListenerPacketConn(t *testing.T) {
// Limit runtime in case of deadlocks
lim := test.TimeOut(time.Second * 20)
defer lim.Stop()

// Check for leaking routines
report := test.CheckRoutines(t)
defer report()

network, addr := getConfig()
pConn, err := net.ListenUDP(network, addr)
assert.NoError(t, err)

listener := (&ListenConfig{}).ListenPacketConn(pConn)
assert.Equal(t, pConn.LocalAddr(), listener.Addr())

raddr, ok := listener.Addr().(*net.UDPAddr)
assert.True(t, ok)
client, err := net.DialUDP(network, nil, raddr)
assert.NoError(t, err)
defer func() {
assert.NoError(t, client.Close())
}()

_, err = client.Write([]byte{0xAA})
assert.NoError(t, err)

conn, _, err := listener.Accept()
assert.NoError(t, err)

buf := make([]byte, 16)
n, _, err := conn.ReadFrom(buf)
assert.NoError(t, err)
assert.Equal(t, []byte{0xAA}, buf[:n])
assert.NoError(t, conn.Close())

// Closing the listener must close the supplied connection.
assert.NoError(t, listener.Close())
_, _, err = pConn.ReadFromUDP(buf)
assert.ErrorIs(t, err, net.ErrClosed)
}

func TestListenerAcceptFilter(t *testing.T) {
// Limit runtime in case of deadlocks
lim := test.TimeOut(time.Second * 20)
Expand Down
73 changes: 54 additions & 19 deletions listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,59 @@ func Listen(network string, laddr *net.UDPAddr, config *Config) (net.Listener, e
return nil, err
}

lc := udp.ListenConfig{
parent, err := udpListenConfig(config).Listen(network, laddr)
if err != nil {
return nil, err
}

return &listener{
config: config,
parent: parent,
}, nil
}

// ListenWithOptions creates a DTLS listener.
func ListenWithOptions(network string, laddr *net.UDPAddr, opts ...ServerOption) (net.Listener, error) {
config, err := buildServerConfig(opts...)
if err != nil {
return nil, err
}

return Listen(network, laddr, config)
}

// ListenPacketConn creates a DTLS listener that accepts connections on the
// supplied packet connection, instead of opening a socket. This allows the
// caller to create and configure the socket itself, e.g. to set socket
// options that have to be applied before bind, or to use a socket received
// from another process. The listener takes ownership of the connection and
// closes it on Close.
func ListenPacketConn(pconn net.PacketConn, config *Config) (net.Listener, error) {
if err := validateConfig(config); err != nil {
return nil, err
}

return &listener{
config: config,
parent: udpListenConfig(config).ListenPacketConn(pconn),
}, nil
}

// ListenPacketConnWithOptions creates a DTLS listener that accepts
// connections on the supplied packet connection.
func ListenPacketConnWithOptions(pconn net.PacketConn, opts ...ServerOption) (net.Listener, error) {
config, err := buildServerConfig(opts...)
if err != nil {
return nil, err
}

return ListenPacketConn(pconn, config)
}

// udpListenConfig returns the UDP listen config used to accept connections
// for the given DTLS config.
func udpListenConfig(config *Config) *udp.ListenConfig {
lc := &udp.ListenConfig{
AcceptFilter: func(packet []byte) bool {
pkts, err := recordlayer.UnpackDatagram(packet)
if err != nil || len(pkts) < 1 {
Expand All @@ -41,25 +93,8 @@ func Listen(network string, laddr *net.UDPAddr, config *Config) (net.Listener, e
lc.DatagramRouter = cidDatagramRouter(len(config.ConnectionIDGenerator()))
lc.ConnectionIdentifier = cidConnIdentifier()
}
parent, err := lc.Listen(network, laddr)
if err != nil {
return nil, err
}

return &listener{
config: config,
parent: parent,
}, nil
}

// ListenWithOptions creates a DTLS listener.
func ListenWithOptions(network string, laddr *net.UDPAddr, opts ...ServerOption) (net.Listener, error) {
config, err := buildServerConfig(opts...)
if err != nil {
return nil, err
}

return Listen(network, laddr, config)
return lc
}

// NewListener creates a DTLS listener which accepts connections from an inner Listener.
Expand Down
80 changes: 80 additions & 0 deletions listener_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

//go:build !js

package dtls

import (
"net"
"testing"
"time"

"github.com/pion/dtls/v3/pkg/crypto/selfsign"
"github.com/pion/transport/v4/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestListenerPacketConn(t *testing.T) {
// Limit runtime in case of deadlocks
lim := test.TimeOut(time.Second * 20)
defer lim.Stop()

// Check for leaking routines
report := test.CheckRoutines(t)
defer report()

cert, err := selfsign.GenerateSelfSigned()
require.NoError(t, err)

pConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
require.NoError(t, err)

listener, err := ListenPacketConnWithOptions(pConn, WithCertificates(cert))
require.NoError(t, err)
assert.Equal(t, pConn.LocalAddr(), listener.Addr())

serverDone := make(chan error, 1)
go func() {
conn, aErr := listener.Accept()
if aErr != nil {
serverDone <- aErr

return
}
buf := make([]byte, 16)
n, rErr := conn.Read(buf)
if rErr != nil {
serverDone <- rErr

return
}
if _, wErr := conn.Write(buf[:n]); wErr != nil {
serverDone <- wErr

return
}
serverDone <- conn.Close()
}()

raddr, ok := listener.Addr().(*net.UDPAddr)
require.True(t, ok)
client, err := DialWithOptions("udp", raddr, WithCertificates(cert), WithInsecureSkipVerify(true))
require.NoError(t, err)

_, err = client.Write([]byte("hello"))
require.NoError(t, err)
buf := make([]byte, 16)
n, err := client.Read(buf)
require.NoError(t, err)
assert.Equal(t, "hello", string(buf[:n]))

assert.NoError(t, client.Close())
assert.NoError(t, <-serverDone)

// Closing the listener must close the supplied connection.
assert.NoError(t, listener.Close())
_, _, err = pConn.ReadFromUDP(buf)
assert.ErrorIs(t, err, net.ErrClosed)
}
Loading