SCTP over UDP

When I create a plain transport (for SCTP over UDP) on the server, is a UDP socket on port 5000 bound and listening for SCTP connection? Or do I need to create a UDP socket explicitly? If so, how do I connect the UDP socket to the transport/producer?

On the client side, I am using usrsctp to send SCTP packets to router

No. When in a PlainTransport, mediasoup implements SCTP directly over UDP. The port 5000 is the SCTP port, not the UDP port. And SCTP exists because SCTP is primary a layer 4 protocol (same as TCP and UDP). However, in mediasoup we implement SCTP over UDP.

You must send SCTP to plainTransport.tuple.port.

When I create a PlainTransport, a UDP socket is bound to ‘tuple.port’ and is listening for connections? Or do I need to create my own UDP socket on that port?

When I try to connect to the ‘tuple.port’ returned from the PlainTransport using usrsctp, the connection never completes.

That’s explained in the PlainTransport API documentation.

In addition: mediasoup :: Communication Between Client and Server

No idea if you are enabling proper debug logs in mediasoup, no idea if you are properly using SCTP port src 5000 and dst 5000, and no idea if you are really sending SCTP over UDP or not to mediasoup. I cannot help much.

Ok, thanks for the help. I’ve been able to connect and send SCTP to the server, but when then writing it to the sctp stream associated with the data producer, the data producer stats don’t show any messages/bytes. I believe it is setup correctly according to the Bot demo code, the debug output is showing completed writes to the same streamId as the data producer, just the stats aren’t indicating that the producer is actually seeing the messages. The plain transport is connected to the local UDP socket that is used for the SCTP socket, and the data producer is created from this transport. Anything else I need to check?

Is it correct that the plain transport should ‘connect’ to the localhost and port of the local UDP socket on the server?

Check the mediasoup Debugging section and enable SCTP logs. They will show everything. Note that I already suggested this in my previous comment.

You send SCTP messages over UDP from the local UDP socket to the mediasoup PLainTransport, so yes, you need to tell the mediasoup transport where the endpoint is.

I’ve enabled logs for everything, with no useful info. Here’s my path in case you spot something wrong:

Server:

  • Created local UDP socket on port 44444
  • Successful creation of plain transport, connected to localhost, port 44444
  • Successful creation of SCTP socket, using local UDP socket, and udpPeer set to my remote “client” IP and port 44444. SCTP ports both set to 5000.
  • Successful creation of stream on SCTP socket, with stream id 1.
  • Successful creation of data producer, with stream id 1.
  • SCTP connection with remote client succeeds, and receives the message within the SCTP socket’s ‘stream’ and ‘data’ event.
  • Message is then written (and completes according to SCTP debug) to stream id 1, with the correct PPID.

It’s at this point that I don’t receive any acknowledgement from Mediasoup that it sees the message sent into the stream and to the producer. I have logs set to Debug level, and have enabled the ‘sctp’ logTag, as well as logs for SCTP module.

This SCTP message is sent every second, and I don’t receive any logs from mediasoup after the writes to the stream associated with the data producer. And the producer stats don’t show any data touching the producer. Does this mean one of the endpoints is incorrect, since the producer isn’t picking up on any writes to the stream?

In addition, the transport’s “sctpconnectionstate” never changes from connecting. Under what circumstances would this change to connected?

Is the remote client your usrsctp based client? Just to be sure. If so, are those ‘stream’ and ‘data’ usrsctp events in your client side?

This means that no SCTP message is being received by the mediasoup plain transport. Otherwise it would be logged.

Well, here the problem. It will change to connected once the SCTP association handshake is completed. It is not, but I don’t know why.

You may wish to use Wireshark to verify which exact SCTP message exchange happens on the UDP tuple. Obviously something is not good.

I’ll try posting some code that may point out an error I’m making:

Remote client C++ usrsctp:

   // Local UDP port 44444
  usrsctp_init(44444, NULL, NULL);
  // Create SCTP socket
  struct socket* sctp_socket = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, NULL, NULL, 0, NULL);
  // Bind local
  local.sin_family = AF_INET;
  local.sin_addr.s_addr = htonl(INADDR_ANY);
  local.sin_port = htons(44444);
  usrsctp_bind(sctp_socket, (struct sockaddr*)&local, sizeof(struct sockaddr_in)));
  // Connect to Mediasoup transport endpoint
  dest.sin_family = AF_INET;
  dest.sin_addr.s_addr = inet_addr(MEDIASOUP_TRANSPORT_TUPLE_LOCALIP);
  dest.sin_port = htons(MEDIASOUP_TRANSPORT_TUPLE_LOCALPORT);
  usrsctp_connect(sctp_socket, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)));

Mediasoup server JS:

const dataTransport = await defaultRoom.createPlainTransport({
    listenIp: { ip: "127.0.0.1" },
    enableSctp: true,
    numSctpStreams:
    {
      OS: 256,
      MIS: 256
    },
    maxSctpMessageSize: 262144,
});
dataTransport.on("sctpstatechange", (sctpState) => {
  console.log("*** SCTP STATE: %s", sctpState);
});
let dataSocket = dgram.createSocket({ type: "udp4" });
await dataSocket.bind(44444);
await dataTransport.connect({ ip: REMOTE_CLIENT_IP, port: 44444 });

The handshake does not appear to be occurring with this code, as SCTP state never changes out of connecting

So you have ignored everything I said about using Wireshark to get traces and also everything about SCTP over UDP that I explained above. I cannot help this way, sorry.

BTW check your local code and ask yourself whether you are really creating a UDP socket or not. I also explained that SCTP is, in its origin, a layer 4 protocol (such as UDP and TCP), but I also explained that in mediasoup Plain Transport we use SCTP over UDP.

Before I can interpret Wireshark output, I need to understand the fundamentals of the handshake in Mediasoup, which I can’t figure out from the docs or sample code (bot). I’ll try to break this into smaller questions, which should answer some things I am unclear on before I can even look at Wireshark properly.

1.) Calling dataTransport.connect(…) should use the IP and UDP port of the remote client correct? In the bot example, it’s using a local IP and local UDP port, which is confusing. Is this just because the demo runs entirely on localhost?

2.) What is port 5000 used for when using SCTP over UDP? I thought everything would go over the port of the UDP socket. Should I be connecting the remote client to port 5000? Or the plain transport tuple.localPort?

I have it working now. Needed to use two SCTP sockets on the server, one for receiving messages from remote client, and one for transporting into the router. My confusion was I thought I could use one SCTP connection for both.

1.) Create SCTP socket between remote client and server for receiving messages (“client SCTP socket”)
2.) Create SCTP socket for plain transport (“transport SCTP socket”) that connects local endpoints (local UDP socket to transport tuple locals), this is what completes SCTP state for the transport.
2.) When messages are received over “client SCTP socket”, write to stream created with “transport SCTP socket”.

yes

Already explained this above.

Well, you don’t need that much. You can make usrsctp directly use SCTP over UDP over IP as transport (same as mediasoup PlainTransport) instead of SCTP over IP. But I feel like you are ignoring this every time I say it, so cannot help more.

I’ll work on figuring that out. Thanks for your patience.

One last question, in the bot.js example you call:

sctp.connect({
  localPort    : 5000, // Required for SCTP over UDP in mediasoup.
  port         : 5000,
  ...
});

Is this connection created just once, and all peers use a single transport (with different stream ids)? This seems contrary to the model that I was expecting from the diagram, of a single transport per connected user.

This is confusing because sctp.connect must have a remote endpoint of each peer, and when sctp.connect is called multiple times with 5000/5000 ports, we get EADDRINUSE. How do we create an SCTP connection like this to each user?

Because you are not using SCTP over UDP (in which ports 5000 mean nothing, it’s just cosmetic and required by mediasoup PlainTransport to properly demux SCTP packets received over UDP). You are using SCTP protocol directly in your usrsctp client side instead, but this is what I’m saying again and again.

Tip: https://github.com/versatica/mediasoup/blob/v3/worker/src/RTC/SctpAssociation.cpp#L101

Tip 2 (do read the UDP stuff here): https://mediasoup.org/documentation/v3/communication-between-client-and-server/#guidelines-for-node-sctp

But really, it’s impossible to help you if you insist on ignoring the SCTP over UDP stuff.

I’m definitely using SCTP over UDP, have been the whole time. I can connect to a node-sctp socket with udp transport from usrsctp, and send messages back and forth fine, I just can’t connect to mediasoup’s transport. Here’s a gist of what’s not working, perhaps I’m missing a socket option or a parameter is wrong (AF_INET vs AF_CONN??)

Mediasoup server:

await dataTransport.connect({ ip: REMOTE_IP, port: 44443 });

usrsctp client:

usrsctp_init(44443, NULL, NULL); // Use UDP port 44443 for SCTP
struct socket* sctp_socket = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, NULL, NULL, 0, NULL);
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(5000);
dest.sin_addr.s_addr = inet_addr(MEDIASOUP_SERVER_IP);
dest.sin_port = htons(5000);
// Use the Mediasoup transport endpoint localPort as the remote UDP port for SCTP over UDP.
encaps.sue_port = htons(MEDIASOUP_TRANPORT_PORT);
usrsctp_setsockopt(sctp_socket, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT,
                   (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps);
usrsctp_bind(sctp_socket, (struct sockaddr*)&local, sizeof(struct sockaddr_in));
usrsctp_connect(sctp_socket, (struct sockaddr*)&dest, sizeof(struct sockaddr_in));

I don’t see that in your code. Perhaps you miss the point that node-sctp does also implement pure SCTP layer 4 protocol, which is ok to directly talk to usrsctp in your usage mode (this is: pure SCTP layer 4 protocol). But here you want to talk from usrsctp to a mediasoupo PlainTransport, so you need SCTP over UDP.

Exactly. You must to use AF_CONN so usrsctp will NOT directly use any SCTP socket but instead you have to get sending data via events and viceversa through a UDP socket that you must create by yourself. Check the SctpAssociation.cpp and DepUsrSCTP.cpp files in mediasoup as guidance since the usrsctp docs are not complete about this.

I see, you’re right that the usrsctp docs are lacking a significant amount of information about how to do this properly and I think that was a large source of confusion for me. I believe I’m setting everything up correctly now, but just have one final question that I can’t figure out from the code.

How is a pointer to the class used to register an address, or set the address for the sockaddr_conn? I see in some code in another project, a file descriptor to a SOCKET is used here. What is supposed to be passed to this, and how does passing ‘this’ work?

usrsctp_register_address(static_cast<void*>(this));
...
sconn.sconn_addr   = static_cast<void*>(this);
...
rconn.sconn_addr   = static_cast<void*>(this);

Thanks again for your patience with me

It can be whatever you wish. It’s custom data you’ll need later to identify which SCTP association (sctp_socket) the SCTP message being sent belongs to. See the second argument given to usrsctp_init_nothreads() or usrsctp_init() in DepUsrSCTP.cpp in mediasoup.