diff --git a/lib/native/windows/LocalhostRetriever.dll b/lib/native/windows/LocalhostRetriever.dll new file mode 100644 index 000000000..376a981c1 Binary files /dev/null and b/lib/native/windows/LocalhostRetriever.dll differ diff --git a/src/native/windows/LocalhostRetriever/Makefile b/src/native/windows/LocalhostRetriever/Makefile new file mode 100644 index 000000000..32a1ad8fa --- /dev/null +++ b/src/native/windows/LocalhostRetriever/Makefile @@ -0,0 +1,15 @@ +## +# \file Makefile +# \brief Win32 (Visual C++) makefile. +# \author Sebastien Vincent + +CC = cl +JNI_HEADERS = /I%JAVA_HOME%\include /I%JAVA_HOME%\include\win32 +CFLAGS = $(JNI_HEADERS) + +LocalhostRetriever.dll: net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.c + $(CC) $(CFLAGS) /LD net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.c iphlpapi.lib /Fe$@ + +clean: + del *.exp *.lib *.dll *.obj + diff --git a/src/native/windows/LocalhostRetriever/net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.c b/src/native/windows/LocalhostRetriever/net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.c new file mode 100644 index 000000000..7838cfcef --- /dev/null +++ b/src/native/windows/LocalhostRetriever/net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.c @@ -0,0 +1,377 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +/** + * \file net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.c + * \brief Get source address for a destination address. + * \author Sebastien Vincent + * \date 2009 + */ + +/** + * \def WIN32_LEAN_AND_MEAN + * \brief Exclude not commonly used headers from win32 API. + * + * It excludes some unused stuff from windows headers and + * by the way code compiles faster. + */ +#define WIN32_LEAN_AND_MEAN + +/** + * \def ASSUME_COMPLETE_JDK + * \brief Assume the JDK has a correct implementation of:\n + * - byte[] InetAddress.getAddress();\n + * - void InetAddress.getByteAddress(byte[]);\n + * - RuntimeException and OutOfMemoryError classes.\n\n + * + * This will not test if GetObjectClass, GetMethodID, + * GetStaticMethodID fail for these classes. + * + * Typically it is safe to set 1 for OpenJDK and SUN JDK. + */ +#define ASSUME_COMPLETE_JDK 1 + +#include +#include + +#include + +#include +#include + +#include "net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.h" + +/** + * \define ADAPTERS_DEFAULT_SIZE + * \brief Default size for GetAdapterAddresses's SizePointer parameter. + * + * We set a relatively high value to not called two times + * GetAdapterAddresses() which is slow. + */ +#define ADAPTERS_DEFAULT_SIZE 15000 + +/** + * \brief Get interface for a destination route. + * \param addr destination address + * \param src source address, this parameter will be filled in function succeed + * \return 0 if success, -1 otherwise + */ +static int get_source_for_destination(struct sockaddr* dst, struct sockaddr_storage* src) +{ + DWORD ifindex = 0; + IP_ADAPTER_ADDRESSES* allAdapters = NULL; + IP_ADAPTER_ADDRESSES* adapter = NULL; + ULONG size = ADAPTERS_DEFAULT_SIZE; + ULONG ret = 0; + BOOL found = FALSE; + + /* need a valid pointer */ + if(!src || !dst) + { + return -1; + } + + /* get output interface index for specific destination address */ + if(GetBestInterfaceEx(dst, &ifindex) != NO_ERROR) + { + return -1; + } + + do + { + /* we should loop only if host has more than + * (ADAPTERS_DEFAULT_SIZE / sizeof(IP_ADAPTER_ADDRESSES)) interfaces + */ + allAdapters = malloc(ADAPTERS_DEFAULT_SIZE); + + if(!allAdapters) + { + /* out of memory */ + return -1; + } + + /* get the list of host addresses and try to find + * the index + */ + ret = GetAdaptersAddresses(dst->sa_family, /* return same address family as destination */ + GAA_FLAG_INCLUDE_ALL_INTERFACES | GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_MULTICAST, + NULL, /* reserved */ + allAdapters, + &size); + + if(ret == ERROR_BUFFER_OVERFLOW) + { + /* free memory as the loop will allocate again with + * proper size + */ + free(allAdapters); + } + + }while(ret == ERROR_BUFFER_OVERFLOW); + + if(ret != ERROR_SUCCESS) + { + free(allAdapters); + return -1; + } + + adapter = allAdapters; + + while(adapter) + { + /* find the right adapter for interface index return by GetBestInterface */ + if(dst->sa_family == AF_INET && (adapter->IfIndex == ifindex)) + { + IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; + struct sockaddr_in* addr = (struct sockaddr_in*)unicast->Address.lpSockaddr; + + memcpy(src, addr, sizeof(struct sockaddr_in)); + found = TRUE; + + /* found source address, break the loop */ + break; + } + else if(dst->sa_family == AF_INET6 && (adapter->Ipv6IfIndex == ifindex)) + { + /* XXX multihoming on IPv6 interfaces, they can have + * multiple global addresses (+ link-local address), handle this case + */ + IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; + struct sockaddr_in6* addr = (struct sockaddr_in6*)unicast->Address.lpSockaddr; + + memcpy(src, addr, sizeof(struct sockaddr_in6)); + found = TRUE; + + /* found source address, break the loop */ + break; + } + + adapter = adapter->Next; + } + + /* cleanup */ + free(allAdapters); + + return found ? 0 : -1; +} + +/** + * \brief JNI native method to get source address for a destination. + * \param env JVM environment + * \param class class that call method (Win32LocalhostRetriever) + * \param dst destination address (InetAddress) + * \return source address for the destination (InetAddress) + */ +JNIEXPORT jobject JNICALL Java_net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever_getSourceForDestination + (JNIEnv *env, jclass class, jobject dst) +{ + jbyteArray array = NULL; + jclass clazz = NULL; + jmethodID mid = NULL; + jsize len = 0; + jobject ret = NULL; /* InetAddress type */ + struct sockaddr_storage source; + struct sockaddr_in dstv4; + struct sockaddr_in6 dstv6; + struct sockaddr* destination = NULL; + char* buf = NULL; + + /* get class for InetAddress and appropriate method getAddress */ + clazz = (*env)->GetObjectClass(env, dst); + +#ifndef ASSUME_COMPLETE_JDK + if(!clazz) + { + /* printf("!clazz\n"); */ + return NULL; + } +#endif + + mid = (*env)->GetMethodID(env, clazz, "getAddress", "()[B"); + +#ifndef ASSUME_COMPLETE_JDK + if(!mid) + { + /* printf("!mid\n"); */ + return NULL; + } +#endif + + /* get the bytes */ + array = (*env)->CallObjectMethod(env, dst, mid); + len = (*env)->GetArrayLength(env, array); + buf = (char*)(*env)->GetByteArrayElements(env, array, NULL); + + if(len == 4) + { + /* IPv4 processing */ + dstv4.sin_family = AF_INET; + memcpy(&dstv4.sin_addr, buf, 4); + dstv4.sin_port = 0; + destination = (struct sockaddr*)&dstv4; + } + else if(len == 16) + { + /* IPv6 processing */ + dstv6.sin6_family = AF_INET6; + memcpy(&dstv6.sin6_addr, buf, 16); + dstv6.sin6_scope_id = 0; + dstv6.sin6_flowinfo = 0; + dstv6.sin6_port = 0; + destination = (struct sockaddr*)&dstv6; + } + else + { + /* printf("not an IPv4 or IPv6 address\n"); */ + jclass exception = (*env)->FindClass(env, "java/lang/RuntimeException"); + (*env)->ThrowNew(env, exception, "Not an IPv4 or IPv6 address"); + (*env)->ReleaseByteArrayElements(env, array, buf, 0); + return NULL; + } + + /* cleanup */ + (*env)->ReleaseByteArrayElements(env, array, buf, 0); + + /* create jbyteArray that will contains raw address */ + array = (*env)->NewByteArray(env, len); + if(!array) + { + jclass exception = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + (*env)->ThrowNew(env, exception, "Not enough memory"); + return NULL; + } + + /* get internal buffer */ + buf = (char*)(*env)->GetByteArrayElements(env, array, NULL); + + /* find the source address for a specific destination */ + if(get_source_for_destination(destination, &source) == -1) + { + jclass exception = (*env)->FindClass(env, "java/lang/RuntimeException"); + (*env)->ThrowNew(env, exception, "Native call failed"); + return NULL; + } + + /* copy raw address */ + if(source.ss_family == AF_INET) + { + /* IPv4 */ + memcpy(buf, &((struct sockaddr_in*)&source)->sin_addr, len); + } + else + { + /* IPv6 */ + memcpy(buf, &((struct sockaddr_in6*)&source)->sin6_addr, len); + } + + /* update content of byte array */ + (*env)->SetByteArrayRegion(env, array, 0, len, (const jbyte*)buf); + + /* construct new InetAddress object with + * static InetAddress.getByAddress(byte[]) method + */ + mid = (*env)->GetStaticMethodID(env, clazz, "getByAddress", "([B)Ljava/net/InetAddress;"); + +#ifndef ASSUME_COMPLETE_JDK + if(!mid) + { + /* printf("!mid2\n"); */ + return NULL; + } +#endif + + /* ret class is InetAddress */ + ret = (*env)->CallStaticObjectMethod(env, clazz, mid, array); + + /* cleanup */ + (*env)->ReleaseByteArrayElements(env, array, (jbyte*)buf, 0); + return ret; +} + +/* test */ +#if 0 + +#include + +int main(int argc, char** argv) +{ + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + struct sockaddr_storage st; + struct sockaddr_storage st2; + struct sockaddr_in* src4 = (struct sockaddr_in*)&st; + struct sockaddr_in6* src6 = (struct sockaddr_in6*)&st2; + char buf[64]; + DWORD len = 0; + WSADATA wsaData; + + /* initialization stuff */ + WSAStartup(MAKEWORD(2, 0), &wsaData); + + /* IPv4 destination */ + + printf("Try to find source address for destination 130.79.200.1\n"); + len = sizeof(addr4); + + if(WSAStringToAddressA("130.79.200.1", AF_INET, NULL, (struct sockaddr*)&addr4, &len) != 0) + { + printf("WSAStringToAddressA failed: %ld\n", WSAGetLastError()); + exit(EXIT_FAILURE); + } + + if(get_source_for_destination((struct sockaddr*)&addr4, &st) == 0) + { + len = sizeof(buf); + if(WSAAddressToStringA((struct sockaddr*)src4, sizeof(*src4), NULL, buf, &len) != 0) + { + printf("WSAAddressToString failed: %ld\n", WSAGetLastError()); + exit(EXIT_FAILURE); + } + + printf("Source address is %s\n", buf); + } + else + { + printf("get_source_for_destination failed for IPv4!\n"); + } + + printf("================================================================\n"); + + /* IPv6 destination */ + + printf("Try to find source address for destination 2001:660:4701:1001::3\n"); + len = sizeof(addr6); + + if(WSAStringToAddressA("2001:660:4701:1001::3", AF_INET6, NULL, (struct sockaddr*)&addr6, &len) != 0) + { + printf("WSAStringToAddressA failed: %ld\n", WSAGetLastError()); + exit(EXIT_FAILURE); + } + + if(get_source_for_destination((struct sockaddr*)&addr6, &st2) == 0) + { + len = sizeof(buf); + if(WSAAddressToStringA((struct sockaddr*)src6, sizeof(*src6), NULL, buf, &len) != 0) + { + printf("WSAAddressToString failed: %ld\n", WSAGetLastError()); + exit(EXIT_FAILURE); + } + + printf("Source address is %s\n", buf); + } + else + { + printf("get_source_for_destination failed for IPv6!\n"); + } + + + return EXIT_SUCCESS; +} + +#endif + diff --git a/src/native/windows/LocalhostRetriever/net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.h b/src/native/windows/LocalhostRetriever/net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.h new file mode 100644 index 000000000..66eea084c --- /dev/null +++ b/src/native/windows/LocalhostRetriever/net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever */ + +#ifndef _Included_net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever +#define _Included_net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever + * Method: getSourceForDestination + * Signature: (Ljava/net/InetAddress;)Ljava/net/InetAddress; + */ +JNIEXPORT jobject JNICALL Java_net_java_sip_communicator_impl_netaddr_Win32LocalhostRetriever_getSourceForDestination + (JNIEnv *, jclass, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java b/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java index 8f529385a..26d4a456b 100644 --- a/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java +++ b/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java @@ -237,19 +237,46 @@ public void stop() */ public synchronized InetAddress getLocalHost(InetAddress intendedDestination) { + InetAddress localHost = null; + if(logger.isTraceEnabled()) { logger.trace("Querying a localhost addr for dst=" + intendedDestination); } - //no point in making sure that the localHostFinderSocket is initialized. - //better let it through a NullPointerException. - InetAddress localHost = null; - localHostFinderSocket.connect(intendedDestination - , RANDOM_ADDR_DISC_PORT); - localHost = localHostFinderSocket.getLocalAddress(); - localHostFinderSocket.disconnect(); + + /* use native code (JNI) to find source address for a specific destination + * address on Windows. + * + * For other systems, we used method based on DatagramSocket.connect + * which will returns us source address. The reason why we cannot use it + * on Windows is because its socket implementation returns the any address... + */ + if(System.getProperty("os.name").startsWith("Windows")) + { + try + { +logger.info("eeeeeeeee"); + localHost = Win32LocalhostRetriever.getSourceForDestination(intendedDestination); +logger.info("eeeeeeeee 2 " + localHost); + } + catch(Exception e) + { + logger.warn("Failed to get localhost ", e); + } + } + else + { + + //no point in making sure that the localHostFinderSocket is initialized. + //better let it through a NullPointerException. + localHostFinderSocket.connect(intendedDestination, + RANDOM_ADDR_DISC_PORT); + localHost = localHostFinderSocket.getLocalAddress(); + localHostFinderSocket.disconnect(); + } +/* //windows socket implementations return the any address so we need to //find something else here ... InetAddress.getLocalHost seems to work //better on windows so lets hope it'll do the trick. @@ -340,7 +367,7 @@ public synchronized InetAddress getLocalHost(InetAddress intendedDestination) logger.warn("Failed to get localhost ", ex); } } - +*/ if(logger.isTraceEnabled()) { logger.trace("Will return the following localhost address: " diff --git a/src/net/java/sip/communicator/impl/netaddr/Win32LocalhostRetriever.java b/src/net/java/sip/communicator/impl/netaddr/Win32LocalhostRetriever.java new file mode 100644 index 000000000..985155cb9 --- /dev/null +++ b/src/net/java/sip/communicator/impl/netaddr/Win32LocalhostRetriever.java @@ -0,0 +1,48 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.netaddr; + +import java.net.*; + +/** + * Class to retrieve local address to use for a + * specific destination. + * + * @author Sebastien Vincent + */ +public class Win32LocalhostRetriever +{ + /* load library */ + static + { + System.out.println("daaaaaaaaaaaaaaaaaaaaa 1"); + System.loadLibrary("LocalhostRetriever"); + System.out.println("daaaaaaaaaaaaaaaaaaaaa 2"); + } + + /** + * Constructor. + */ + public Win32LocalhostRetriever() + { + } + + /** + * Native function to retrieve source address to use for a specific destination. + * @param dst destination address + * @return source address or null if error + * @note This function is only implemented for Microsoft Windows (>= XP SP1). + * Do not try to call it from another OS. + * @throws RuntimeException if dst is not an IPv4 or IPv6 address, and if + * native get_source_for_destination function failed + * @throws OutOfMemoryError if run out of memory + */ + public native static InetAddress getSourceForDestination(InetAddress dst) throws + RuntimeException, + OutOfMemoryError; +} +