package com.limegroup.gnutella.messages; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; import org.limewire.collection.BitNumbers; import org.limewire.core.settings.ApplicationSettings; import org.limewire.io.BadGGEPPropertyException; import org.limewire.io.Connectable; import org.limewire.io.GGEP; import org.limewire.io.InvalidDataException; import org.limewire.io.IpPort; import org.limewire.io.IpPortImpl; import org.limewire.io.NetworkInstanceUtils; import org.limewire.io.NetworkUtils; import org.limewire.security.AddressSecurityToken; import org.limewire.security.InvalidSecurityTokenException; import org.limewire.security.MACCalculatorRepositoryManager; import org.limewire.util.ByteUtils; import org.limewire.util.StringUtils; import com.limegroup.gnutella.ExtendedEndpoint; import com.limegroup.gnutella.dht.DHTManager.DHTMode; import com.limegroup.gnutella.util.Utilities; /** * A ping reply message, AKA, "pong". This implementation provides a way * to "mark" pongs as being from supernodes. */ public class PingReplyImpl extends AbstractMessage implements IpPort, Connectable, PingReply { /** * The list of extra Gnutella ip/ports contained in this reply. */ private final List<IpPort> PACKED_IP_PORTS; /** * The list of extra DHT IP/ports contained in this reply. */ private final List<IpPort> PACKED_DHT_IP_PORTS; /** * The list of extra IP/ports contained in this reply. */ private final List<IpPort> PACKED_UDP_HOST_CACHES; /** * The IP address to connect to if this is a UDP host cache. * Null if this is not a UDP host cache. */ private final String UDP_CACHE_ADDRESS; /** * Constant for the number of ultrapeer slots for this host. */ private final int FREE_ULTRAPEER_SLOTS; /** * Constant for the number of free leaf slots for this host. */ private final int FREE_LEAF_SLOTS; /** All the data. We extract the port, IP address, number of files, * and number of kilobytes lazily. */ private final byte[] PAYLOAD; /** The IP string as extracted from payload[2..5]. Cached to avoid * allocations. LOCKING: obtain this' monitor. */ private final InetAddress IP; /** * Constant for the port number of this pong. */ private final int PORT; /** * The address this pong claims to be my external address. */ private final InetAddress MY_IP; /** * The port this pong claims to be my external port. */ private final int MY_PORT; /** * Constant for the number of shared files reported in the pong. */ private final long FILES; /** * Constant for the number of shared kilobytes reported in the pong. */ private final long KILOBYTES; /** * Constant int for the daily average uptime. */ private final int DAILY_UPTIME; /** * Constant for whether or not the remote node supports unicast. */ private final boolean SUPPORTS_UNICAST; /** * Constant for the vendor of the remote host. */ private final String VENDOR; /** * Constant for the major version number reported in the vendor block. */ private final int VENDOR_MAJOR_VERSION; /** * Constant for the minor version number reported in the vendor block. */ private final int VENDOR_MINOR_VERSION; /** * Constant for the query key reported for the pong. */ private final AddressSecurityToken QUERY_KEY; /** * Constant for the DHT Version. */ private final int DHT_VERSION; /** * Constant for the DHT mode (active/passive/none) */ private final DHTMode DHT_MODE; /** True if the remote host supports TLS. */ private final boolean TLS_CAPABLE; /** * Constant boolean for whether or not this pong contains any GGEP * extensions. */ private final boolean HAS_GGEP_EXTENSION; /** * Constant for the locale. */ private String CLIENT_LOCALE; /** * The number of free preferenced slots */ private int FREE_LOCALE_SLOTS; /** * Sole <tt>PingReply</tt> constructor. This establishes all ping reply * invariants. * * @param guid the Globally Unique Identifier (GUID) for this message * @param ttl the time to live for this message * @param hops the hops for this message * @param payload the message payload * @throws BadPacketException */ protected PingReplyImpl(byte[] guid, byte ttl, byte hops, byte[] payload, GGEP ggep, InetAddress ip, Network network, MACCalculatorRepositoryManager manager, NetworkInstanceUtils networkInstanceUtils) throws BadPacketException { super(guid, Message.F_PING_REPLY, ttl, hops, payload.length, network); PAYLOAD = payload; PORT = ByteUtils.ushort2int(ByteUtils.leb2short(PAYLOAD, 0)); FILES = ByteUtils.uint2long(ByteUtils.leb2int(PAYLOAD, 6)); KILOBYTES = ByteUtils.uint2long(ByteUtils.leb2int(PAYLOAD, 10)); IP = ip; // GGEP parsing //GGEP ggep = parseGGEP(); int dailyUptime = -1; boolean supportsUnicast = false; String vendor = ""; int vendorMajor = -1; int vendorMinor = -1; int freeLeafSlots = -1; int freeUltrapeerSlots = -1; AddressSecurityToken key = null; boolean tlsCapable = false; String locale /** def. val from settings? */ = ApplicationSettings.DEFAULT_LOCALE.get(); int slots = -1; //-1 didn't get it. InetAddress myIP=null; int myPort=0; List<IpPort> packedIPs = Collections.emptyList(); List<IpPort> packedDHTIPs = Collections.emptyList(); List<IpPort> packedCaches = Collections.emptyList(); String cacheAddress = null; int dhtVersion = -1; DHTMode dhtMode = null; // TODO: the exceptions thrown here are messy if(ggep != null) { if(ggep.hasKey(GGEPKeys.GGEP_HEADER_DAILY_AVERAGE_UPTIME)) { try { dailyUptime = ggep.getInt(GGEPKeys.GGEP_HEADER_DAILY_AVERAGE_UPTIME); } catch(BadGGEPPropertyException e) {} } supportsUnicast = ggep.hasKey(GGEPKeys.GGEP_HEADER_UNICAST_SUPPORT); if (ggep.hasKey(GGEPKeys.GGEP_HEADER_QUERY_KEY_SUPPORT)) { try { byte[] bytes = ggep.getBytes(GGEPKeys.GGEP_HEADER_QUERY_KEY_SUPPORT); key = new AddressSecurityToken(bytes, manager); } catch (InvalidSecurityTokenException e) { throw new BadPacketException("invalid query key"); } catch (BadGGEPPropertyException e) { throw new BadPacketException("invalid query key"); } } if(ggep.hasKey((GGEPKeys.GGEP_HEADER_UP_SUPPORT))) { try { byte[] bytes = ggep.getBytes(GGEPKeys.GGEP_HEADER_UP_SUPPORT); if(bytes.length >= 3) { freeLeafSlots = bytes[1]; freeUltrapeerSlots = bytes[2]; } } catch (BadGGEPPropertyException e) {} } if(ggep.hasKey((GGEPKeys.GGEP_HEADER_DHT_SUPPORT))) { try { byte[] bytes = ggep.getBytes(GGEPKeys.GGEP_HEADER_DHT_SUPPORT); if(bytes.length >= 3) { dhtVersion = ByteUtils.ushort2int(ByteUtils.beb2short(bytes, 0)); byte mode = (byte)(bytes[2] & DHTMode.DHT_MODE_MASK); dhtMode = DHTMode.valueOf(mode); if (dhtMode == null) { // Reset the Version number if the mode // is unknown dhtVersion = -1; } } } catch (BadGGEPPropertyException e) {} } if(ggep.hasKey(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE)) { try { byte[] bytes = ggep.getBytes(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE); if(bytes.length >= 2) locale = StringUtils.getASCIIString(bytes, 0, 2); if(bytes.length >= 3) slots = ByteUtils.ubyte2int(bytes[2]); } catch(BadGGEPPropertyException e) {} } if (ggep.hasKey(GGEPKeys.GGEP_HEADER_IPPORT)) { try{ byte[] data = ggep.getBytes(GGEPKeys.GGEP_HEADER_IPPORT); byte [] myip = new byte[4]; // only copy the addr if the data is at least 6 // bytes (ip + port). that way isValidAddress // will fail & we don't need to recheck the length // when getting the port. if(data.length >= 6) System.arraycopy(data,0,myip,0,4); if (NetworkUtils.isValidAddress(myip)) { try{ myIP = InetAddress.getByAddress(myip); myPort = ByteUtils.ushort2int(ByteUtils.leb2short(data,4)); if (networkInstanceUtils.isPrivateAddress(myIP) || !NetworkUtils.isValidPort(myPort) ) { // liars, or we are behind a NAT and there is LAN outside // either way we can't use it myIP=null; myPort=0; } }catch(UnknownHostException bad) { //keep the ip address null and the port 0 } } }catch(BadGGEPPropertyException ignored) {} } if(ggep.hasKey(GGEPKeys.GGEP_HEADER_UDP_HOST_CACHE)) { cacheAddress = ""; try { cacheAddress = ggep.getString(GGEPKeys.GGEP_HEADER_UDP_HOST_CACHE); } catch(BadGGEPPropertyException bad) {} } if(ggep.hasKey(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS)) { try { byte[] data = ggep.getBytes(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS); packedIPs = NetworkUtils.unpackIps(data); } catch(BadGGEPPropertyException bad) { } catch(InvalidDataException bpe) {} if(ggep.hasKey(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS_TLS)) { try { byte[] data = ggep.getBytes(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS_TLS); packedIPs = decoratePackedIPs(data, packedIPs); } catch(BadGGEPPropertyException bad) { } } } if(ggep.hasKey(GGEPKeys.GGEP_HEADER_DHT_IPPORTS)) { try { byte[] data = ggep.getBytes(GGEPKeys.GGEP_HEADER_DHT_IPPORTS); packedDHTIPs = NetworkUtils.unpackIps(data); } catch(BadGGEPPropertyException bad) { } catch(InvalidDataException bpe) { } } if(ggep.hasKey(GGEPKeys.GGEP_HEADER_PACKED_HOSTCACHES)) { try { String data = ggep.getString(GGEPKeys.GGEP_HEADER_PACKED_HOSTCACHES); packedCaches = listCaches(data); } catch(BadGGEPPropertyException bad) {} } tlsCapable = ggep.hasKey(GGEPKeys.GGEP_HEADER_TLS_CAPABLE); } MY_IP = myIP; MY_PORT = myPort; HAS_GGEP_EXTENSION = ggep != null; DAILY_UPTIME = dailyUptime; SUPPORTS_UNICAST = supportsUnicast; VENDOR = vendor; VENDOR_MAJOR_VERSION = vendorMajor; VENDOR_MINOR_VERSION = vendorMinor; QUERY_KEY = key; FREE_LEAF_SLOTS = freeLeafSlots; FREE_ULTRAPEER_SLOTS = freeUltrapeerSlots; CLIENT_LOCALE = locale; FREE_LOCALE_SLOTS = slots; if(cacheAddress != null && "".equals(cacheAddress)) UDP_CACHE_ADDRESS = getAddress(); else UDP_CACHE_ADDRESS = cacheAddress; PACKED_IP_PORTS = packedIPs; PACKED_DHT_IP_PORTS = packedDHTIPs; PACKED_UDP_HOST_CACHES = packedCaches; DHT_VERSION = dhtVersion; DHT_MODE = dhtMode; TLS_CAPABLE = tlsCapable; } /** Iterates through the hosts and sets TLS data if the data indicated the host supports TLS. */ private List<IpPort> decoratePackedIPs(byte[] tlsData, List<IpPort> hosts) { if(tlsData.length == 0) return hosts; List<IpPort> decorated = null; BitNumbers tlsBits = new BitNumbers(tlsData); int hostIdx = 0; for(IpPort next : hosts) { if(tlsBits.isSet(hostIdx)) { ExtendedEndpoint ep = new ExtendedEndpoint(next.getInetAddress(), next.getPort()); ep.setTLSCapable(true); if(decorated == null) { decorated = new ArrayList<IpPort>(hosts.size()); decorated.addAll(hosts.subList(0, hostIdx)); // add all prior hosts. } decorated.add(ep); } else if(decorated != null) { decorated.add(next); // preserve decorated } // If we've gone past the end of however much is stored, // we're done. if(hostIdx >= tlsBits.getMax()) { // add the rest of the hosts to the decorated list if necessary if(decorated != null && hostIdx+1 < hosts.size()) decorated.addAll(hosts.subList(hostIdx+1, hosts.size())); break; } hostIdx++; } if(decorated != null) { assert decorated.size() == hosts.size() : "decorated: " + decorated + ", hosts: " + hosts; return decorated; } else { return hosts; } } /** * Returns whether or not this pong is reporting any free slots on the * remote host, either leaf or ultrapeer. * * @return <tt>true</tt> if the remote host has any free leaf or ultrapeer * slots, otherwise <tt>false</tt> */ public boolean hasFreeSlots() { return hasFreeLeafSlots() || hasFreeUltrapeerSlots(); } /** * Returns whether or not this pong is reporting free leaf slots on the * remote host. * * @return <tt>true</tt> if the remote host has any free leaf slots, * otherwise <tt>false</tt> */ public boolean hasFreeLeafSlots() { return FREE_LEAF_SLOTS > 0; } /** * Returns whether or not this pong is reporting free ultrapeer slots on * the remote host. * * @return <tt>true</tt> if the remote host has any free ultrapeer slots, * otherwise <tt>false</tt> */ public boolean hasFreeUltrapeerSlots() { return FREE_ULTRAPEER_SLOTS > 0; } /** * Accessor for the number of free leaf slots reported by the remote host. * This will return -1 if the remote host did not include the necessary * GGEP block reporting slots. * * @return the number of free leaf slots, or -1 if the remote host did not * include this information */ public int getNumLeafSlots() { return FREE_LEAF_SLOTS; } /** * Accessor for the number of free ultrapeer slots reported by the remote * host. This will return -1 if the remote host did not include the * necessary GGEP block reporting slots. * * @return the number of free ultrapeer slots, or -1 if the remote host did * not include this information */ public int getNumUltrapeerSlots() { return FREE_ULTRAPEER_SLOTS; } @Override protected void writePayload(OutputStream out) throws IOException { out.write(PAYLOAD); } /** * Accessor for the port reported in this pong. * * @return the port number reported in the pong */ public int getPort() { return PORT; } /** * Returns the IP field in standard dotted decimal format, e.g., * "127.0.0.1". The most significant byte is written first. */ public String getAddress() { return IP.getHostAddress(); } /** * Returns the IP address bytes (MSB first). */ public byte[] getIPBytes() { byte[] ip=new byte[4]; ip[0]=PAYLOAD[2]; ip[1]=PAYLOAD[3]; ip[2]=PAYLOAD[4]; ip[3]=PAYLOAD[5]; return ip; } /** * Accessor for the number of files shared, as reported in the * pong. * * @return the number of files reported shared */ public long getFiles() { return FILES; } /** * Accessor for the number of kilobytes shared, as reported in the * pong. * * @return the number of kilobytes reported shared */ public long getKbytes() { return KILOBYTES; } /** Returns the average daily uptime in seconds from the GGEP payload. * If the pong did not report a daily uptime, returns -1. * * @return the daily uptime reported in the pong, or -1 if the uptime * was not present or could not be read */ public int getDailyUptime() { return DAILY_UPTIME; } /** Returns whether or not this host support unicast, GUESS-style * queries. * * @return <tt>true</tt> if this host does support GUESS-style queries, * otherwise <tt>false</tt> */ public boolean supportsUnicast() { return SUPPORTS_UNICAST; } /** Returns the AddressSecurityToken (if any) associated with this pong. May be null! * * @return the <tt>AddressSecurityToken</tt> for this pong, or <tt>null</tt> if no * key was specified */ public AddressSecurityToken getQueryKey() { return QUERY_KEY; } /** * Gets the list of packed IP/Ports. */ public List<IpPort> getPackedIPPorts() { return PACKED_IP_PORTS; } /** * Gets the list of packed DHT IP/Ports. */ public List<IpPort> getPackedDHTIPPorts() { return PACKED_DHT_IP_PORTS; } /** * Gets a list of packed IP/Ports of UDP Host Caches. */ public List<IpPort> getPackedUDPHostCaches() { return PACKED_UDP_HOST_CACHES; } public DHTMode getDHTMode() { return DHT_MODE; } public int getDHTVersion() { return DHT_VERSION; } /** * Returns whether or not this pong has a GGEP extension. * * @return <tt>true</tt> if the pong has a GGEP extension, otherwise * <tt>false</tt> */ public boolean hasGGEPExtension() { return HAS_GGEP_EXTENSION; } /** * Unzips data about UDP host caches & returns a list of'm. */ private List<IpPort> listCaches(String allCaches) { List<IpPort> theCaches = new LinkedList<IpPort>(); StringTokenizer st = new StringTokenizer(allCaches, "\n"); while(st.hasMoreTokens()) { String next = st.nextToken(); // look for possible features and ignore'm int i = next.indexOf("&"); // basically ignore. if(i != -1) next = next.substring(0, i); i = next.indexOf(":"); int port = 6346; if(i == 0 || i == next.length()) { continue; } else if(i != -1) { try { port = Integer.valueOf(next.substring(i+1)).intValue(); } catch(NumberFormatException invalid) { continue; } } else { i = next.length(); // setup for i-1 below. } if(!NetworkUtils.isValidPort(port)) continue; String host = next.substring(0, i); try { theCaches.add(new IpPortImpl(host, port)); } catch(UnknownHostException invalid) { continue; } } return Collections.unmodifiableList(theCaches); } ////////////////////////// Pong Marking ////////////////////////// /** * Returns true if this message is "marked", i.e., likely from an * Ultrapeer. * * @return <tt>true</tt> if this pong is marked as an Ultrapeer pong, * otherwise <tt>false</tt> */ public boolean isUltrapeer() { //Returns true if kb is a power of two greater than or equal to eight. long kb = getKbytes(); if (kb < 8) return false; return Utilities.isPowerOf2(ByteUtils.long2int(kb)); } // overrides Object.toString @Override public String toString() { return "PingReply("+getAddress()+":"+getPort()+ ", free ultrapeers slots: "+hasFreeUltrapeerSlots()+ ", free leaf slots: "+hasFreeLeafSlots()+ ", vendor: "+VENDOR+" "+VENDOR_MAJOR_VERSION+"."+ VENDOR_MINOR_VERSION+ ", "+super.toString()+ ", locale : " + CLIENT_LOCALE + ")"; } /** * Implements <tt>IpPort</tt> interface. Returns the <tt>InetAddress</tt> * for this host. * * @return the <tt>InetAddress</tt> for this host */ public InetAddress getInetAddress() { return IP; } public InetSocketAddress getInetSocketAddress() { return new InetSocketAddress(getInetAddress(), getPort()); } @Override public String getAddressDescription() { return getInetSocketAddress().toString(); } public InetAddress getMyInetAddress() { return MY_IP; } public int getMyPort() { return MY_PORT; } /** * Access the client_locale. */ public String getClientLocale() { return CLIENT_LOCALE; } public int getNumFreeLocaleSlots() { return FREE_LOCALE_SLOTS; } /** * Accessor for host cacheness. */ public boolean isUDPHostCache() { return UDP_CACHE_ADDRESS != null; } /** * Gets the UDP host cache address. */ public String getUDPCacheAddress() { return UDP_CACHE_ADDRESS; } /** Returns true if the host supports TLS. */ public boolean isTLSCapable() { return TLS_CAPABLE; } public byte[] getPayload() { return PAYLOAD; } @Override public Class<? extends Message> getHandlerClass() { return PingReply.class; } }