Hi,

On Sun, Jan 19, 2014 at 03:04:17PM +0100, Gert Doering wrote:
> In case you want to test yourself, the code is appended.  It's not pretty,
> but gets the job done :-)

Here's the latest iteration of the code.  The changes compared to
openvpn's socket.c are actually bigger than I expected, as what we
have seems to be completely broken on some platforms due to structure
size/alignment issues...

The current version works on everything I have access to (except MacOS),
both 32bit and 64bit.  I'll eventually merge that back into OpenVPN proper,
and send a patch for that...

What it still confirms is that *some* platforms just do not send 
IP6_PKTINFO for IPv4-mapped platforms, notably

 - Linux
 - FreeBSD up to 8.x (7.4, 8.2 do not, 9.1 does)
 - OpenBSD (no dual-stacked sockets, no ipv4-mapped packets anywhere)

OTOH, NetBSD 5.1 and FreeBSD 9+ get it right, and this code will handle
it correctly.

Next step: find someone who understands kernel code on Linux :-)

gert


-- 
USENET is *not* the non-clickable part of WWW!
                                                           //www.muc.de/~gert/
Gert Doering - Munich, Germany                             g...@greenie.muc.de
fax: +49-89-35655025                        g...@net.informatik.tu-muenchen.de
/*
 *  mhome.c - test program for multihoming server tests
 *
 *  open UDP socket on port specified on command line, bind IPv4 or IPv6
 *  to it, log all incoming packets and reply, hopefully with the right
 *  source address
 *
 *  largely based on OpenVPN sources to test those modules
 *
 *  v3: FreeBSD 9.2/amd64: padding for openvpn_in6_pktinfo added,
 *      (needed to get non-corrupt ifindex on reception + sending)
 *      patch from #327 applied, slightly adjusted, works
 *
 *  v4: FreeBSD IPv4 code extended with ASSERT() check
 *      always include padding, get recvmsg()/sendmsg() right by using
 *      CMSG_SPACE()/CMSG_LEN() macros (RFC2292)
 *      works on FreeBSD 9.1/amd64, FreeBSD 8.3/i386 (not for v4-mapped)
 *
 *  v5: Linux IPv4 code changed to CMSG_SPACE/CMSG_LEN, works on 64 bit now
 *      (but still no v4-mapped for IPv6)
 *      works on OpenBSD 4.9/i386, OpenBSD 5.4/amd64 (no dual-stack sockets),
 *      NetBSD 5.1/amd64 for IPv6 (with v4-mapped!)
 *         getnameinfo() on NetBSD fails (sa->sa_len not filled in)
 */

#define ENABLE_IP_PKTINFO 1

#ifdef linux
/* needed for netinet/in.h */
#define _GNU_SOURCE 1           
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <net/if.h>

#include <stdbool.h>
#include <assert.h>
#define ASSERT(x) assert(x)

#define CLEAR(x) memset(&(x), 0, sizeof(x))

/* IP_PKTINFO or IP_RECVDSTADDR are defined in <netinet/in.h> on BSDs
 * and Linux just fine, but our configure/ifdef logic is weird */
#ifdef IP_PKTINFO
# define HAVE_IN_PKTINFO 1
#endif

#define msg(a,b) fprintf(stderr,b "\n")

/* glue definitions, copied from syshead.h or socket.h */
typedef int socket_descriptor_t;
# define SF_USE_IP_PKTINFO (1<<0)

#define PS_SHOW_PORT_IF_DEFINED (1<<0)
#define PS_SHOW_PORT            (1<<1)
#define PS_SHOW_PKTINFO         (1<<2)
#define PS_DONT_SHOW_ADDR       (1<<3)

/* OpenVPN sockaddr struct */
struct openvpn_sockaddr
{
  union {
    struct sockaddr sa;
    struct sockaddr_in in4;
    struct sockaddr_in6 in6;
  } addr;
};

struct link_socket_actual
{
 int ai_family; /* PF_xxx */
 int ai_socktype;       /* SOCK_xxx */
 int ai_protocol;       /* 0 or IPPROTO_xxx for IPv4 and IPv6 */

  struct openvpn_sockaddr dest;
#if ENABLE_IP_PKTINFO
  union {
#ifdef HAVE_IN_PKTINFO
    struct in_pktinfo in4;
#elif defined(IP_RECVDSTADDR)
    struct in_addr in4;
#endif
    struct in6_pktinfo in6;
  } pi;
#endif
};

/*
 * Format IP addresses in ascii
 *
 * (hacked-together non-safe version of the version in openvpn)
 */
const char *
print_sockaddr_ex (const struct sockaddr *sa,
                                   const char* separator,
                                   const unsigned int flags )
{
  static char out[1000];
  bool addr_is_defined;
  char hostaddr[NI_MAXHOST] = "";
  char servname[NI_MAXSERV] = "";
  int status;

  socklen_t salen;
  switch(sa->sa_family)
    {
    case AF_INET:
      strcpy(out, "[AF_INET]");
      salen = sizeof (struct sockaddr_in);
      addr_is_defined = ((struct sockaddr_in*) sa)->sin_addr.s_addr != 0;
      break;
    case AF_INET6:
      strcpy(out, "[AF_INET6]");
      salen = sizeof (struct sockaddr_in6);
      addr_is_defined = !IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*) 
sa)->sin6_addr);
      break;
    case AF_UNSPEC:
      return "[AF_UNSPEC]";
    default:
      ASSERT(0);
    }

  status = getnameinfo(sa, salen, hostaddr, sizeof (hostaddr),
              servname, sizeof(servname), NI_NUMERICHOST | NI_NUMERICSERV);

  if(status!=0) {
      sprintf(&out[strlen(out)],"[nameinfo() err: %s]",gai_strerror(status));
      return out;
  }

  if (!(flags & PS_DONT_SHOW_ADDR))
    {
      if (addr_is_defined)
        strcat (out, hostaddr);
      else
        strcat (out, "[undef]");
    }

  if ((flags & PS_SHOW_PORT) || (flags & PS_SHOW_PORT_IF_DEFINED))
    {
      if (separator)
        strcat (out, separator);

      strcat (out, servname);
    }

  return out;
}

/* ------- mostly unchanged code from socket.c below this line ----- */

static socket_descriptor_t
create_socket_udp (const int af, const unsigned int flags)
{
  socket_descriptor_t sd;

  if ((sd = socket (af, SOCK_DGRAM, IPPROTO_UDP)) < 0)
    msg (M_ERR, "UDP: Cannot create UDP/UDP6 socket");
#if ENABLE_IP_PKTINFO
  else if (flags & SF_USE_IP_PKTINFO)
    {
      int pad = 1;
      if(af == AF_INET)
        {
#ifdef IP_PKTINFO
          fprintf( stderr, "AF_INET/IP_PKTINFO enabled\n");
          if (setsockopt (sd, SOL_IP, IP_PKTINFO,
                          (void*)&pad, sizeof(pad)) < 0)
            msg(M_ERR, "UDP: failed setsockopt for IP_PKTINFO");
#elif defined(IP_RECVDSTADDR)
          fprintf( stderr, "AF_INET/IP_RECVDSTADDR enabled\n");
          if (setsockopt (sd, IPPROTO_IP, IP_RECVDSTADDR,
                          (void*)&pad, sizeof(pad)) < 0)
            msg(M_ERR, "UDP: failed setsockopt for IP_RECVDSTADDR");
#else
#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix 
syshead.h)
#endif
        }
      else if (af == AF_INET6 )
        {
#ifndef IPV6_RECVPKTINFO /* Some older Darwin platforms require this */
          fprintf( stderr, "AF_INET6/IPV6_PKTINFO enabled\n");
          if (setsockopt (sd, IPPROTO_IPV6, IPV6_PKTINFO,
                          (void*)&pad, sizeof(pad)) < 0)
#else
          fprintf( stderr, "AF_INET6/IPV6_RECVPKTINFO enabled\n");
          if (setsockopt (sd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
                          (void*)&pad, sizeof(pad)) < 0)
#endif
            msg(M_ERR, "UDP: failed setsockopt for IPV6_RECVPKTINFO");
        }
    }
#endif
  return sd;
}


void
socket_bind (socket_descriptor_t sd,
             char* port, int ai_family)
{
  struct addrinfo hints;
  struct addrinfo* cur;

  CLEAR(hints);
  hints.ai_family = ai_family;
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_protocol = IPPROTO_UDP;
  hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;

  int s = getaddrinfo( NULL, port, &hints, &cur );

  if ( s != 0 )
        { fprintf( stderr, "getaddrinfo failed: %s (%d)\n",
                        gai_strerror(s), s ); exit(1); }

  if (ai_family == AF_INET6)
    {
      int v6only = 0;

      fprintf (stderr, "setsockopt(IPV6_V6ONLY=%d)\n", v6only);
      if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)))
        {
          fprintf (stderr, "Setting IPV6_V6ONLY=%d failed\n", v6only);
        }
    }
  if (bind (sd, cur->ai_addr, cur->ai_addrlen))
    {
      const int err = errno;
      fprintf (stderr, "Socket bind failed on local address %s: %s\n",
           print_sockaddr_ex (cur->ai_addr, ":", PS_SHOW_PORT),
           strerror(err));
      exit(1);
    }
  fprintf (stderr, "Socket bound to local address %s\n",
       print_sockaddr_ex (cur->ai_addr, ":", PS_SHOW_PORT) );
}

const char *
print_link_socket_actual_ex (const struct link_socket_actual *act,
                             const char *separator,
                             const unsigned int flags)
{
  static char out[1000];

  if (act)
    {
      char ifname[IF_NAMESIZE] = "[undef]";
      strcpy (out, print_sockaddr_ex (&act->dest.addr.sa, separator, flags));
#if ENABLE_IP_PKTINFO
      if ((flags & PS_SHOW_PKTINFO)) /* && addr_defined_ipi(act))*/
        {
          switch(act->dest.addr.sa.sa_family)
            {
            case AF_INET:
                {
                  struct openvpn_sockaddr sa;
                  CLEAR (sa);
                  sa.addr.in4.sin_family = AF_INET;
#ifdef IP_PKTINFO
                  sa.addr.in4.sin_addr = act->pi.in4.ipi_spec_dst;
                  if_indextoname(act->pi.in4.ipi_ifindex, ifname);
#elif defined(IP_RECVDSTADDR)
                  sa.addr.in4.sin_addr = act->pi.in4;
                  ifname[0]=0;
#else
#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix 
syshead.h)
#endif
                  sprintf (&out[strlen(out)], " (via %s%%%s)",
                              print_sockaddr_ex (&sa.addr.sa, separator, 0),
                              ifname);
                }
              break;
            case AF_INET6:
                {
                  struct sockaddr_in6 sin6;
                  char buf[INET6_ADDRSTRLEN] = "[undef]";
                  CLEAR(sin6);
                  sin6.sin6_family = AF_INET6;
                  sin6.sin6_addr = act->pi.in6.ipi6_addr;
                  if_indextoname(act->pi.in6.ipi6_ifindex, ifname);
                  if (getnameinfo((struct sockaddr *)&sin6, sizeof (struct 
sockaddr_in6),
                                  buf, sizeof (buf), NULL, 0, NI_NUMERICHOST) 
== 0)
                    sprintf (&out[strlen(out)], " (via %s%%%s [%u])", buf, 
ifname, act->pi.in6.ipi6_ifindex);
                  else
                    sprintf (&out[strlen(out)], " (via [getnameinfo() 
err]%%%s)", ifname);
                }
              break;
            }
        }
#endif
      return out;
    }
  else
    return "[NULL]";
}

struct openvpn_in4_pktinfo
{
  struct cmsghdr cmsghdr;
#ifdef HAVE_IN_PKTINFO
  struct in_pktinfo pi4;
#elif defined(IP_RECVDSTADDR)
  struct in_addr pi4;
#endif
};
struct openvpn_in6_pktinfo
{
  struct cmsghdr cmsghdr; 
uint32_t padding;
  struct in6_pktinfo pi6;
};

union openvpn_pktinfo {
        struct openvpn_in4_pktinfo msgpi4;
        struct openvpn_in6_pktinfo msgpi6;
};
#pragma pack()


static socklen_t
link_socket_read_udp_posix_recvmsg (socket_descriptor_t sd,
                                    char *buf,
                                    int maxsize,
                                    struct link_socket_actual *from,
                                    int *r_len)
{
  struct iovec iov;
  union openvpn_pktinfo opi;
  struct msghdr mesg;

  socklen_t fromlen = sizeof (from->dest.addr);

  iov.iov_base = buf;
  iov.iov_len = maxsize;
  mesg.msg_iov = &iov;
  mesg.msg_iovlen = 1;
  mesg.msg_name = &from->dest.addr;
  mesg.msg_namelen = fromlen;
  mesg.msg_control = &opi;
  mesg.msg_controllen = sizeof opi;

  *r_len = recvmsg (sd, &mesg, 0);
  if (*r_len >= 0)
    {
      struct cmsghdr *cmsg;
      fromlen = mesg.msg_namelen;
      cmsg = CMSG_FIRSTHDR (&mesg);
      if (cmsg == NULL)
        fprintf( stderr, "CMSG_FIRSTHDR()=NULL, no packet info available\n");
      else
        fprintf( stderr, "CMSG_NXTHDR=%p, level=%d, type=%d\n",
                 CMSG_NXTHDR (&mesg, cmsg), cmsg->cmsg_level, cmsg->cmsg_type );
      if (cmsg != NULL
          && CMSG_NXTHDR (&mesg, cmsg) == NULL
#ifdef IP_PKTINFO
          && cmsg->cmsg_level == SOL_IP 
          && cmsg->cmsg_type == IP_PKTINFO
          && cmsg->cmsg_len >= CMSG_LEN (sizeof(struct in_pktinfo)) )
#elif defined(IP_RECVDSTADDR)
          && cmsg->cmsg_level == IPPROTO_IP
          && cmsg->cmsg_type == IP_RECVDSTADDR
          && cmsg->cmsg_len >= CMSG_LEN (sizeof(struct in_addr)) )
#else
#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix 
syshead.h)
#endif
        {
#ifdef IP_PKTINFO
          struct in_pktinfo *pkti = (struct in_pktinfo *) CMSG_DATA (cmsg);
          from->pi.in4.ipi_ifindex = pkti->ipi_ifindex;
          from->pi.in4.ipi_spec_dst = pkti->ipi_spec_dst;
          fprintf( stderr, "IP_PKTINFO\n" );
#elif defined(IP_RECVDSTADDR)
          from->pi.in4 = *(struct in_addr*) CMSG_DATA (cmsg);
          fprintf( stderr, "IP_RECVDSTADDR\n" );
#else
#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix 
syshead.h)
#endif
        }
      else if (cmsg != NULL
          && CMSG_NXTHDR (&mesg, cmsg) == NULL
          && cmsg->cmsg_level == IPPROTO_IPV6 
          && cmsg->cmsg_type == IPV6_PKTINFO
          && cmsg->cmsg_len >= CMSG_LEN(sizeof(struct in6_pktinfo)) )
        {
          struct in6_pktinfo *pkti6 = (struct in6_pktinfo *) CMSG_DATA (cmsg);
          from->pi.in6.ipi6_ifindex = pkti6->ipi6_ifindex;
          from->pi.in6.ipi6_addr = pkti6->ipi6_addr;
          fprintf( stderr, "IPV6_PKTINFO\n" );
        }
    }
  return fromlen;
}


size_t
link_socket_write_udp_posix_sendmsg (socket_descriptor_t sd,
                                     char *buf, int len,
                                     struct link_socket_actual *to)
{
  struct iovec iov;
  struct msghdr mesg;
  struct cmsghdr *cmsg;
  union openvpn_pktinfo opi;

  iov.iov_base = buf;
  iov.iov_len = len;
  mesg.msg_iov = &iov;
  mesg.msg_iovlen = 1;
  switch (to->ai_family)
    {
    case AF_INET:
      {
        mesg.msg_name = &to->dest.addr.sa;
        mesg.msg_namelen = sizeof (struct sockaddr_in);
        mesg.msg_control = &opi;
        mesg.msg_flags = 0;
#ifdef HAVE_IN_PKTINFO
        mesg.msg_controllen = CMSG_SPACE (sizeof (struct in_pktinfo));
        cmsg = CMSG_FIRSTHDR (&mesg);
        cmsg->cmsg_len = CMSG_LEN (sizeof(struct in_pktinfo));
        cmsg->cmsg_level = SOL_IP;
        cmsg->cmsg_type = IP_PKTINFO;
        {
        struct in_pktinfo *pkti;
        pkti = (struct in_pktinfo *) CMSG_DATA (cmsg);
        pkti->ipi_ifindex = to->pi.in4.ipi_ifindex;
        pkti->ipi_spec_dst = to->pi.in4.ipi_spec_dst;
        pkti->ipi_addr.s_addr = 0;
        }
#elif defined(IP_RECVDSTADDR)
        ASSERT( CMSG_SPACE(sizeof (struct in_addr)) <= sizeof(opi) );
        mesg.msg_controllen = CMSG_SPACE(sizeof (struct in_addr));
        cmsg = CMSG_FIRSTHDR (&mesg);
        cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
        cmsg->cmsg_level = IPPROTO_IP;
        cmsg->cmsg_type = IP_RECVDSTADDR;
        *(struct in_addr *) CMSG_DATA (cmsg) = to->pi.in4;
#else
#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix 
syshead.h)
#endif
        break;
      }
    case AF_INET6:
      {
        struct in6_pktinfo *pkti6;
        mesg.msg_name = &to->dest.addr.sa;
        mesg.msg_namelen = sizeof (struct sockaddr_in6);
        mesg.msg_control = &opi;
        mesg.msg_controllen = CMSG_SPACE(sizeof (struct in6_pktinfo));
        mesg.msg_flags = 0;
        cmsg = CMSG_FIRSTHDR (&mesg);
        cmsg->cmsg_len = CMSG_LEN(sizeof (struct in6_pktinfo));
        cmsg->cmsg_level = IPPROTO_IPV6;
        cmsg->cmsg_type = IPV6_PKTINFO;
        pkti6 = (struct in6_pktinfo *) CMSG_DATA (cmsg);
        fprintf( stderr, "control=%p, controllen=%d, pkti6=%p, struct 
cmsghdr=%d bytes\n", 
                        mesg.msg_control, (int)mesg.msg_controllen, 
                        (void*)pkti6, (int)sizeof(struct cmsghdr));
        pkti6->ipi6_ifindex = to->pi.in6.ipi6_ifindex;
        pkti6->ipi6_addr = to->pi.in6.ipi6_addr;
        break;
      }
    default: ASSERT(0);
    }
  return sendmsg (sd, &mesg, 0);
}


void loop(int sd,int af)
{
    int r_len, fromlen, r;
    char buf[10000];
    struct link_socket_actual from;

    while(1)
    {
        CLEAR(from);
        fprintf( stderr, "--\n" );
        fromlen = link_socket_read_udp_posix_recvmsg (sd, buf, sizeof(buf), 
&from, &r_len);
        fprintf( stderr, "read: fromlen=%d, r_len=%d\n", fromlen, r_len );
        fprintf( stderr, "  from=%s\n",
           print_link_socket_actual_ex(&from, ":", 
PS_SHOW_PORT|PS_SHOW_PKTINFO) );

        /* send back response (just mirror packet) */
        from.ai_family = af;
        r = link_socket_write_udp_posix_sendmsg(sd, buf, r_len, &from);
        if ( r<0 )
            fprintf( stderr, "sendmsg() returns r=%d: %s\n", r, strerror(errno) 
);
    }
}

int main(int argc, char **argv)
{
    int s;
    int af = AF_INET6;
    char* port = "50001";

    
    if ( argc > 1 && argv[1][0] == '4' ) { af = AF_INET; }
    if ( argc > 2 )                      { port = argv[2]; }

    s = create_socket_udp( af, SF_USE_IP_PKTINFO );
    socket_bind( s, port, af );

    loop(s,af);
    exit(0);
}

Attachment: pgpM8y2FXYLrN.pgp
Description: PGP signature

Reply via email to