eServerSocket: fix remote client's address being garbage
authorErik Slagter <erik@openpli.org>
Thu, 17 Dec 2015 19:34:02 +0000 (20:34 +0100)
committerErik Slagter <erik@openpli.org>
Thu, 17 Dec 2015 19:34:02 +0000 (20:34 +0100)
when we're on a dualstack host.

Two issues fixed:
 - If we're going to request a client's remote address from accept()
   and we supply a sockaddr to store it, the size will incidently
   suffice for an ipv4 address, but an ipv6 address will not fit and
   result in garbage and accept() will not mention it, no error.
   Fix: use a sockaddr_in6 instead (well actually use a union
   that contains all of sockaddr, sockaddr_in and sockaddr_in6, to
   be always safe).
 - inet_ntop() is not socket family agnostic. You cannot supply it
   the generic sa_addr member from sockaddr, the offset is wrong
   for both ipv4 and ipv6. Fix: use specific code for PF_LOCAL,
   PF_INET and PF_INET6 to supply inet_ntop the correct struct
   offset. This also prevents ugly static pointer casts that often
   introduce these sorts of bugs and the compiler not complaining
   about it.

Also added translation from ::ffff:a.b.c.d (ipv4 mapped address
in ipv6 socket address) to a simple  a.b.c.d which I think is more
appropriate for most users.

lib/network/serversocket.cpp

index 14cfb2a..3e93ec7 100644 (file)
@@ -12,7 +12,13 @@ void eServerSocket::notifier(int)
 {
        int clientfd;
        socklen_t clientlen;
-       struct sockaddr client_addr;
+       union // ugly workaround for sizeof(sockaddr) < sizeof(sockaddr_in6) issue
+       {
+               sockaddr sock;
+               sockaddr_in sock_in;
+               sockaddr_in6 sock_in6;
+       } client_addr;
+
        char straddr[INET6_ADDRSTRLEN];
 
 #ifdef DEBUG_SERVERSOCKET
@@ -20,21 +26,49 @@ void eServerSocket::notifier(int)
 #endif
 
        clientlen = sizeof(client_addr);
-       clientfd = accept(getDescriptor(), &client_addr, &clientlen);
+       clientfd = accept(getDescriptor(), &client_addr.sock, &clientlen);
        if (clientfd < 0)
        {
                eDebug("[eServerSocket] error on accept: %m");
                return;
        }
 
-       if (client_addr.sa_family == AF_LOCAL)
+       switch(client_addr.sock.sa_family)
        {
-               strRemoteHost = "(local)";
-       }
-       else
-       {
-               strRemoteHost = inet_ntop(client_addr.sa_family, client_addr.sa_data, straddr, sizeof(straddr));
+               case(PF_LOCAL):
+               {
+                       strRemoteHost = "(local)";
+                       break;
+               }
+
+               case(PF_INET):
+               {
+                       strRemoteHost = inet_ntop(PF_INET, &client_addr.sock_in.sin_addr, straddr, sizeof(straddr));
+                       break;
+               }
+
+               case(PF_INET6):
+               {
+                       static uint8_t ipv4_mapped_pattern[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff };
+
+                       if(!memcmp(&client_addr.sock_in6.sin6_addr, ipv4_mapped_pattern, sizeof(ipv4_mapped_pattern)))
+                       {
+                                // ugly hack to get real ipv4 address without the ::ffff:, inet_ntop doesn't have an option for it
+                               strRemoteHost = inet_ntop(PF_INET, (sockaddr_in *)&client_addr.sock_in6.sin6_addr.s6_addr[12], straddr, sizeof(straddr));
+                       }
+                       else
+                               strRemoteHost = inet_ntop(PF_INET6, &client_addr.sock_in6.sin6_addr, straddr, sizeof(straddr));
+
+                       break;
+               }
+
+               default:
+               {
+                       strRemoteHost = "(error)";
+                       break;
+               }
        }
+
        newConnection(clientfd);
 }