* * *

Author Topic: Synapse bidirectional UDP  (Read 1719 times)

rvk

  • Hero Member
  • *****
  • Posts: 2666
Re: Synapse bidirectional UDP
« Reply #15 on: August 14, 2017, 10:25:45 pm »
And now GetLocalSinPort returns 0 as well, so I don't know which port is used.
I'm not sure why you would need the local port. You could listen on a different uniform port.

But the reason GetLocalSinPort is 0 is because with binding to 0.0.0.0, you bind to all ip-addresses the computer has. Normally this is localhost (127.0.0.1) AND your local ip address on your LAN (which might be more if you have multiple network cards and/or WiFi). So two or more IP-addresses. But each IP-address can have a different local-port so GetLocalSinPort isn't really useful because it can be multiple ports.

If you really want GetLocalSinPort to have a valid number you need to bind to your local LAN ip-address (so not 0.0.0.0 and not 127.0.0.1 but 192.168.1.something).

Remy Lebeau

  • Sr. Member
  • ****
  • Posts: 322
    • Lebeau Software
Re: Synapse bidirectional UDP
« Reply #16 on: August 15, 2017, 12:01:25 am »
But the reason GetLocalSinPort is 0 is because with binding to 0.0.0.0, you bind to all ip-addresses the computer has.

More accurately, to all local IPv4 addresses.

But each IP-address can have a different local-port

Only if you explicitly bind different sockets to each IP address individually.  When binding a single socket, regardless of whether you bind it to "0.0.0.0" (or "::0" in IPv6), or "127.0.0.1", or any other specific IP, all IP addresses bound to the socket will be bound to a single port number that they all share (if multiple IPs are bound).

If you really want GetLocalSinPort to have a valid number you need to bind to your local LAN ip-address (so not 0.0.0.0 and not 127.0.0.1 but 192.168.1.something).

That is not true.  You can bind to "0.0.0.0" or "127.0.0.1" and still get back a valid port number.  For example, when a socket user wants to bind a socket to a random port chosen by the OS, it first binds to port 0 (whether on "0.0.0.0" or a specific IP, it doesn't matter at all) and then queries the socket for the local port that it was actually bound to.  The IP address(es) being bound to does not affect the port number that is being bound to, and vice versa.
« Last Edit: August 15, 2017, 12:05:22 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) open source project - Admin, Developer

rvk

  • Hero Member
  • *****
  • Posts: 2666
Re: Synapse bidirectional UDP
« Reply #17 on: August 15, 2017, 12:24:22 am »
If you really want GetLocalSinPort to have a valid number you need to bind to your local LAN ip-address (so not 0.0.0.0 and not 127.0.0.1 but 192.168.1.something).
That is not true.  You can bind to "0.0.0.0" or "127.0.0.1" and still get back a valid port number.  For example, when a socket user wants to bind a socket to a random port chosen by the OS, it first binds to port 0 (whether on "0.0.0.0" or a specific IP, it doesn't matter at all) and then queries the socket for the local port that it was actually bound to.  The IP address(es) being bound to does not affect the port number that is being bound to, and vice versa.
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? Even after sending an UDP GetLocalSinPort is still 0.

At what point and how can you query what port is locally used for sending UDP when bound to 0.0.0.0?

Remy Lebeau

  • Sr. Member
  • ****
  • Posts: 322
    • Lebeau Software
Re: Synapse bidirectional UDP
« Reply #18 on: August 15, 2017, 02:27:19 am »
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:

Code: [Select]
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).
« Last Edit: August 15, 2017, 02:39:47 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) open source project - Admin, Developer

rvk

  • Hero Member
  • *****
  • Posts: 2666
Re: Synapse bidirectional UDP
« Reply #19 on: August 15, 2017, 10:46:47 am »
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().

Well, I also had a quick look in the source code and it says this:
Quote
Warning: when you call : Bind('0.0.0.0','0'); then is nothing done! In this case is used implicit system bind instead.}
And if I look in the TBlockSocket.Bind() I see this:
Code: Pascal  [Select]
  1.   if (FSocket <> INVALID_SOCKET)
  2.     or not((FFamily = SF_ANY) and (IP = cAnyHost) and (Port = cAnyPort)) then
And 0.0.0.0 is the same as cAnyHost so Bind() really does NOTHING when called with 0.0.0.0.
I checked this and the code really jumps over this "if" when called with 0.0.0.0.
So Bind(0.0.0.0) does NOTHING.

The Bind() is implicitly done with the first real UDP send. So, the GetSinLocal needs to be called AFTER the first SendString call.

Code: Pascal  [Select]
  1.       MyUDPSocket.SendString('aaaaaaaaaaaa');
  2.       MyUDPSocket.GetSinLocal; // needs to be called manually after the first UDP for Bind(0.0.0.0)
  3.       ShowMessage(MyUDPSocket.GetLocalSinIP + ' ' + IntToStr(MyUDPSocket.GetLocalSinPort)); // works

This worked for me and gave me "0.0.0.0 random local port".
« Last Edit: August 15, 2017, 11:02:09 am by rvk »

Thaddy

  • Hero Member
  • *****
  • Posts: 4419
Re: Synapse bidirectional UDP
« Reply #20 on: August 15, 2017, 11:34:57 am »
Correct.
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

SymbolicFrank

  • Sr. Member
  • ****
  • Posts: 297
Re: Synapse bidirectional UDP
« Reply #21 on: August 15, 2017, 02:54:55 pm »
Thanks!

It turns out that the device always sends to a fixed port number (it's not in the manual), but I have another project in the works where I have to do it as you guys described.

Remy Lebeau

  • Sr. Member
  • ****
  • Posts: 322
    • Lebeau Software
Re: Synapse bidirectional UDP
« Reply #22 on: August 15, 2017, 08:52:03 pm »
Well, I also had a quick look in the source code and it says this:
Quote
Warning: when you call : Bind('0.0.0.0','0'); then is nothing done! In this case is used implicit system bind instead.}
And if I look in the TBlockSocket.Bind() I see this:
Code: Pascal  [Select]
  1.   if (FSocket <> INVALID_SOCKET)
  2.     or not((FFamily = SF_ANY) and (IP = cAnyHost) and (Port = cAnyPort)) then
And 0.0.0.0 is the same as cAnyHost so Bind() really does NOTHING when called with 0.0.0.0.
I checked this and the code really jumps over this "if" when called with 0.0.0.0.
So Bind(0.0.0.0) does NOTHING.

When binding to '0.0.0.0:0', that 'if' statement evaluates to false only if FSocket has not been allocated yet and FFamily is SF_ANY.  Calling TBlockSocket.CreateSocket() explicitly will allocate the socket, but only if FFamily is not SF_ANY:

Code: [Select]
procedure TBlockSocket.CreateSocket;
var
  sin: TVarSin;
begin
  //dummy for SF_Any Family mode
  ResetLastError;
  if (FFamily <> SF_Any) and (FSocket = INVALID_SOCKET) then // <-- here
  begin
    ...
  end;
end;

Unfortunately, TBlockSocket.Family is SF_ANY by default:

Code: [Select]
constructor TBlockSocket.Create;
begin
  CreateAlternate('');
end;

constructor TBlockSocket.CreateAlternate(Stub: string);
{$IFNDEF ONCEWINSOCK}
var
  e: ESynapseError;
{$ENDIF}
begin
  inherited Create;
  ...
  FFamily := SF_Any;
  FFamilySave := SF_Any;
  ...
end;

So, setting TBlockSocket.Family to SF_IP4 explicitly (since the code is trying to bind to an IPv4 address) should allow binding to '0'0.0.0:0' and not skip it:

Code: [Select]
procedure TSixTLNode.Execute;
var
  msg: TMessage;
begin
  MyUDPSocket := TUDPBlockSocket.Create;
  try
    MyUDPSocket.Family := SF_IP4; // <-- add this!

    MyUDPSocket.CreateSocket;
    MyUDPSocket.Bind('0.0.0.0', '0');

    if MyUDPSocket.LastError <> 0 then Exit;

    MyUDPPort := MyUDPSocket.GetLocalSinPort; // <-- should work now!

    ...

  finally
    MyUDPSocket.Free;
  end;
end;

The Bind() is implicitly done with the first real UDP send.

But the OP's code is *explicitly* calling Bind(), which disables any implicit binding.

So, the GetSinLocal needs to be called AFTER the first SendString call.

Or, after an explicit call to TBlockSocket.Bind() that actually calls the underlying socket API bind() instead of skip it.
« Last Edit: August 15, 2017, 09:00:44 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) open source project - Admin, Developer

rvk

  • Hero Member
  • *****
  • Posts: 2666
Re: Synapse bidirectional UDP
« Reply #23 on: August 16, 2017, 10:12:06 am »
So, setting TBlockSocket.Family to SF_IP4 explicitly (since the code is trying to bind to an IPv4 address) should allow binding to '0'0.0.0:0' and not skip it:
Ah, Ok, your example is very clear.

So, calling CreateSocket and Bind(0.0.0.0) with family set to SF_IP4 will actually do a real Bind and local-port is known.
If family is set to SF_ANY (default), CreateSocket and Bind(0,0.0.0) will have no effect in synapse and an implicit Bind will be done with the first UDP and in that case local-port is only known after that.

Setting family to SF_IP4 is a good idea anyway, so you know what you're dealing with.

Thnx for the explanation.

Thaddy

  • Hero Member
  • *****
  • Posts: 4419
Re: Synapse bidirectional UDP
« Reply #24 on: August 16, 2017, 10:14:30 am »
Tnx Remy. I completely missed that too.
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

SymbolicFrank

  • Sr. Member
  • ****
  • Posts: 297
Re: Synapse bidirectional UDP
« Reply #25 on: August 16, 2017, 11:51:35 am »
Thanks Remy, that was very clear.

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus