How to use Netlink for multicast IPC between userspace and userspace?

53 Views Asked by At

The information that I collected about netlink multicast is all between kernal and userspace.

Although I know it maybe a better way to use Shared Memory for my situation, by I need to use Netlink for multicast IPC between userspace and userspace. I have already constructed a demo with a netlink receiver and sender, but I encountered a few issues due to my limited understanding of Netlink.

Here is my simple netlink demo for receiver:

#include <linux/netlink.h>
#include <malloc.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include <cstring>
#include <string>

#define NLINK_MSG_LEN 1024

int main() {
  // Question:
  // when I use `NETLINK_USERSOCK` in the receiver program, the system informs
  // me of insufficient permissions to bind fd, and I have to usesudo, but I
  // prefer not to.
  int fd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
  printf("receiver is ready!\n");
  if (fd < 0) {
    perror("create Netlink socket failed!\n");
    exit(1);
  }

  struct sockaddr_nl listen_addr;
  listen_addr.nl_family = AF_NETLINK;  // AF_NETLINK socket protocol
  listen_addr.nl_pid = getpid();       // application unique id
  listen_addr.nl_groups =
      (1 << 3);  // specify the address which the process want to receive
                 // if two or more address then turn on those bits
                 // subscribe to 3 multicast address
  if (::bind(fd, (struct sockaddr*)&listen_addr, sizeof(listen_addr)) != 0) {
    perror("bind netlink socket fail");
    exit(1);
  }
  printf("Listening to the MCAST address (3): %d\n", listen_addr.nl_groups);
  printf("receiver pid [%u]\n", getpid());

  struct nlmsghdr* nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(NLINK_MSG_LEN));
  nlh->nlmsg_len = NLMSG_SPACE(NLINK_MSG_LEN);
  nlh->nlmsg_pid = getpid();
  nlh->nlmsg_flags = 0;
  struct iovec iov;
  iov.iov_base = reinterpret_cast<void*>(nlh);
  iov.iov_len = nlh->nlmsg_len;

  // we can get sender address from sender_addr
  struct sockaddr_nl sender_addr;
  struct msghdr msg;
  msg.msg_name = reinterpret_cast<void*>(&sender_addr);
  msg.msg_namelen = sizeof(sender_addr);
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  // try to receive netlink message
  while (1) {
    ssize_t bytes_received = ::recvmsg(fd, &msg, 0);
    if (bytes_received == -1) {
      perror("receive message fail\n");
    } else if (bytes_received == 0) {
      printf("connection closed by remote peer\n");
    } else {
      printf("receive message from [%u]\n", sender_addr.nl_pid);
      printf("receive message: %s\n", reinterpret_cast<char*>(NLMSG_DATA(nlh)));
    }
  }
  close(fd);
}

my demo for sender:

#include <linux/netlink.h>
#include <malloc.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include <cstring>
#include <string>

constexpr uint32_t NLINK_MSG_LEN = 1024;

int main() {
  // **Question**:when I use `NETLINK_GENERIC` in the sender program, the system
  // informs me of insufficient permissions to send message, and I have to use 
  // sudo, but I prefer not to.
  int fd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
  printf("sender is ready!\n");
  if (fd < 0) {
    perror("create Netlink socket failed!");
    exit(1);
  }

  struct sockaddr_nl dest_addr;
  dest_addr.nl_family = AF_NETLINK;
  // **Question**: Online recommendations suggest using the process ID as
  // nl_pid, but I cannot obtain the process ID of the receiver. Is there a way
  // to configure only nl_groups, so that all receivers in this nl_groups can
  // receive messages?
  //
  // Moreover, what's strange is that even if I arbitrarily set a number for
  // nl_pid here, although it throws a 'Connection refused' error, all the
  // receivers in nl_groups can still receive messages.
  dest_addr.nl_pid = 100;
  dest_addr.nl_groups = (1 << 3);

  // constructor netlink message
  struct nlmsghdr* nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(NLINK_MSG_LEN));
  ::memset(nlh, 0, NLMSG_SPACE(NLINK_MSG_LEN));
  nlh->nlmsg_len = NLMSG_SPACE(NLINK_MSG_LEN);
  nlh->nlmsg_pid = getpid();
  nlh->nlmsg_flags = 0;

  struct iovec iov;
  iov.iov_base = reinterpret_cast<void*>(nlh);
  iov.iov_len = nlh->nlmsg_len;

  struct msghdr msg;
  msg.msg_name = reinterpret_cast<void*>(&dest_addr);
  msg.msg_namelen = sizeof(dest_addr);
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  // send message
  ::memcpy(reinterpret_cast<char*>(NLMSG_DATA(nlh)), "Hello World !", 14);
  ssize_t bytes_sent = ::sendmsg(fd, &msg, 0);
  printf("sender pid [%u]\n", getpid());
  if (bytes_sent == -1) {
    perror("send message fail\n");
  } else {
    printf("send message [%s]\n", reinterpret_cast<char*>(NLMSG_DATA(nlh)));
  }

  ::close(fd);  // close the socket
}

Here are my serveral questions, and some of them have already been raised in the demo code.

Question 1

I don't want to require sudo permission to execute the code. However, when I use NETLINK_USERSOCK in the Receiver or NETLINK_GENERIC in the Sender, it prompts me for insufficient permissions.

Question 2

I wish to send messages to all receivers corresponding to nl_groups in the sender code without specifying dest_addr.nl_pid.

Moreover, it is strange that the sender program can send to the nl_groups's all receiver but it would raise the Connection refused problem.

enter image description here

Question 3

If any question abound my code, please let me know, thanks~

0

There are 0 best solutions below