/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.netaddr; import java.io.*; import java.net.*; import java.util.*; import com.sun.jna.*; import com.sun.jna.ptr.*; /** * Utility class to lookup the local source address of a UDP socket on BSD-like * platforms (i.e. @{link sockaddr_in} has the sin_len member) using * JNA. Other platforms (including Windows) could be used by introducing new * {@link sockaddr} subclasses without the len member. * * @author Ingo Bauersachs */ public class BsdLocalhostRetriever { /** * JNA interface to LibC. */ public static interface LibC extends Library { static final LibC INSTANCE = (LibC) Native.loadLibrary("c", LibC.class); public static final int AF_INET = 2; public static final int AF_INET6 = 30; public static final int SOCK_DGRAM = 2; // see the man pages for the mapped C functions int socket(int domain, int type, int protocol); int connect(int s, sockaddr name, int namelen); int getsockname(int fd, sockaddr addr, IntByReference len); int close(int fd); String strerror(int error); public static class CException extends IOException { private static final long serialVersionUID = 2988769925077172885L; public CException() { super(LibC.INSTANCE.strerror(Native.getLastError())); } } } /** * Corresponds to the C structure sockaddr with some utility functions. */ public abstract static class sockaddr extends Structure { /** * Creates a C sockaddr_in or sockaddr_in6 based on the type of the * passed socket address. * * @param socket The socket address to map. * @return The mapped socket address. */ public static sockaddr create(InetSocketAddress socket) { InetAddress address = socket.getAddress(); if (address instanceof Inet4Address) { sockaddr_in v4 = new sockaddr_in(); v4.sin_addr = address.getAddress(); v4.sin_port = (short) socket.getPort(); v4.sin_len = (byte) Native.getNativeSize(v4.getClass(), v4); return v4; } else if (address instanceof Inet6Address) { sockaddr_in6 v6 = new sockaddr_in6(); v6.sin6_addr = address.getAddress(); v6.sin6_port = (short) socket.getPort(); v6.sin6_len = (byte) Native.getNativeSize(v6.getClass(), v6); return v6; } else { throw new IllegalArgumentException("Unsupported address type"); } } /** * Gets the socket type. * @return {@link LibC#AF_INET} or {@link LibC#AF_INET6}. */ public abstract int getFamily(); /** * Creates a new sockaddr instance of the same type. * @return {@link sockaddr_in} or {@link sockaddr_in6} */ public abstract sockaddr createEmpty(); /** * Gets an {@link InetAddress} from the sin[6]_addr member. * * @return an {@link InetAddress} with only the address set. * @throws UnknownHostException When the data in the structure does not * represent a valid IPv4 or IPv6 address. */ public abstract InetAddress getAddress() throws UnknownHostException; /** * Gets the native size of this structure. * @return the native size of this structure. */ public int getSize() { return Native.getNativeSize(this.getClass(), this); } } /** * JNA mapping of sockaddr_in. */ public final static class sockaddr_in extends sockaddr { public byte sin_len; public byte sin_family; public short sin_port; public byte[] sin_addr; public byte[] sin_zero; public sockaddr_in() { sin_family = LibC.AF_INET; sin_addr = new byte[4]; sin_zero = new byte[8]; } @Override public sockaddr createEmpty() { sockaddr_in v4 = new sockaddr_in(); v4.sin_len = (byte) Native.getNativeSize(v4.getClass(), v4); return v4; } @Override public InetAddress getAddress() throws UnknownHostException { return InetAddress.getByAddress(sin_addr); } @Override public int getFamily() { return LibC.AF_INET; } @Override protected List getFieldOrder() { return Arrays.asList( new String[] { "sin_len", "sin_family", "sin_port", "sin_addr", "sin_zero" }); } } /** * JNA mapping of sockaddr_in6 */ public final static class sockaddr_in6 extends sockaddr { public byte sin6_len; public byte sin6_family; public short sin6_port; public int sin6_flowinfo; public byte[] sin6_addr; public int sin6_scope_id; public sockaddr_in6() { sin6_family = LibC.AF_INET6; sin6_addr = new byte[16]; } @Override public sockaddr createEmpty() { sockaddr_in6 v6 = new sockaddr_in6(); v6.sin6_len = (byte) Native.getNativeSize(v6.getClass(), v6); return v6; } @Override public InetAddress getAddress() throws UnknownHostException { return InetAddress.getByAddress(sin6_addr); } @Override public int getFamily() { return LibC.AF_INET6; } @Override protected List getFieldOrder() { return Arrays.asList( new String[] { "sin6_len", "sin6_family", "sin6_port", "sin6_flowinfo", "sin6_addr", "sin6_scope_id" }); } } /** * Gets the local address of a UDP socket that connects to the given * address. * * @param dest The destination to which the socket connects. * @return The local address of the connecting socket. * @throws IOException When the socket cannot be created, connected or * returns an invalid local address. */ public static InetAddress getLocalSocketAddress(InetSocketAddress dest) throws IOException { sockaddr addr = sockaddr.create(dest); int fd = LibC.INSTANCE.socket(addr.getFamily(), LibC.SOCK_DGRAM, 0); if (fd == -1) { throw new LibC.CException(); } try { int err = LibC.INSTANCE.connect(fd, addr, addr.getSize()); if (err != 0) { throw new LibC.CException(); } sockaddr local = addr.createEmpty(); IntByReference size = new IntByReference(local.getSize()); err = LibC.INSTANCE.getsockname(fd, local, size); if (err != 0) { throw new LibC.CException(); } return local.getAddress(); } finally { LibC.INSTANCE.close(fd); } } }