ARMmbed / mbed-os

Arm Mbed OS is a platform operating system designed for the internet of things
https://mbed.com
Other
4.67k stars 2.98k forks source link

Ethernet Multicast Problems #2766

Closed sstaub closed 7 years ago

sstaub commented 8 years ago

Target: LPC1768 mbed2: build 124 Priority: Blocker Tools: PlatformIO

I need to receive two multicast streams from one port but different address (sACN E1.31) The binding of the first socket (A) is ok, but the binding of the second socket (B) fails, so I receive only the data from socket A. Maybe the problem is that is not possible to bind two sockets on the same port? I find no function for binding to an ip-address and port, only for binding to a port. Any solution? I posted the problem in the forum, but i got no answer.

Here the code:

/* Multicast Test
** (c)2016 stefan Staub
**
*/

// include mbed libraries
#include "mbed.h"
#include `"EthernetInterface.h"`

// ethernet variables
const int PORT = 5568;

char IP_ADDRESS[] = "10.101.1.200";
char IP_ADDRESS_MASK[] = "255.255.0.0";
char IP_ADDRESS_GATEWAY[] = "0.0.0.0";
char MCAST_GRP_A[] = "239.255.0.1";
char MCAST_GRP_B[] = "239.255.0.2";

int lengthA;
int lengthB;

char bufferA[638];
char bufferB[638];

// main
int main() {

  // ethernet
  EthernetInterface eth;
  UDPSocket socketA;
  UDPSocket socketB;
  Endpoint multiA;
  Endpoint multiB;

  // ethernet
  eth.init(IP_ADDRESS, IP_ADDRESS_MASK, IP_ADDRESS_GATEWAY);
  eth.connect();

  // debug
  printf("IP Address %s\n", eth.getIPAddress());
  printf("Subnet %s\n", eth.getNetworkMask());
  printf("MAC Address %s\n", eth.getMACAddress());

  // udp non blocking
  socketA.set_blocking (false);
  socketB.set_blocking (false);

  // Multicast on Socket A -> OK
  if (socketA.bind(PORT) == -1) {
    printf("socket A bind failed \n");
    }
  else {
    printf("socket A bind success \n");
    }

  if (socketA.join_multicast_group(MCAST_GRP_A) == -1) {
    printf("multicast A failed \n");
    }
  else {
    printf("multicast A success \n");
    }
  // Endpoint A
  if (multiA.set_address(MCAST_GRP_A, PORT) == -1){
    printf("endpoint A failed \n");
    }
  else {
    printf("endpoint A success \n");
    }

  // Multicast on Socket B -> Fails when binding
  if (socketB.bind(PORT) == -1) {
    printf("socket B bind failed \n");
    }
  else {
    printf("socket B bind success \n");
    }
  if (socketB.join_multicast_group(MCAST_GRP_B) == -1) {
    printf("multicast B failed \n");
    }
  else {
    printf("multicast B success \n");
    }
  // Endpoint B
  if (multiB.set_address(MCAST_GRP_B, PORT) == -1){
    printf("endpoint B failed \n");
    }
  else {
    printf("endpoint B success \n");
    }

  // main loop
  while (true) {
    lengthA = socketA.receiveFrom(multiA, bufferA, sizeof(bufferA));
    printf("Packet A from %s\": %d\n", multiA.get_address(), lengthA);

    lengthB = socketB.receiveFrom(multiB, bufferB, sizeof(bufferB));
    printf("Packet B from %s\": %d\n", multiB.get_address(), lengthB);
    }
  }

sstaub

sg- commented 8 years ago

@geky any thoughts on this?

kjbracey commented 8 years ago

I don't believe lwIP supports this - this would be "SO_REUSEPORT", but it's not implemented.

Could you reconsider the protocol? Different streams are conventionally distinguished by different ports, not by destination/group address, so using different ports would be the more traditional behaviour.

Binding to a multicast address also isn't ideal, as it prevents you receiving unicast packets.

So, identify the traffic by a unique port, then add the also-unique IP group membership. Don't try to bypass traditional port=socket semantics just because multicast gives you the theoretical ability to distinguish by destination group.

If you absolutely require multiple sockets on the same port, as I think you might for some multicast RTP cases, you would need to submit a feature request to lwIP, I think.

Another possible approach would be to have a single socket, turn on LWIP_NETBUF_RECVINFO, and extend the NSAPI receive API to allow you to see the destination address, and dispatch based on that. Or indeed, just include some sort of stream identifier in the payload.

geky commented 8 years ago

Binding multiple sockets to a single port is a bit of unusual behaviour. Since it's not supported in lwip I believe it's safe to say it's outside of the scope of the network-socket API.

sstaub commented 8 years ago

What to do? I'm not a professional, so I need practical examples for a solution, if it is possible. I can run a modified version on an Arduino with an Ethernetshield (with a Wiznet W5500 chip) without any problem?! Sorry I'm not a specialist for lwip and ethernet-socket, I do programming more as a hobby. For your info, the sACN protocol is a standard in professional lighting industry (my main profession) and it is used for transporting DMX-Data for controlling dimmers, moving lights .., more info at http://tsp.esta.org/tsp/documents/published_docs.php I hope that I doesn't need to go back to Arduino, I hate it, really.

Thanks for your efforts

geky commented 8 years ago

I'm unfamiliar with the sACN protocol, is it possible to use only one UDPSocket bound to the port?

Actually, looking at your example, it appears you are using the older socket API. Are you using mbed OS 5?

With the network-socket API in mbed OS 5, the UDPSocket has a recvfrom method that allows you to determine which address the inbound packet came from:

UDPSocket socket(&eth);

int err = socket.bind(PORT);
if (err) {
    printf("socket A bind failed %d\n", err);
} else {
    printf("socket A bind success \n");
}

while (true) {
    SocketAddress address;
    length = socket.recvfrom(&address, buffer, sizeof(buffer));

    if (address == SocketAddress(CAST_GRP_A)) {
        printf("Packet A from %s\": %d\n", address.get_ip_address(), length);
    } else  (address == SocketAddress(CAST_GRP_B)) {
        printf("Packet B from %s\": %d\n", address.get_ip_address(), length);
    }
}

Would this work as an alternative solution?

sstaub commented 8 years ago

I have tried to use only one socket longer time ago, but it doesn't work. I have to bind a socket for each multicast ip. Your example doesn't run on my system, i use v124

sg- commented 8 years ago

Your example doesn't run on my system, i use v124

To use this example you need to use mbed-os 5.1.x to put this in your program, remove any networking, rtos and mbed library and import from url using https://github.com/armmbed/mbed-os

kjbracey commented 8 years ago

Looking more carefully at lwIP, I think it does actually support the type of bind you want - binding two sockets to 239.255.0.1:5568 and 239.255.0.2:5568 appears to be permitted, because the addresses differ.

So with mbed OS 5.1, you should be good to go.

sstaub commented 8 years ago

Online Compiler with mbed-os When I try to build code with ethernet functionality i get generally following errors: error_ethernetinterface So I can't test your examples. ???

geky commented 8 years ago

Hi @sstaub. You will need to remove the EthernetInterface library since it is now included in mbed-os.

sstaub commented 8 years ago

Thank you, the documentation of mbed-os is misleading. But how to initialize the EthernetInterface (with ip, subnet...)

EthernetInterface eh 
eth.init(IP_ADDRESS, IP_ADDRESS_MASK, IP_ADDRESS_GATEWAY);
eth.connect();
```does not work
geky commented 8 years ago

Unfortunately I don't have an answer for you there, static IP support was temporarily removed as a part of an effort to refactor the EthernetInterface and add support for more devices.

However, we do have static IP support on the current development branch of mbed-os, and static IP support should be available in the next release for the online compiler.

sstaub commented 8 years ago

Thank you, i will wait for the next release and have then a look to the new network library. The most thing I'm missing in the documentation are short and simple examples, the documentation for the classic mbed 2 was very good, hope the the docs for mbed-os5 will get the same quality. Maybe you can give me an example for starting with dchp.

sg- commented 7 years ago

@sstaub Can you provide an update if the problem persists with mbed OS 5.3.3

sstaub commented 7 years ago

See issue #3512, in mbed-os the method join_multicast_group is missing, it is needed to receive multicast streams from the network switch. If this will work i can test it.