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 [email protected]
fax: +49-89-35655025 [email protected]
/*
* 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);
}
pgpM8y2FXYLrN.pgp
Description: PGP signature
