You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@cacggghp oh, it makes sense, NewSTUNConn expects a stream transport (TCP/TLS/DTLS). I patched the example a bit to show how you can use it with custom dial (DialUDP because we need a packetConn).
@JoTurk I just have a flag to choose between tcp and udp, and I thought net.Dial would work for both. You probably should write in description of NewSTUNConn that it expects a stream. By the way, STUN is a datagram protocol, why would the parsing function break on udp? Aren't STUN messages independent of each other?
NewSTUNConn takes a net.Conn and re-expose it as a PakcetConn, by framing STUN messages with a length header, this is used for transports with no datagram boundaries. it's unnecessary for UDP and it will cause many problems with handling of address-ports.
And it's described here https://pkg.go.dev/github.com/pion/turn/v4#STUNConn
We're doing v5 soon. Maybe we should make this more explicit, clear?
Agreed — this belongs in the v5 API rather than as another clarification on the v4 surface.
The trap is that net.Dial("udp", …) satisfies net.Conn, even though the transport still preserves datagram boundaries. That makes it easy to pass UDP into the stream framer, where length-based reassembly is both unnecessary and wrong for address/port handling.
For v5, an API boundary that makes the stream-only case explicit (for example a stream-named constructor), with UDP callers using net.PacketConn directly, would make the valid path much harder to misuse. A small regression pair could then cover: a fragmented stream STUN message is reassembled; a UDP datagram is left untouched.
If the v5 naming/direction is decided, I would be happy to prepare that focused implementation and the characterization tests.
On a semi-related note: we're working on a refactor to translate pion/turn fully to net.Conns. Once we're ready, there'd be no net.PacketConns at all in the server. TURN is a connection-oriented protocol after all (really!), so it makes little sense to run it over a net.PacketConn. Plus, the server runs a separate readloop per net.Conn (so one goroutine per TCP client) but only a single readloop per net.PacketConn (shared between all clients), so currently we're multi-threading much more efficiently for TCP and TLS than for UDP.
The idea is to expect the user to bootstrap TURN/UDP servers with a net.Listener that implements a "UDP server" analogous to a TCP server: every UDP packet from a new source IP:port spawns a new connected net.Conn (pion/transport/udp offers a candidate implementation, and here is a multi-threaded alternative). Since all the other transports are already net.Listeners (even DTLS), we can drop the ServerConfig.PacketConnConfigs and we get UDP multi-threading for free. On the relay address generator side we would implement UDP relays over the same API as TCP: AllocateListener creates a net.ListenPacket with reuse-port as the shared relay address and each new peer relay connection gets a separate net.Conn with AllocateConn. Then we drop AllocatePacketConn.
We're halfway there: server config API ported but internally the server still uses net.PacketConns and the relay generator has not been rewritten yet. This already bore some fruit: net.Conns can be individually closed when the surrounding allocation is closed (which is not the case with the shared net.PacketConn), plus we get proper protocol tags inside the server (last time I checked we defaulted everything to UDP).
I will post a PR when we're finished. Apart from the obvious gains the API may make very very little sense to most users (people default UDP to PacketConns), so I would not fight for it being accepted, but it's at least an interesting experiment.
Activity
JoTurk commentedon Jan 10, 2026
@cacggghp hello 👋 can you share more details? test environment? how did you test it?
cacggghp commentedon Jan 10, 2026
Like here: https://github.com/pion/turn/blob/master/examples/turn-client/udp/main.go but using net.Dial and turn.NewSTUNConn instead of net.ListenPacket
JoTurk commentedon Jan 10, 2026
@cacggghp oh, it makes sense, NewSTUNConn expects a stream transport (TCP/TLS/DTLS). I patched the example a bit to show how you can use it with custom dial (DialUDP because we need a packetConn).
What is your use case anyway?
udp-client-dial.patch
cacggghp commentedon Jan 10, 2026
@JoTurk I just have a flag to choose between tcp and udp, and I thought net.Dial would work for both. You probably should write in description of NewSTUNConn that it expects a stream. By the way, STUN is a datagram protocol, why would the parsing function break on udp? Aren't STUN messages independent of each other?
I'm making a dtls encrypted TURN vpn.
JoTurk commentedon Jan 10, 2026
NewSTUNConn takes a net.Conn and re-expose it as a PakcetConn, by framing STUN messages with a length header, this is used for transports with no datagram boundaries. it's unnecessary for UDP and it will cause many problems with handling of address-ports.
And it's described here https://pkg.go.dev/github.com/pion/turn/v4#STUNConn
We're doing v5 soon. Maybe we should make this more explicit, clear?
CC: @Sean-Der @rg0now
iibaranov-IG commentedon Oct 12, 2026
Agreed — this belongs in the v5 API rather than as another clarification on the v4 surface.
The trap is that
net.Dial("udp", …)satisfiesnet.Conn, even though the transport still preserves datagram boundaries. That makes it easy to pass UDP into the stream framer, where length-based reassembly is both unnecessary and wrong for address/port handling.For v5, an API boundary that makes the stream-only case explicit (for example a stream-named constructor), with UDP callers using
net.PacketConndirectly, would make the valid path much harder to misuse. A small regression pair could then cover: a fragmented stream STUN message is reassembled; a UDP datagram is left untouched.If the v5 naming/direction is decided, I would be happy to prepare that focused implementation and the characterization tests.
rg0now commentedon Oct 12, 2026
On a semi-related note: we're working on a refactor to translate pion/turn fully to net.Conns. Once we're ready, there'd be no
net.PacketConnsat all in the server. TURN is a connection-oriented protocol after all (really!), so it makes little sense to run it over anet.PacketConn. Plus, the server runs a separate readloop pernet.Conn(so one goroutine per TCP client) but only a single readloop pernet.PacketConn(shared between all clients), so currently we're multi-threading much more efficiently for TCP and TLS than for UDP.The idea is to expect the user to bootstrap TURN/UDP servers with a
net.Listenerthat implements a "UDP server" analogous to a TCP server: every UDP packet from a new source IP:port spawns a new connectednet.Conn(pion/transport/udp offers a candidate implementation, and here is a multi-threaded alternative). Since all the other transports are alreadynet.Listeners(even DTLS), we can drop theServerConfig.PacketConnConfigsand we get UDP multi-threading for free. On the relay address generator side we would implement UDP relays over the same API as TCP:AllocateListenercreates anet.ListenPacketwith reuse-port as the shared relay address and each new peer relay connection gets a separatenet.ConnwithAllocateConn. Then we dropAllocatePacketConn.We're halfway there: server config API ported but internally the server still uses net.PacketConns and the relay generator has not been rewritten yet. This already bore some fruit: net.Conns can be individually closed when the surrounding allocation is closed (which is not the case with the shared
net.PacketConn), plus we get proper protocol tags inside the server (last time I checked we defaulted everything to UDP).I will post a PR when we're finished. Apart from the obvious gains the API may make very very little sense to most users (people default UDP to PacketConns), so I would not fight for it being accepted, but it's at least an interesting experiment.