[Ace-users] Why doesn't ACE allow 0 length UDP packets (datagrams) to be sent?

Andi aheusser at gmail.com
Thu Mar 6 22:38:08 CST 2008


    ACE VERSION:

        5.6


    HOST MACHINE and OPERATING SYSTEM:


        Windows


    TARGET MACHINE and OPERATING SYSTEM, if different from HOST:
    COMPILER NAME AND VERSION (AND PATCHLEVEL):


        Windows
        Microsoft Visual C++ 8 (VS 2005)


    THE $ACE_ROOT/ace/config.h FILE [if you use a link to a platform-
    specific file, simply state which one]:

	#ifndef ACE_CONFIG_WIN32_H
	#define ACE_CONFIG_WIN32_H
	#include /**/ "ace/pre.h"

	// NOTE: Please do not add anything besides #include's here.  Put
other stuff
	//       (definitions, etc.) in the included headers

	// We need to ensure that for Borland vcl.h can be included before
	// windows.h.  So we will not include config-win32-common.h from
here,
	// but instead let it be included at the appropriate place in
	// config-win32-borland.h.
	#if !defined (__BORLANDC__)
	#    include "ace/config-win32-common.h"
	#endif /* !__BORLANDC__ */

	// Include the config-win32-* file specific to the compiler
	#if defined (__BORLANDC__)
	#    include "ace/config-win32-borland.h"
	#elif defined (_MSC_VER)
	#    include "ace/config-win32-msvc.h"
	#elif defined (ghs)
	#    include "ace/config-win32-ghs.h"
	#elif defined (__MINGW32__)
	#    include "ace/config-win32-mingw.h"
	#elif defined (__DMC__)
	#    include "ace/config-win32-dmc.h"
	#else
	#    error Compiler is not supported
	#endif

	#include /**/ "ace/post.h"
	#endif /* ACE_CONFIG_WIN32_H */



    AREA/CLASS/EXAMPLE AFFECTED:


        N/A


    DOES THE PROBLEM AFFECT:

       Problem prevents applications from sending 0 byte size UDP
packets.


    SYNOPSIS:

       Ace prevents applications from sending UDP packets of size 0
through the sockets even though the underlying WINSOCK2 commands allow
sending such packets.


    DESCRIPTION:

       For our application, clients need to be able to send UDP
datagrams of size 0 to our servers for hole punching attempts. During
our testing we've found that any attempt of sending 0 bytes through
the ACE Proactor results in a failure.
       After some investigation of the source code I have found the
place where it rejects those packets and I have been able to change it
so that it allows me to send 0 byte size packets.

       The main question I have now is, what the reason is for denying
to send those kinds of packets.


       The class where it does the check for the packet size is in
WIN32_Asynch_IO.cpp in the method

ssize_t
ACE_WIN32_Asynch_Write_Dgram::send (ACE_Message_Block *message_block,
                                    size_t &number_of_bytes_sent,
                                    int flags,
                                    const ACE_Addr &addr,
                                    const void *act,
                                    int priority,
                                    int signal_number)
{
  number_of_bytes_sent = 0;

  size_t bytes_to_write = 0;

  iovec  iov[ACE_IOV_MAX];
  int    iovcnt = 0;

  for (const ACE_Message_Block* msg = message_block;
       msg != 0 && iovcnt < ACE_IOV_MAX;
       msg = msg->cont () , ++iovcnt )
  {
    size_t msg_len = msg->length ();

...

    bytes_to_write += msg_len;

    // Make as many iovec as needed to fit all of msg_len.
    size_t rd_ptr_offset = 0;
    while (msg_len > 0 && iovcnt < ACE_IOV_MAX)
      {
        u_long this_chunk_length;
        if (msg_len > ULONG_MAX)
          this_chunk_length = ULONG_MAX;
        else
          this_chunk_length = static_cast<u_long> (msg_len);
        // Collect the data in the iovec.
        iov[iovcnt].iov_base = msg->rd_ptr () + rd_ptr_offset;
        iov[iovcnt].iov_len  = this_chunk_length;
        msg_len -= this_chunk_length;
        rd_ptr_offset += this_chunk_length;

        // Increment iovec counter if there's more to do.
        if (msg_len > 0)
          iovcnt++;
      }
...
  }

  if ( bytes_to_write == 0 )
      ACE_ERROR_RETURN ((LM_ERROR,
                         ACE_TEXT
("ACE_WIN32_Asynch_Write_Dgram::send:")
                         ACE_TEXT ("Attempt to write 0 bytes\n")),
                        -1);
....
  ssize_t initiate_result = ACE_OS::sendto (result->handle (),
                                            iov,
                                            iovcnt,
                                            number_of_bytes_sent,
                                            result->flags_,
                                            (sockaddr *) addr.get_addr
(),
                                            addr.get_size(),
                                            result,
                                            0);
....
}

When sending in a message_block of length 0, then the while loop is
never executed thus leaving the 'iov' array uninitialized but the
'iovcnt' is incremented to 1 as part of the for loop.
The 'if ( bytes_to_write == 0 )' statement will then return since no
bytes are to be written/sent. If I comment out that if statement, then
the ACE_OS::sendto will fail because the first entry in the iov array
is not valid.

If I change this method to the following, then everything works fine
and it's able to deal with sending 0 bytes.

ssize_t
ACE_WIN32_Asynch_Write_Dgram::send (ACE_Message_Block *message_block,
                                    size_t &number_of_bytes_sent,
                                    int flags,
                                    const ACE_Addr &addr,
                                    const void *act,
                                    int priority,
                                    int signal_number)
{
  number_of_bytes_sent = 0;

  size_t bytes_to_write = 0;

  iovec  iov[ACE_IOV_MAX];
  int    iovcnt = 0;

  for (const ACE_Message_Block* msg = message_block;
       msg != 0 && iovcnt < ACE_IOV_MAX;
       msg = msg->cont () , ++iovcnt )
  {
    size_t msg_len = msg->length ();

...

    bytes_to_write += msg_len;

    // Make as many iovec as needed to fit all of msg_len.
    size_t rd_ptr_offset = 0;
    do
    {
        if (msg_len >= 0 && iovcnt < ACE_IOV_MAX)
        {
            u_long this_chunk_length;
            if (msg_len > ULONG_MAX)
              this_chunk_length = ULONG_MAX;
            else
              this_chunk_length = static_cast<u_long> (msg_len);
            // Collect the data in the iovec.
            iov[iovcnt].iov_base = msg->rd_ptr () + rd_ptr_offset;
            iov[iovcnt].iov_len  = this_chunk_length;
            msg_len -= this_chunk_length;
            rd_ptr_offset += this_chunk_length;

            // Increment iovec counter if there's more to do.
            if (msg_len > 0)
              iovcnt++;
        }
    }while (msg_len > 0 && iovcnt < ACE_IOV_MAX);
...
  }

  //if ( bytes_to_write == 0 )
  //    ACE_ERROR_RETURN ((LM_ERROR,
  //                       ACE_TEXT
("ACE_WIN32_Asynch_Write_Dgram::send:")
  //                       ACE_TEXT ("Attempt to write 0 bytes\n")),
  //                      -1);

....

  ssize_t initiate_result = ACE_OS::sendto (result->handle (),
                                            iov,
                                            iovcnt,
                                            number_of_bytes_sent,
                                            result->flags_,
                                            (sockaddr *) addr.get_addr
(),
                                            addr.get_size(),
                                            result,
                                            0);
....
}

I changed the 'while' loop to a 'do-while' loop and added the
additional if statement inside it. This way the first entry in the iov
array will be properly initialized in the case where the message
length is 0. It still works the same way as before when sending non-
zero size messages.

Is there anything wrong with changing this method in such a way?

Andi


More information about the Ace-users mailing list