Monday, August 20, 2012

IPv6 in Perl on Windows

The state of IPv6 support in Perl has long been lamented but there are those trying to change this including Paul Evans who's updating many of the networking modules - including the Socket core module - to support IPv6. It's a shame the C compiler shipping with Strawberry Perl still doesn't accommodate IPv6 on Windows.

I've been using Strawberry Perl after switching from ActiveState since version 5.10. I found myself needing more and more XS modules and the ActiveState PPM process was always lagging behind. I had a copy of MinGW gcc - the free Windows port of the 'gcc' compiler - and with 'dmake', I could build my own modules directly from CPAN. However, I had some issues and when I learned about Strawberry Perl including the gcc compiler in the distribution, I switched. I've used Strawberry Perl on Windows XP 32-bit and now Windows 7 64-bit.

I've also recently been doing a lot of IPv6 work and using Perl as a quick development platform to test the IPv6 protocol proved easy with the Net::Frame modules detailed in an early series of blog posts. However, when looking to create sockets with Perl, I found errors, most notably:

Socket::inet_ntop not implemented on this architecture

The lack of this function prevented the display of IPv6 addresses and it's missing complement - inet_pton - prevents the resolution of IPv6. These functions are certainly present in Windows 7.

The issue lies in the header and library files distributed with MinGW gcc compiler in the Strawberry Perl release. They simply don't identify the functions in the headers, nor provide linkage to them in the libraries. Fortunately, tools shipped with the MinGW gcc compiler in Strawberry Perl can be used to remedy the issue and get full IPv6 functionality. Here's how.

First off, we need to update the header file with the function prototypes. I did this by creating a new header file called 'ws2tcpip-win.h':


#ifndef WS2TCPIP_WIN_H
#define WS2TCPIP_WIN_H

typedef USHORT ADDRESS_FAMILY;

#if (NTDDI_VERSION >= NTDDI_VISTA)
WINSOCK_API_LINKAGE INT WSAAPI inet_pton(INT, PCSTR, PVOID);
WINSOCK_API_LINKAGE INT WSAAPI InetPtonW(INT, PCWSTR, PVOID);

WINSOCK_API_LINKAGE PCSTR WSAAPI inet_ntop(INT, PVOID, PSTR, size_t);
WINSOCK_API_LINKAGE PCWSTR WSAAPI InetNtopW(INT, PVOID, PWSTR, size_t);

#define InetPtonA       inet_pton
#define InetNtopA       inet_ntop

#ifdef UNICODE
#define InetPton        InetPtonW
#define InetNtop        InetNtopW
#else
#define InetPton        InetPtonA
#define InetNtop        InetNtopA
#endif

#if INCL_WINSOCK_API_TYPEDEFS
typedef INT (WSAAPI * LPFN_INET_PTONA)(INT Family, PCSTR pszAddrString, PVOID pAddrBuf);
typedef INT (WSAAPI * LPFN_INET_PTONW)(INT Family, PCWSTR pszAddrString, PVOID pAddrBuf);

typedef PCSTR (WSAAPI * LPFN_INET_NTOPA)(INT Family, PVOID pAddr, PSTR pStringBuf, size_t StringBufSize);
typedef PCWSTR (WSAAPI * LPFN_INET_NTOPW)(INT Family, PVOID pAddr, PWSTR pStringBuf, size_t StringBufSize);

#ifdef UNICODE
#define LPFN_INET_PTON          LPFN_INET_PTONW
#define LPFN_INET_NTOP          LPFN_INET_NTOPW
#else
#define LPFN_INET_PTON          LPFN_INET_PTONA
#define LPFN_INET_NTOP          LPFN_INET_NTOPA
#endif

#endif  //  TYPEDEFS
#endif  //  (NTDDI_VERSION >= NTDDI_VISTA)

#endif

Next, copy that file to the appropriate location:

copy ws2tcpip-win.h c:\strawberry\c\x86_64-w64-mingw32\include

and update the existing 'ws2tcpip.h' file in that location with the following:

#include <ws2tcpip-win.h>

Now we need to create the library. This is done by first exporting the symbols from the appropriate DLL file - in this case, ws2_32.dll - and then creating the link library. The version of the MinGW gcc compiler with Strawberry Perl has 'pexports' included, so simply create the DEF file:

pexports c:\windows\system32\ws2_32.dll > ws2_32.def

Now create the library:

dlltool --as-flags=--64 -m i386:x86-64 -k --output-lib libws2_32-new.a --input-def WS2_32.def

Then put the new library in the appropriate location:

copy libws2_32-new.a c:\strawberry\c\x86_64-w64-mingw32\lib

Now we're ready to go. Get the latest release of Socket from CPAN, unzip and extract the tar file. Change into the directory and start the build process:

perl Makefile.PL

Now, before doing the 'dmake', edit the generated Makefile. Find the "DEFINE =" and "LDLOADLIBS =" lines and update them as per below:

DEFINE = -DHAS_GETADDRINFO -DHAS_GETNAMEINFO -DHAS_SOCKADDR_IN6 -DHAS_IP_MREQ -DHAS_IP_MREQ_SOURCE -DHAS_IPV6_MREQ -DHAS_INETNTOP -DHAS_INETPTON -DAF_INET6

LDLOADLIBS = -lmoldname -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -lnetapi32 -luuid -lws2_32 -lmpr -lwinmm -lversion -lodbc32 -lodbccp32 -lcomctl32 -lws2_32-new

Finally, 'dmake', 'dmake test' and 'dmake install' and you're good to go with IPv6!

4 comments :

LeoNerd said...

This seems a little awkward. Is there anything that could be done in Socket itself, that might make this simpler for people?

Vince said...

Wow, Paul, thanks for the comments. I agree it is awkward, but I'm not sure of another way since the problem seems to lie in the GCC distribution shipping with Strawberry. Windows XP does not support inet_ntop/pton() in ws2_32.dll but Windows 7 does. So, even if there was a way, Windows 7 users still couldn't get it to work because the GCC headers and link libraries (ws2tcpip.h and libws2_32.a) don't have the inet_ntop/pton() definitions.

Perhaps a check in the Socket Makefile.PL to look for version:

if ($^O eq "MSWin32") {
my $ver = `ver`;
$ver =~ /Version ([\d\.]+)/;
$ver = $1;
}


That would give you a difference between Windows XP and Windows 7 if for sometime in the future, the GCC distribution distinguishes with compiler directives. Otherwise, it would at least tell you Socket is being compiled on Windows and you'll need to include your own support for inet_ntop/pton() - like for example Socket6 Perl module with inet_ntop.c and inet_pton.c - and conditionally compile that in.

Unknown said...

Thanks for the fix, Vince.
I just compiled and am no longer getting the IPv6 related perl errors when using LWP::UserAgent and/or LWP::Simple to get web pages.
Now I get HTTP error 500 - bad hostname.

AM I missing something?

Vince said...

HTTP 500 error messages should be sent by the server so it may be a server configuration issue or perhaps the way the IPv6 address / hostname are configured in DNS generates that error.

 

Copyright © VinsWorld. All Rights Reserved.