Search code examples
csocketsudprecvfrom

recvfrom fills the buffer with zeros


I am trying to implement distance vector routing over udp. I have the following structs:

struct cost_info { 
    uint8_t id; 
    uint8_t pad;
    uint16_t cost; 
}__attribute__((__packed__));

struct advertisement_packet { 
    uint8_t type;
    uint8_t version;
    uint16_t num_updates;
    struct cost_info data[];
}__attribute__((__packed__));

So the packets are 1 byte type, 1 byte version, 2 bytes update count, and then 4*update count bytes of data. I am using flexible array members so that I can just do

sendto(fd, pkt, sizeof(struct advertisement_packet) + pkt->num_updates * sizeof(struct cost_info), 0, addr, sizeof(struct sockaddr_in));

and send the entire thing in one packet. Sending in one packet is required. I am able to verify using wireshark that the data is being sent out correctly. On the receiving end, I do two receives. The first gets the first 4 bytes so I can know how many updates to expect. I then resize my receiver buffer accordingly and recieve 4*num_updates bytes. The issue is that this second receive fills my buffer with zeros! If I expect 12 bytes of data, I get 12 bytes of zeros. The next time I read from that socket, I get the beginning of the next packet as expected. Why would this happen?

Here is my receive code for if needed.

recvfrom(i, pkt, sizeof(struct advertisement_packet),0,NULL,0);
//reallocate more space if the incoming data will need it
if (pkt->num_updates > pkt_cost_slots) {
  pkt_cost_slots *= 2;
  pkt = realloc(pkt,sizeof(struct advertisement_packet) + sizeof(struct cost_info) * pkt_cost_slots);
  assert(pkt);
}
//receive data
recvfrom(i,pkt->data,pkt->num_updates * sizeof(struct cost_info),0,NULL,0);

Solution

  • You cannot split receipt of one datagram over two recv() / recvfrom() calls. It is the fundamental nature of datagram-oriented sockets that sends and receives operate in units of datagrams. You can, however, read part or all of a datagram without removing it from the receive queue (i.e. "peek" at it). The specifications for recvfrom() put it this way:

    For message-based sockets, such as SOCK_RAW, SOCK_DGRAM, and SOCK_SEQPACKET, the entire message shall be read in a single operation. If a message is too long to fit in the supplied buffer, and MSG_PEEK is not set in the flags argument, the excess bytes shall be discarded.

    You can thus accomplish your goal by using MSG_PEEK on the first recvfrom() call, but note that then you need to receive the entire datagram in the second recvfrom() call, not just the part that you did not read the first time.