LinuxQuestions.org
Review your favorite Linux distribution.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 07-25-2011, 03:56 PM   #1
dwhitney67
Senior Member
 
Registered: Jun 2006
Location: Maryland
Distribution: Kubuntu, Fedora, RHEL
Posts: 1,541

Rep: Reputation: 335Reputation: 335Reputation: 335Reputation: 335
IPv6: Using results from recvfrom()


I have a socket library that I tinker with from time to time. Lately, I decided to expand it to support IPv6.

One of the issues I am having is sending data, using sendto(). When the server receives data from a client, using recvfrom(), it does not retain the sockaddr_in information, but instead provides the callee the IPv6 address and port number from where the data originated. See here:
Code:
int
UDPSocket::recv(char* buffer, size_t bufferLen, std::string& sourceAddr, unsigned short& sourcePort)
{
  sockaddr_in sin;
  socklen_t   sinLen = sizeof(sin);

  int bytesRcvd = ::recvfrom(m_Socket, buffer, bufferLen, 0, (sockaddr*) &sin, (socklen_t*) &sinLen);

  if (bytesRcvd == -1)
  {
    throw SocketException("Error occurred when attempting to receive data", errno);
  }

  char buf[INET6_ADDRSTRLEN + 1] = {0};
  const char* res = inet_ntop(m_Domain, &sin.sin_addr, buf, sizeof(buf));

  sourceAddr = (res ? res : "Source Not Available");
  sourcePort = ntohs(sin.sin_port);

  return bytesRcvd;
}
As indicated above, after the UDPSocket::recv() method is called, the callee is returned a string and a port number. When UDPSocket::send() is called, the string/number pair is used to formulate the destination address using getaddrinfo(). However that fails. Why is this?

Here's the send() method:
Code:
void
UDPSocket::send(const char* msg, size_t msgLen, const std::string& destAddr, unsigned short destPort)
{
  struct addrinfo info;

  fillAddress(info, destPort, destAddr.c_str());

  if (::sendto(m_Socket, msg, msgLen, 0, info.ai_addr, info.ai_addrlen) != (int) msgLen)  // <-- FAILS HERE
  {
    throw SocketException("Error occurred when attempting to send data", errno);
  }
}
And the code for fillAddress():
Code:
void
Socket::fillAddress(struct addrinfo& info, const unsigned short port, const char* address)
{
  struct addrinfo hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family   = m_Domain;
  hints.ai_socktype = m_Type;
  hints.ai_protocol = m_Protocol;
  hints.ai_flags    = AI_PASSIVE;

  struct addrinfo* host_info = 0;

  char portstr[6] = {0};
  snprintf(portstr, sizeof(portstr), "%d", port);

  if (getaddrinfo(address, portstr, &hints, &host_info) != 0  ||
      !host_info || !host_info->ai_addr || host_info->ai_family != m_Domain)
  {
    SocketException e(gai_strerror(errno), errno);
    if (host_info) freeaddrinfo(host_info);
    throw e;
  }

  memcpy(&info, host_info, sizeof(struct addrinfo));

  freeaddrinfo(host_info);
}
P.S. The client is able to send data to the server, using the send() code above; the server fails with the following error when attempting to reply:
Code:
Error occurred when attempting to send data [ Network is unreachable (101) ]

Last edited by dwhitney67; 07-25-2011 at 10:55 PM. Reason: size of buf was changed in actual code (and above too)
 
Old 07-25-2011, 05:35 PM   #2
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by dwhitney67 View Post
Code:
void
Socket::fillAddress(struct addrinfo& info, const unsigned short port, const char* address)
{
//...
  memcpy(&info, host_info, sizeof(struct addrinfo));

  freeaddrinfo(host_info);
}
I believe you copy a member struct sockaddr* with memcpy (note a pointer, not an object) and then free it indirectly with freeaddrinfo. Maybe you should pass a struct sockaddr& to this function and copy what's pointed to by host_info->ai_addr instead of passing struct addrinfo&.
Kevin Barry

PS You might comment out the freeaddrinfo line temporarily, before making major changes, to see if this is actually the problem.

Last edited by ta0kira; 07-25-2011 at 05:42 PM.
 
Old 07-25-2011, 08:28 PM   #3
dwhitney67
Senior Member
 
Registered: Jun 2006
Location: Maryland
Distribution: Kubuntu, Fedora, RHEL
Posts: 1,541

Original Poster
Rep: Reputation: 335Reputation: 335Reputation: 335Reputation: 335
Quote:
Originally Posted by ta0kira View Post
I believe you copy a member struct sockaddr* with memcpy (note a pointer, not an object) and then free it indirectly with freeaddrinfo. Maybe you should pass a struct sockaddr& to this function and copy what's pointed to by host_info->ai_addr instead of passing struct addrinfo&.
Kevin Barry

PS You might comment out the freeaddrinfo line temporarily, before making major changes, to see if this is actually the problem.
Kevin, thanks for the reply. I tried both of your suggestions, however I went from the same predicament to a worse situation. Merely commenting out freeaddrinfo() didn't buy me anything (ie results were the same). When I changed the code to rely on a struct sockaddr versus using a struct addrinfo, then things turned for the worse. I could not even get the server code to bind() the socket. An invalid argument (errno = 22) was returned.

With the original code that I submitted earlier, I was able to get the server to bind() the socket, and receive a message from a client (that also uses the same socket library).

Here's the revamped fillAddress() function, which succeeds. It is when I bind() that the server fails.
Code:
void
Socket::fillAddress(sockaddr& info, const unsigned short port, const char* address)
{
  struct addrinfo hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family   = m_Domain;
  hints.ai_socktype = m_Type;
  hints.ai_protocol = m_Protocol;
  hints.ai_flags    = AI_PASSIVE;

  struct addrinfo* host_info = 0;

  char portstr[6] = {0};
  snprintf(portstr, sizeof(portstr), "%d", port);

  if (getaddrinfo(address, portstr, &hints, &host_info) != 0  ||
      !host_info || !host_info->ai_addr || host_info->ai_family != m_Domain)
  {
    SocketException e(gai_strerror(errno), errno);
    if (host_info) freeaddrinfo(host_info);
    throw e;
  }

  memset(&info, 0, sizeof(sockaddr));
  memcpy(&info, host_info->ai_addr, sizeof(sockaddr));

  freeaddrinfo(host_info);
}
I'm accustomed to working with struct sockaddr_in for my IPv4 connection information, and I have seen that there's a struct sockaddr_in6; how do these structures relate to struct sockaddr? Are the structures the same size? I wonder if I am losing context of the data when I copy the host_info->ai_addr using a sizeof(sockaddr)?

Last edited by dwhitney67; 07-25-2011 at 08:30 PM.
 
Old 07-25-2011, 10:19 PM   #4
dwhitney67
Senior Member
 
Registered: Jun 2006
Location: Maryland
Distribution: Kubuntu, Fedora, RHEL
Posts: 1,541

Original Poster
Rep: Reputation: 335Reputation: 335Reputation: 335Reputation: 335
Well, I found a problem; these numbers ought to point it out:
Code:
ai_addrlen        = 28
sockaddr size     = 16
sockaddr_in size  = 16
sockaddr_in6 size = 28
I had to make an adjustment to the code before calling fillAddress(). Something like:
Code:
  sockaddr* addr    = NULL;
  socklen_t addrlen = 0;

  if (m_Domain == AF_INET)
  {
     addr    = reinterpret_cast<sockaddr*>(new sockaddr_in);
     addrlen = sizeof(sockaddr_in);
  }
  else if (m_Domain == AF_INET6)
  {
     addr    = reinterpret_cast<sockaddr*>(new sockaddr_in6);
     addrlen = sizeof(sockaddr_in6);
  }

  fillAddress(addr, addrlen, destPort, destAddr.c_str());

  ...
So now, I am able to get the Server running once again. The Client is able to send data to the Server, but when the Server replies, I get the following error:
Code:
Error occurred when attempting to send data [ Network is unreachable (101) ]
So it seems that I'm back to square one... albeit with better code.
 
Old 07-25-2011, 10:21 PM   #5
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by dwhitney67 View Post
I'm accustomed to working with struct sockaddr_in for my IPv4 connection information, and I have seen that there's a struct sockaddr_in6; how do these structures relate to struct sockaddr? Are the structures the same size? I wonder if I am losing context of the data when I copy the host_info->ai_addr using a sizeof(sockaddr)?
struct sockaddr is just a generalization of all socket-address formats for the purposes of passing them as arguments. It's basically a polymorphism hack so different socket types can be used with the same functions, hence the specification of family and address length in most functions. Given that IPv4 addresses are shorter than both IPv6 and Unix-socket addresses, struct sockaddr is probably the same size as struct sockaddr_in. Therefore:
  1. struct sockaddr_in6 is almost certainly not the same size as struct sockaddr_in; use each of them only for their respective protocols.
  2. Set hints.ai_addrlen.
  3. Use host_info->ai_addrlen with memcpy.
Kevin Barry
 
Old 07-25-2011, 10:25 PM   #6
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by dwhitney67 View Post
So it seems that I'm back to square one... albeit with better code.
You didn't mention fixing this line:
Quote:
Originally Posted by dwhitney67 View Post
Code:
  memcpy(&info, host_info->ai_addr, sizeof(sockaddr));
Try this:
Code:
  memcpy(&info, host_info->ai_addr, host_info->ai_addrlen);
Kevin Barry
 
Old 07-25-2011, 10:45 PM   #7
dwhitney67
Senior Member
 
Registered: Jun 2006
Location: Maryland
Distribution: Kubuntu, Fedora, RHEL
Posts: 1,541

Original Poster
Rep: Reputation: 335Reputation: 335Reputation: 335Reputation: 335
Quote:
Originally Posted by ta0kira View Post
You didn't mention fixing this line:Try this:
Code:
  memcpy(&info, host_info->ai_addr, host_info->ai_addrlen);
Kevin Barry
I had something similar; instead of host_info->ai_addrlen, I simply used the addrlen function parameter.

Anyhow, I've modified the function per your suggestion. I still get the same results as earlier.
Code:
void
Socket::fillAddress(sockaddr* addr, const socklen_t addrlen, const unsigned short port, const char* address)
{
  if (!addr)
  {
     throw SocketException("fillAddress sockaddr param cannot be NULL", EINVAL);
  }

  struct addrinfo hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family   = m_Domain;
  hints.ai_socktype = m_Type;
  hints.ai_protocol = m_Protocol;
  hints.ai_flags    = AI_PASSIVE;
  hints.ai_addrlen  = addrlen;

  struct addrinfo* host_info = 0;

  char portstr[6] = {0};
  snprintf(portstr, sizeof(portstr), "%d", port);

  if (getaddrinfo(address, portstr, &hints, &host_info) != 0  ||
      !host_info || !host_info->ai_addr || host_info->ai_family != m_Domain)
  {
    SocketException e(gai_strerror(errno), errno);
    if (host_info) freeaddrinfo(host_info);
    throw e;
  }

  memset(addr, 0, addrlen);
  memcpy(addr, host_info->ai_addr, host_info->ai_addrlen);

  freeaddrinfo(host_info);
}
And the send() function:
Code:
void
UDPSocket::send(const char* msg, size_t msgLen, const std::string& destAddr, unsigned short destPort)
{
  sockaddr* addr    = NULL;
  socklen_t addrlen = 0;

  if (m_Domain == AF_INET)
  {
    addr    = reinterpret_cast<sockaddr*>(new sockaddr_in);
    addrlen = sizeof(sockaddr_in);
  }
  else if (m_Domain == AF_INET6)
  {
    addr    = reinterpret_cast<sockaddr*>(new sockaddr_in6);
    addrlen = sizeof(sockaddr_in6);
  }
  else
  {
    throw SocketException("send() only supports AF_INET and AF_INET6", EINVAL);
  }

  fillAddress(addr, addrlen, destPort, destAddr.c_str());

  if (::sendto(m_Socket, msg, msgLen, 0, addr, addrlen) != (int) msgLen)
  {
    throw SocketException("Error occurred when attempting to send data", errno);
  }

  delete addr;
}
Note, Client sends data successfully to host at "::1" via port 9000; Server fails in its attempt to send data back to the Client at "::0.60.177.163" via port 51686.

Last edited by dwhitney67; 07-25-2011 at 10:53 PM.
 
Old 07-25-2011, 10:56 PM   #8
dwhitney67
Senior Member
 
Registered: Jun 2006
Location: Maryland
Distribution: Kubuntu, Fedora, RHEL
Posts: 1,541

Original Poster
Rep: Reputation: 335Reputation: 335Reputation: 335Reputation: 335
Damn... I just realized that my recv() method is incorrect; it assumes IPv4 and uses sockaddr_in.

It's late now, so I will need to fix this tomorrow, and then re-test.

Forget tomorrow... I got it working. Here's the revamped recv() method.
Code:
int
UDPSocket::recv(char* buffer, size_t bufferLen, std::string& sourceAddr, unsigned short& sourcePort)
{
  sockaddr* saddr = 0;
  socklen_t saddrlen = 0;

  if (m_Domain == AF_INET)
  {
    saddr    = reinterpret_cast<sockaddr*>(new sockaddr_in);
    saddrlen = sizeof(sockaddr_in);
  }
  else if (m_Domain == AF_INET6)
  {
    saddr    = reinterpret_cast<sockaddr*>(new sockaddr_in6);
    saddrlen = sizeof(sockaddr_in6);
  }
  else
  {
    throw SocketException("recv() only supports AF_INET and AF_INET6", EINVAL);
  }

  int bytesRcvd = ::recvfrom(m_Socket, buffer, bufferLen, 0, saddr, (socklen_t*) &saddrlen);

  if (bytesRcvd == -1)
  {
    throw SocketException("Error occurred when attempting to receive data", errno);
  }

  char buf[INET6_ADDRSTRLEN + 1] = {0};

  void* src = 0;

  if (m_Domain == AF_INET)
  {
    sockaddr_in* sin   = reinterpret_cast<sockaddr_in*>(saddr);
    in_addr&     iaddr = sin->sin_addr;

    src = reinterpret_cast<void*>(&iaddr);
  }
  else
  {
    sockaddr_in6* sin6  = reinterpret_cast<sockaddr_in6*>(saddr);
    in6_addr&     iaddr = sin6->sin6_addr;

    src = reinterpret_cast<void*>(&iaddr);
  }

  const char* res = inet_ntop(m_Domain, src, buf, sizeof(buf));

  const unsigned short port = (m_Domain == AF_INET ? reinterpret_cast<sockaddr_in*>(saddr)->sin_port :
                                                     reinterpret_cast<sockaddr_in6*>(saddr)->sin6_port);

  sourceAddr = (res ? res : "Source Not Available");
  sourcePort = ntohs(port);

  delete saddr;

  return bytesRcvd;
}

Kevin - Thanks for all of the help.

Last edited by dwhitney67; 07-25-2011 at 11:39 PM.
 
Old 07-25-2011, 11:53 PM   #9
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
Quote:
Originally Posted by dwhitney67 View Post
I have seen that there's a struct sockaddr_in6; how do these structures relate to struct sockaddr?
They are compatible; they can be substituted for each other. All these structures begin with the type identifier (a short int), which identifies the exact type of the socket. The length varies.

Quote:
Originally Posted by dwhitney67 View Post
Are the structures the same size?
No, and struct sockaddr is not large enough (16 bytes, I think) to hold a struct sockaddr_in6 (28 bytes, I think).

Nothing stops you from defining such a "supertype" yourself, however:
Code:
union sockaddr_any {
    struct sockaddr     any;
    struct sockaddr_in  in;
    struct sockaddr_in6 in6;
};
In your own code, you can then just use e.g.
Code:
union sockaddr_any   address;

/* As   struct sockaddr *       &(address.any)
 * As   struct sockaddr_in *    &(address.in)
 * As   struct sockaddr_in6 *   &(address.in6)
*/
The above address variable can specify either socket type, and you can add more socket types simply by extending the union. This works, because all struct sockaddr types are compatible, and union sockaddr type is guaranteed to be at least as large as the largest member type.

In your case, you could simply use the union sockaddr_any type everywhere.

Last edited by Nominal Animal; 07-25-2011 at 11:56 PM.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
LXer: Another IPv6 Crash Course For Linux: Real IPv6 Addresses, Routing, Name Services LXer Syndicated Linux News 0 04-21-2011 07:40 AM
issue while receving data on IPV6 socket using select,recvfrom nagendrar Programming 1 01-19-2010 04:28 AM
problem with recvfrom saheelahamed Linux - Networking 1 08-28-2007 03:10 PM
recvfrom in its own thread? estratos Programming 9 02-18-2006 03:26 AM
Problem in recvfrom() live_dont_exist Programming 5 05-08-2005 04:25 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 10:06 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration