package lbms.plugins.mldht.kad.utils;
import static the8472.utils.Functional.unchecked;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.ProtocolFamily;
import java.net.SocketException;
import java.net.StandardProtocolFamily;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import lbms.plugins.mldht.kad.PeerAddressDBItem;
import the8472.utils.Arrays;
public class AddressUtils {
public static boolean isBogon(PeerAddressDBItem item)
{
return isBogon(item.getInetAddress(), item.getPort());
}
public static boolean isBogon(InetSocketAddress addr)
{
return isBogon(addr.getAddress(),addr.getPort());
}
public static boolean isBogon(InetAddress addr, int port)
{
return !(port > 0 && port <= 0xFFFF && isGlobalUnicast(addr));
}
public static boolean isTeredo(InetAddress addr) {
if(!(addr instanceof Inet6Address))
return false;
byte[] raw = addr.getAddress();
return raw[0] == 0x20 && raw[1] == 0x01 && raw[2] == 0x00 && raw[3] == 0x00;
}
public static boolean isGlobalUnicast(InetAddress addr)
{
if(addr instanceof Inet4Address && addr.getAddress()[0] == 0)
return false;
return !(addr.isAnyLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress() || addr.isMulticastAddress() || addr.isSiteLocalAddress());
}
public static byte[] packAddress(InetSocketAddress addr) {
byte[] result = null;
int port = addr.getPort();
if(addr.getAddress() instanceof Inet4Address) {
result = new byte[6];
}
if(addr.getAddress() instanceof Inet6Address) {
result = new byte[18];
}
ByteBuffer buf = ByteBuffer.wrap(result);
buf.put(addr.getAddress().getAddress());
buf.putChar((char)(addr.getPort() & 0xffff));
return result;
}
public static List<InetSocketAddress> unpackCompact(byte[] raw, Class<? extends InetAddress> type) {
if(raw == null || raw.length == 0)
return Collections.emptyList();
int addressSize = 0;
if(type == Inet4Address.class)
addressSize = 6;
if(type == Inet6Address.class)
addressSize = 18;
if((raw.length % addressSize) != 0)
throw new IllegalArgumentException("ipv4 / ipv6 compact format length must be multiple of 6 / 18 bytes");
InetSocketAddress[] addrs = new InetSocketAddress[raw.length / addressSize];
ByteBuffer buf = ByteBuffer.wrap(raw);
byte[] ip = new byte[addressSize - 2];
int i = 0;
while(buf.hasRemaining()) {
buf.get(ip);
addrs[i] = new InetSocketAddress(unchecked(() -> InetAddress.getByAddress(ip)), Short.toUnsignedInt(buf.getShort()));
i++;
}
return java.util.Arrays.asList(addrs);
}
public static InetSocketAddress unpackAddress(byte[] raw) {
if(raw.length != 6 && raw.length != 18)
return null;
ByteBuffer buf = ByteBuffer.wrap(raw);
byte[] rawIP = new byte[raw.length - 2];
buf.get(rawIP);
int port = buf.getChar();
InetAddress ip;
try {
ip = InetAddress.getByAddress(rawIP);
} catch (UnknownHostException e) {
return null;
}
return new InetSocketAddress(ip, port);
}
public static List<InetAddress> getAvailableGloballyRoutableAddrs(Class<? extends InetAddress> type) {
LinkedList<InetAddress> addrs = new LinkedList<>();
try
{
for (NetworkInterface iface : Collections.list(NetworkInterface.getNetworkInterfaces()))
{
if(!iface.isUp() || iface.isLoopback())
continue;
for (InterfaceAddress ifaceAddr : iface.getInterfaceAddresses())
{
if (type == Inet6Address.class && ifaceAddr.getAddress() instanceof Inet6Address)
{
Inet6Address addr = (Inet6Address) ifaceAddr.getAddress();
// only accept globally reachable IPv6 unicast addresses
if (addr.isIPv4CompatibleAddress() || !isGlobalUnicast(addr))
continue;
// prefer other addresses over teredo
if (isTeredo(addr))
addrs.addLast(addr);
else
addrs.addFirst(addr);
}
if(type == Inet4Address.class && ifaceAddr.getAddress() instanceof Inet4Address)
{
Inet4Address addr = (Inet4Address) ifaceAddr.getAddress();
if(!isGlobalUnicast(addr))
continue;
addrs.add(addr);
}
}
}
} catch (Exception e)
{
e.printStackTrace();
}
Collections.sort(addrs, (a, b) -> Arrays.compareUnsigned(a.getAddress(), b.getAddress()));
return addrs;
}
public static boolean isValidBindAddress(InetAddress addr) {
// we don't like them them but have to allow them
if(addr.isAnyLocalAddress())
return true;
try {
NetworkInterface iface = NetworkInterface.getByInetAddress(addr);
if(iface == null)
return false;
return iface.isUp() && !iface.isLoopback();
} catch (SocketException e) {
return false;
}
}
public static InetAddress getAnyLocalAddress(Class<? extends InetAddress> type) {
try {
if(type == Inet6Address.class)
return InetAddress.getByAddress(new byte[16]);
if(type == Inet4Address.class)
return InetAddress.getByAddress(new byte[4]);
} catch (UnknownHostException e) {
e.printStackTrace();
}
throw new RuntimeException("this shouldn't happen");
}
public static InetAddress getDefaultRoute(Class<? extends InetAddress> type) {
InetAddress target = null;
ProtocolFamily family = type == Inet6Address.class ? StandardProtocolFamily.INET6 : StandardProtocolFamily.INET;
try(DatagramChannel chan=DatagramChannel.open(family)) {
if(type == Inet4Address.class)
target = InetAddress.getByAddress(new byte[] {8,8,8,8});
if(type == Inet6Address.class)
target = InetAddress.getByName("2001:4860:4860::8888");
chan.connect(new InetSocketAddress(target,63));
InetSocketAddress soa = (InetSocketAddress) chan.getLocalAddress();
InetAddress local = soa.getAddress();
if(type.isInstance(local) && !local.isAnyLocalAddress())
return local;
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static String toString(InetSocketAddress sockAddr) {
InetAddress addr = sockAddr.getAddress();
if(addr instanceof Inet6Address)
return String.format("%41s:%-5d", "[" + addr.getHostAddress() + "]", sockAddr.getPort());
return String.format("%15s:%-5d", addr.getHostAddress(), sockAddr.getPort());
}
}