Jake Sandler
Jake Sandler

Reputation: 162

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);

Upvotes: 1

Views: 1556

Answers (2)

selbie
selbie

Reputation: 104464

UDP is a datagram protocol. The entire datagram packet gets passed into the first recvfrom call. If your buffer size is insufficient, then the data bytes will get lost. They are not passed to a subsequent recvfrom.

So the solution is to just pass in a buffer big enough for any UDP datagram and then cast it to your data type.

unsigned char buffer[65535];
struct advertisement_packet *pkt = (advertisement_packet *)buffer;
recvfrom(i, buffer, sizeof(buffer), (sockaddr*)&addr, &addrlen);

Here's a better solution that also does the usual security checks so you don't get duped into a buffer overrun by a malicious attacker.

int result = 0;
unsigned char buffer[65535];
struct advertisement_packet *pkt = (advertisement_packet *)buffer;
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
int accepted = false;

result = recvfrom(i, buffer, sizeof(buffer), (sockaddr*)&addr, &addrlen);

if (result > 0)
{
    size_t expected_size = sizeof(struct advertisement_packet) + pkt->num_updates * sizeof(struct cost_info);

    if (result >= expected_size)
    {
        accepted = true;
    }
    else
    {
        // packet is malformed or undersized, so let's avoid a buffer overrun
        result = -1;
    }
}

if (accepted)
{
     // got packet and it's valid
     // continue parsing it, or make a copy of it.
}

Upvotes: 1

John Bollinger
John Bollinger

Reputation: 180048

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.

Upvotes: 1

Related Questions