For UDP transports, createClient passes tran.BindPort into sipgo.WithClientConnectionAddr whenever it has a resolved hostname, without checking whether anything is listening on that port.
Sourcing from a listened port reuses that connection. Sourcing from any other port is a fresh bind, which the OS refuses as soon as another process owns the port.
That distinction matters because a UA may register before it serves. At that moment nothing holds the configured port, so pinning it there turns an unrelated process on the same port into "bind: address already in use" on the REGISTER, rather than the intended connection reuse.
Fix: return the requested port only when the transport layer already lists it in ListenPorts("udp"), and 0 otherwise, which asks for an ephemeral port and always binds. The check is for that specific port rather than for any listener at all — serving 5060 says nothing about whether 25000 is bindable.
For UDP transports, createClient passes tran.BindPort into sipgo.WithClientConnectionAddr whenever it has a resolved hostname, without checking whether anything is listening on that port.
Sourcing from a listened port reuses that connection. Sourcing from any other port is a fresh bind, which the OS refuses as soon as another process owns the port.
That distinction matters because a UA may register before it serves. At that moment nothing holds the configured port, so pinning it there turns an unrelated process on the same port into "bind: address already in use" on the REGISTER, rather than the intended connection reuse.
Fix: return the requested port only when the transport layer already lists it in ListenPorts("udp"), and 0 otherwise, which asks for an ephemeral port and always binds. The check is for that specific port rather than for any listener at all — serving 5060 says nothing about whether 25000 is bindable.