Building UDP datagram with fixed size header in C

1k Views Asked by At

I am new to C and I am trying to build a UDP datagram and then send it from a client program to a server program on an different machine.

I have run into some problem when trying to build the datagram, specifically the fixed size (IPv4) header: The format of the header is (client IP (32 bit) | client port (16 bit) | packet length (32 bit) | ... etc.)

The approach I choose to go with was to gather the necessary header and body information and then cast them into char* and then concatenate them together to generate the datagram. However, when I cast int and long to char* (port number and packet length) it does not guarantee a fixed size char* (for example, if the packet length (datatype long) is calculated to be 440 and I convert it to char*, then strlen will show the length of the char* to be 2 instead of 4 since the number is not big enough to take all 4 bytes, I would assume...)

I tried a different way of converting long into fixed size char* by bit-wise operation:

//assume the datagram size was calculated to be 440 bits
unsigned long len = 440;
unsigned char dg_len [4];
dg_len[0] = (len >> 24) & 0xFF;
dg_len[1] = (len >> 16) & 0xFF;
dg_len[2] = (len >> 8) & 0xFF;
dg_len[3] = len & 0xFF;
printf("%d" ,strlen(dg_lenPtr)); //this would display 0 
printf("%d" ,(unsigned long)dg_lenPtr); //this display some random number

The only explanation I thought of for this is that the unused 2 higher bytes of dg_lenPtr are being read as null character since strlen shows the length to be 0.

I am starting to think that char* is not the data structure I need to use to build the datagram. Can someone point me to the right direction on what I need to do to build the fix sized header for the datagram?

2

There are 2 best solutions below

1
On

You are not responsible for generating the IPv4 header. The send() and sendto() functions handle that for you. All you have to focus on is sending your desired datagram payload to the desired destination IP:Port (by specifying it directly to sendto(), or to connect() in the case of send()).

int connect(int socket, const struct sockaddr *address, socklen_t address_len);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
0
On
unsigned long len = 440;
unsigned char dg_len [4];
dg_len[0] = (len >> 24) & 0xFF;
dg_len[1] = (len >> 16) & 0xFF;
dg_len[2] = (len >> 8) & 0xFF;
dg_len[3] = len & 0xFF;

This is the right way to store values in a specific byte-order. Shift-and-mask lets you access any part of the buffer without having to be concerned about alignment issues or the byte-order on your local machine, and unsigned char is the only type that is quaranteed to be without padding bits, trap representations or alignment problems.

printf("%d" ,strlen(dg_lenPtr)); //this would display 0

You haven't declared dg_lenPtr, but assuming that it is a pointer to dg_len, calling strlen() on it is meaningless because it doesn't point to a string. If len is less than 224, strlen() will return 0 because the first byte happens to be zero.

printf("%d" ,(unsigned long)dg_lenPtr); //this display some random number

This might output the address stored in dg_lenPtr, converted to unsigned long (whatever that means). It might not, because you're passing an unsigned long to printf() which is expecting a signed int. The result of this mismatch is undefined behavior, which means that the program is free to do whatever it wants.


Personally, I would use a buffer to represent the entire datagram and write the values directly into the buffer. You can use either a fixed sized buffer (unsigned char buf[/* some size*/];) or an allocated one (unsigned char *buf = malloc (/* some size */);) depending on your needs or preferences.

To simplify reading and writing to the buffer, I would create some helper functions:

static void w32 (unsigned char *b, unsigned long v)
{
  b[0] = (v >> 24) & 0xff;
  b[1] = (v >> 16) & 0xff;
  b[2] = (v >>  8) & 0xff;
  b[3] = (v      ) & 0xff;
}
static void w16 (unsigned char *b, unsigned v)
{
  b[0] = (v >>  8) & 0xff;
  b[1] = (v      ) & 0xff;
}

w32 (buf  , ip);
w16 (buf+4, port);
w32 (buf+6, len);
/* And so on */

You could define macros to do the same job, but that would be pointless. Any reasonably modern compiler with optimizations turned on should inline the functions and exclude them from the final executable. The macros will just be more convoluted and less type-safe.