Then why does GetLocalSinPort return 0 if bound to 0.0.0.0 and a valid local-port number if bound to a local IP-address?
I don't know. Maybe a bug in Synapse? The underlying BSD socket API should behave the way I described. Try it for yourself. Call the API socket(), bind(), and getsockname() functions directly, and it should return a valid port number. It does for me when I try it. Something like this:
var
s: TSocket;
addr, bound: sockaddr_in;
len: integer;
port: Integer;
s := socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if s = -1 then ...
FillByte(addr, sizeof(addr), 0);
addr.sin_family := AF_INET;
addr.sin_addr.s_addr := INADDR_ANY; // "0.0.0.0"
addr.sin_port := 0;
if bind(s, addr, sizeof(addr)) <> 0 then ...
FillByte(bound, sizeof(bound), 0);
len := sizeof(bound);
if getsockname(s, bound, len) <> 0 then ...
port := ntohs(bound.sin_port);
I checked the latest Synapse source code. It is basically doing the same as above. TBlockSocket.Bind() calls synsock.Bind() (which calls the socket API bind()) followed by synsock.GetSockName() (which calls the socket API getsockname()). The result is stored in the TBlockSocket.FLocalSin data member (which is a TVarSin, Synapse's equivalent of the standard SOCKADDR_STORAGE record to wrap the sockaddr_in and sockaddr_in6 records). TBlockSocket.GetLocalSinPort() calls synsock.GetSinPort(), passing it FLocalSin as input. GetSinPort() extracts the TVarSin's port number from the appropriate field (sockaddr_in.sin_port for IPv4, sockaddr_in6.sin6_port for IPv6) and converts the value from network byte order to host byte order with ntohs().
At what point ... can you query what port is locally used for sending UDP when bound to 0.0.0.0?
For UDP, immediately after it is successfully bound with bind().
For TCP, immediately after it starts listening (via listen()) or is connected to a peer (via connect() or accept()).
and how
Via getsockname() (or higher wrapper).