Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UDP connect() and recv() on Linux

Tags:

c

linux

udp

According to the connect(2) man pages

If the socket sockfd is of type SOCK_DGRAM then serv_addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection to the socket that is bound to the address specified by serv_addr.

I am trying to filter packets from two different multicast groups that are being broadcasted on the same port and I thought connect() would have done the job but I can't make it work. In facts when I add it to my program I don't receive any packet. More info in this thread.

This is how I set the connect parameters:

memset(&mc_addr, 0, sizeof(mc_addr));
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr(multicast_addr);
mc_addr.sin_port = htons(multicast_port);
printf("Connecting...\n");
if( connect(sd, (struct sockaddr*)&mc_addr, sizeof(mc_addr)) < 0 ) {
  perror("connect");
  return -1;
}

printf("Receiving...\n");
while( (len = recv(sd, msg_buf, sizeof(msg_buf), 0)) > 0 )
  printf("Received %d bytes\n", len);
like image 546
Robert Kubrick Avatar asked Dec 12 '11 22:12

Robert Kubrick


People also ask

Can I use recv with UDP?

Unlike send(), the recv() function of Python's socket module can be used to receive data from both TCP and UDP sockets.

What does connect () do on a UDP socket?

Datagram (UDP) sockets. The CONNECT command enables an application to associate a socket with the socket name of a peer. The socket then is considered to be a connected UDP socket. You can call the CONNECT command multiple times with different peer names to change the socket association.

What is RECV in Linux?

The recv() function receives data on a socket with descriptor socket and stores it in a buffer. The recv() call applies only to connected sockets.

What is the difference between read () and recv ()?

The only difference between recv() and read(2) is the presence of flags. With a zero flags argument, recv() is generally equivalent to read(2) (but see NOTES).


1 Answers

Your program (probably) has the following problems:

  • you should be using bind() instead of connect(), and
  • you're missing setsockopt(..., IP_ADD_MEMBERSHIP, ...).

Here's an example program that receives multicasts. It uses recvfrom(), not recv(), but it's the same except you also get the source address for each received packet.

To receive from multiple multicast groups, you have three options.

First option: Use a separate socket for each multicast group, and bind() each socket to a multicast address. This is the simplest option.

Second option: Use a separate socket for each multicast group, bind() each socket INADDR_ANY, and use a socket filter to filter out all but a single multicast group.

Because you've bound to INADDR_ANY, you may still get packets for other multicast groups. It is possible to filter them out using the kernel's socket filters:

#include <stdint.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/filter.h>

/**
 * Adds a Linux socket filter to a socket so that only IP
 * packets with the given destination IP address will pass.
 * dst_addr is in network byte order.
 */
int add_ip_dst_filter (int fd, uint32_t dst_addr)
{
    uint16_t hi = ntohl(dst_addr) >> 16;
    uint16_t lo = ntohl(dst_addr) & 0xFFFF;

    struct sock_filter filter[] = {
        BPF_STMT(BPF_LD + BPF_H + BPF_ABS, SKF_NET_OFF + 16), // A <- IP dst high
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, hi, 0, 3),        // if A != hi, goto ignore
        BPF_STMT(BPF_LD + BPF_H + BPF_ABS, SKF_NET_OFF + 18), // A <- IP dst low
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, lo, 0, 1),        // if A != lo, goto ignore
        BPF_STMT(BPF_RET + BPF_K, 65535),                     // accept
        BPF_STMT(BPF_RET + BPF_K, 0)                          // ignore
    };

    struct sock_fprog fprog = {
        .len = sizeof(filter) / sizeof(filter[0]),
        .filter = filter
    };

    return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
}    

Third option: use a single socket to receive multicasts for all multicast groups.

In that case, you should do an IP_ADD_MEMBERSHIP for each of the groups. This way you get all packets on a single socket.

However, you need extra code to determine which multicast group a received packet was addressed to. To do that, you have to:

  • receive packets with recvmsg() and read the IP_PKTINFO or equivalent ancillary data message. However, to make recvmsg() give you this message, you first have to
  • enable reception of IP_PKTINFO ancillary data messages with setsockopt().

The exact thing you need to do depends on IP protocol version and OS. Here's how I did it (IPv6 code not tested): enabling PKTINFO and reading the option.

Here's a simple program that receives multicasts, which demonstrates the first option (bind to multicast address).

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXBUFSIZE 65536

int main (int argc, char **argv)
{
    if (argc != 4) {
        printf("Usage: %s <group address> <port> <interface address>\n", argv[0]);
        return 1;
    }

    int sock, status, socklen;
    char buffer[MAXBUFSIZE+1];
    struct sockaddr_in saddr;
    struct ip_mreq imreq;

    // set content of struct saddr and imreq to zero
    memset(&saddr, 0, sizeof(struct sockaddr_in));
    memset(&imreq, 0, sizeof(struct ip_mreq));

    // open a UDP socket
    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket failed!");
        return 1;
    }

    // join group
    imreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
    imreq.imr_interface.s_addr = inet_addr(argv[3]);
    status = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
    (const void *)&imreq, sizeof(struct ip_mreq));

    saddr.sin_family = PF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    status = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
    if (status < 0) {
        perror("bind failed!");
        return 1;
    }

    // receive packets from socket
    while (1) {
        socklen = sizeof(saddr);
        status = recvfrom(sock, buffer, MAXBUFSIZE, 0, (struct sockaddr *)&saddr, &socklen);
        if (status < 0) {
            printf("recvfrom failed!\n");
            return 1;
        }

        buffer[status] = '\0';
        printf("Received: '%s'\n", buffer);
    }
}
like image 160
Ambroz Bizjak Avatar answered Nov 15 '22 04:11

Ambroz Bizjak