switchbrew / libnx

Library for Switch Homebrew
https://switchbrew.github.io/libnx/
ISC License
1.27k stars 174 forks source link

Question: sys/socket.h multicast capabilities #551

Closed fra189 closed 1 year ago

fra189 commented 3 years ago

Hi there, I'm trying to implement mDNS protocol in my nintendo switch brew. My approach:

#include <switch.h>
...
socketInitialize();

 sock = socket(AF_INET, SOCK_DGRAM, 0);
 setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &param, sizeof(param));
 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &enable, sizeof(enable));
 setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void*) &param, sizeof(param));
 setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,(void*) &enable, sizeof(enable));
 setsockopt (sock, IPPROTO_IP, IP_MULTICAST_IF, (void*) &addr, sizeof(addr));
 setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &mreq, sizeof(mreq));

....
socketExit();

...

Triyng to setup IP_MULTICAST_TTL and IP_ADD_MEMBERSHIP flags, I obtain an "operation not permitted" error (errno 1). What am I missing?

Thank you in advance.

mtheall commented 3 years ago

What did you fill into addr for IP_MULTICAST_IF and mreq for IP_ADD_MEMBERSHIP?

fra189 commented 3 years ago

Hi, Thank you for answering. So this is the part of the code:

#define MDNS_MULTICAST_ADDRESS "224.0.0.251"
#define MDNS_PORT 5353

int init(struct in_addr host) {
    struct ip_mreq mreq;
    struct sockaddr_in addr;
    int sock;
    char param;

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0) {
       return sock;
     }

    param = 32;
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &param, sizeof(param)) < 0) {
      return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(MDNS_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
      return -1;
    }

    memset(&mreq, 0, sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr(MDNS_MULTICAST_ADDRESS);
    mreq.imr_interface.s_addr = host.s_addr;

    if (setsockopt (sock, IPPROTO_IP, IP_MULTICAST_IF, (void*) &mreq.imr_interface.s_addr, sizeof(mreq.imr_interface.s_addr)) < 0)  {
        return -1;
    }
    if(setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &mreq, sizeof(mreq)) < 0) {
       return -1;
    }
    return sock;
  }

  .....
  struct in_addr host;
  const long ip = gethostid();
  char ip_str[32] = {0};
  inet_ntop(AF_INET, &ip, ip_str, sizeof(ip_str));
  host.s_addr = inet_addr(ip_str);  
  int sock = init(host);
mtheall commented 3 years ago

I get EPERM for every multicast-related socket option. It's possible the OS won't allow unprivileged multicast servers.

cgutman commented 3 years ago

@fra189 fwiw, if you're just looking to resolve mDNS services on your network (rather than advertise them), you may be able to avoid having to join the multicast group to get a response. You can send an mDNS query and request a unicast response by setting the unicast-response bit in the header. It doesn't guarantee a unicast response, but it generally should result in one (especially if you send QU queries periodically if you get no response).

See https://datatracker.ietf.org/doc/html/rfc6762#section-5.4

fra189 commented 3 years ago

Thank you @cgutman I can make a try with that.

xfangfang commented 1 year ago

I figured out how to join a multicast group, just pass in higher permissions when initializing the socket.

SocketInitConfig cfg = *(socketGetDefaultInitConfig());
cfg.bsd_service_type = BsdServiceType_Auto; // or BsdServiceType_System
socketInitialize(&cfg);

This can also be used to help create an 80 port HTTP service, hoping this comment would help someone ~

SciresM commented 1 year ago

I think this is actually about library compatibility version, not about permissions.

This is untested and based on RE, but I think the line that causes EPERM is this one: https://github.com/switchbrew/libnx/blob/master/nx/source/runtime/devices/socket.c#L65

If I am reading this permission check function right, the following require library version > 3:

IP_MULTICAST_IF
IP_MULTICAST_TTL
IP_MULTICAST_LOOP
IP_ADD_MEMBERSHIP
IP_DROP_MEMBERSHIP
IP_MULTICAST_VIF

I think BsdServiceType_System just causes an early return in the permission checking function, which bypasses the library compatibility check entirely.

xfangfang commented 1 year ago

Thanks for the detailed explanation, I confirmed that after setting cfg.bsdsockets_version = 4;, multicast is indeed working.

fincs commented 1 year ago

Fixed by libnx v4.5.0.