Package freenet.io.comm

Source Code of freenet.io.comm.FreenetInetAddress

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.io.comm;

import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;

import freenet.io.AddressIdentifier;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.transport.ip.HostnameSyntaxException;
import freenet.support.transport.ip.HostnameUtil;
import freenet.support.transport.ip.IPUtil;

/**
* Long-term InetAddress. If created with an IP address, then the IP address is primary.
* If created with a name, then the name is primary, and the IP address can change.
* Most code ripped from Peer.
*
* Propagates the IP address on equals() but not the hostname. This does not change
* hashCode() because it only happens if hostname is set, and in that case, hashCode()
* is based on the hostname and not on the IP address. So it is safe to put
* FreenetInetAddress's into hashtables: neither equals() nor getAddress() will change
* its hashCode.
*
* BUT a FreenetInetAddress with IP 1.2.3.4 and no hostname is *NOT* equal to one with
* the IP address and no name. So if you want to match on only the IP address, you need
* to either call dropHostname() first (after which neither propagation nor getAddress()
* will change the hashcode), or just use InetAddress's.
*
* FIXME reconsider whether we need this. The lazy lookup is useful but not THAT useful,
* and we have a regular lookup task now anyway. Over-complex, could lead to odd bugs,
* although not if used correctly as explained above.
* @author amphibian
*/
public class FreenetInetAddress {

  private static volatile boolean logMINOR;
  private static volatile boolean logDEBUG;

  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
        logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
      }
    });
  }

  // hostname - only set if we were created with a hostname
  // and not an address
  private final String hostname;
  private InetAddress _address;

  /**
   * Create from serialized form on a DataInputStream.
   */
  public FreenetInetAddress(DataInput dis) throws IOException {
    int firstByte = dis.readUnsignedByte();
    byte[] ba;
    if(firstByte == 255) {
      if(logMINOR) Logger.minor(this, "New format IPv6 address");
      // New format IPv6 address
      ba = new byte[16];
      dis.readFully(ba);
    } else if(firstByte == 0) {
      if(logMINOR) Logger.minor(this, "New format IPv4 address");
      // New format IPv4 address
      ba = new byte[4];
      dis.readFully(ba);
    } else {
      throw new IOException("Unknown type byte (old form? corrupt stream? too short/long prev field?): "+firstByte);
    }
    _address = InetAddress.getByAddress(ba);
    String name = null;
    String s = dis.readUTF();
    if(s.length() > 0)
      name = s;
    hostname = name;
  }

  /**
   * Create from serialized form on a DataInputStream.
   */
  public FreenetInetAddress(DataInput dis, boolean checkHostnameOrIPSyntax) throws HostnameSyntaxException,
          IOException {
    int firstByte = dis.readUnsignedByte();
    byte[] ba;
    if(firstByte == 255) {
      if(logMINOR) Logger.minor(this, "New format IPv6 address");
      // New format IPv6 address
      ba = new byte[16];
      dis.readFully(ba);
    } else if(firstByte == 0) {
      if(logMINOR) Logger.minor(this, "New format IPv4 address");
      // New format IPv4 address
      ba = new byte[4];
      dis.readFully(ba);
    } else {
      // Old format IPv4 address
      ba = new byte[4];
      ba[0] = (byte)firstByte;
      dis.readFully(ba, 1, 3);
    }
    _address = InetAddress.getByAddress(ba);
    String name = null;
    String s = dis.readUTF();
    if(s.length() > 0)
      name = s;
    hostname = name;
        if(checkHostnameOrIPSyntax && null != hostname) {
          if(!HostnameUtil.isValidHostname(hostname, true)) throw new HostnameSyntaxException();
    }
  }

  /**
   * Create from an InetAddress. The IP address is primary i.e. fixed.
   * The hostname either doesn't exist, or is looked up.
   */
  public FreenetInetAddress(InetAddress address) {
    _address = address;
    hostname = null;
  }

  public FreenetInetAddress(String host, boolean allowUnknown) throws UnknownHostException {
        InetAddress addr = null;
        if(host != null){
          if(host.startsWith("/")) host = host.substring(1);
          host = host.trim();
        }
        // if we were created with an explicit IP address, use it as such
        // debugging log messages because AddressIdentifier doesn't appear to handle all IPv6 literals correctly, such as "fe80::204:1234:dead:beef"
        AddressIdentifier.AddressType addressType = AddressIdentifier.getAddressType(host);
        if(logDEBUG) Logger.debug(this, "Address type of '"+host+"' appears to be '"+addressType+ '\'');
        if(addressType != AddressIdentifier.AddressType.OTHER) {
          // Is an IP address
            addr = InetAddress.getByName(host);
            // Don't catch UnknownHostException here, if it happens there's a bug in AddressIdentifier.
            if(logDEBUG) Logger.debug(this, "host is '"+host+"' and addr.getHostAddress() is '"+addr.getHostAddress()+ '\'');
            if(addr != null) {
                host = null;
            } else {
                addr = null;
            }
        }
        if( addr == null ) {
          if(logDEBUG) Logger.debug(this, '\'' +host+"' does not look like an IP address");
        }
        this._address = addr;
        this.hostname = host;
        // we're created with a hostname so delay the lookup of the address
        // until it's needed to work better with dynamic DNS hostnames
  }

  public FreenetInetAddress(String host, boolean allowUnknown, boolean checkHostnameOrIPSyntax) throws HostnameSyntaxException, UnknownHostException {
        InetAddress addr = null;
        if(host != null){
          if(host.startsWith("/")) host = host.substring(1);
          host = host.trim();
        }
        // if we were created with an explicit IP address, use it as such
        // debugging log messages because AddressIdentifier doesn't appear to handle all IPv6 literals correctly, such as "fe80::204:1234:dead:beef"
        AddressIdentifier.AddressType addressType = AddressIdentifier.getAddressType(host);
        if(logDEBUG) Logger.debug(this, "Address type of '"+host+"' appears to be '"+addressType+ '\'');
        if(addressType != AddressIdentifier.AddressType.OTHER) {
            try {
                addr = InetAddress.getByName(host);
            } catch (UnknownHostException e) {
              if(!allowUnknown) throw e;
                addr = null;
            }
            if(logDEBUG) Logger.debug(this, "host is '"+host+"' and addr.getHostAddress() is '"+(addr != null ? addr.getHostAddress()+ '\'' : ""));
            if(addr != null && addr.getHostAddress().equals(host)) {
              if(logDEBUG) Logger.debug(this, '\'' +host+"' looks like an IP address");
                host = null;
            } else {
                addr = null;
            }
        }
        if( addr == null ) {
          if(logDEBUG) Logger.debug(this, '\'' +host+"' does not look like an IP address");
        }
        this._address = addr;
        this.hostname = host;
        if(checkHostnameOrIPSyntax && null != this.hostname) {
          if(!HostnameUtil.isValidHostname(this.hostname, true)) throw new HostnameSyntaxException();
    }
        // we're created with a hostname so delay the lookup of the address
        // until it's needed to work better with dynamic DNS hostnames
  }
 
  public boolean laxEquals(FreenetInetAddress addr) {
    if(hostname != null) {
      if(addr.hostname == null) {
        if(_address == null) return false; // No basis for comparison.
        if(addr._address != null) {
          return _address.equals(addr._address);
        }
      } else {
        if (!hostname.equalsIgnoreCase(addr.hostname)) {
          return false;
        }
        // Now that we know we have the same hostname, we can propagate the IP.
        if((_address != null) && (addr._address == null))
          addr._address = _address;
        if((addr._address != null) && (_address == null))
          _address = addr._address;
        // Except if we actually do have two different looked-up IPs!
        if((addr._address != null) && (_address != null) && !addr._address.equals(_address))
          return false;
        // Equal.
        return true;
      }
    }
    // His hostname might not be null. Not a problem.
    return _address.equals(addr._address);
  }
 
  @Override
  public boolean equals(Object o) {
    if(!(o instanceof FreenetInetAddress)) {
      return false;
    }
    FreenetInetAddress addr = (FreenetInetAddress)o;
    if(hostname != null) {
      if(addr.hostname == null)
        return false;
      if (!hostname.equalsIgnoreCase(addr.hostname)) {
        return false;
      }
      // Now that we know we have the same hostname, we can propagate the IP.
      if((_address != null) && (addr._address == null))
        addr._address = _address;
      if((addr._address != null) && (_address == null))
        _address = addr._address;
      // Except if we actually do have two different looked-up IPs!
      if((addr._address != null) && (_address != null) && !addr._address.equals(_address))
        return false;
      // Equal.
      return true;
    }
    if(addr.hostname != null)
      return false;

    // No hostname, go by address.
    if(!_address.equals(addr._address)) {
      return false;
    }
   
    return true;
  }

  public boolean strictEquals(FreenetInetAddress addr) {
    if(hostname != null) {
      if(addr.hostname == null)
        return false;
      if (!hostname.equalsIgnoreCase(addr.hostname)) {
        return false;
      }
      // Now that we know we have the same hostname, we can propagate the IP.
      if((_address != null) && (addr._address == null))
        addr._address = _address;
      if((addr._address != null) && (_address == null))
        _address = addr._address;
      // Except if we actually do have two different looked-up IPs!
      if((addr._address != null) && (_address != null) && !addr._address.equals(_address))
        return false;
      // Equal.
      return true;
    } else if(addr.hostname != null /* && hostname == null */) {
      return false;
    }

    // No hostname, go by address.
    if(!getHostName(_address).equalsIgnoreCase(getHostName(addr._address))) {
      //Logger.minor(this, "Addresses do not match: mine="+getHostName(_address)+" his="+getHostName(addr._address));
      return false;
    }
   
    return true;
  }

  /**
   * Get the IP address. Look it up if necessary, but return the last value if it
   * has ever been looked up before; will not trigger a new lookup if it has been
   * looked up before.
   */
  public InetAddress getAddress() {
    return getAddress(true);
  }

  /**
   * Get the IP address. Look it up only if allowed to, but return the last value if it
   * has ever been looked up before; will not trigger a new lookup if it has been
   * looked up before.
   */
  public InetAddress getAddress(boolean doDNSRequest) {
    if (_address != null) {
      return _address;
    } else {
            if(!doDNSRequest) return null;
            InetAddress addr = getHandshakeAddress();
            if( addr != null ) {
                    this._address = addr;
            }
            return addr;
    }
  }

  /**
   * Get the IP address, looking up the hostname if the hostname is primary, even if
   * it has been looked up before. Typically called on a reconnect attempt, when the
   * dyndns address may have changed.
   */
  public InetAddress getHandshakeAddress() {
      // Since we're handshaking, hostname-to-IP may have changed
      if ((_address != null) && (hostname == null)) {
        if(logMINOR) Logger.minor(this, "hostname is null, returning "+_address);
          return _address;
      } else {
        if(logMINOR) Logger.minor(this, "Looking up '"+hostname+"' in DNS", new Exception("debug"));
          /*
           * Peers are constructed from an address once a
           * handshake has been completed, so this lookup
           * will only be performed during a handshake
           * (this method should normally only be called
           * from PeerNode.getHandshakeIPs() and once
           * each connection from this.getAddress()
           * otherwise) - it doesn't mean we perform a
           * DNS lookup with every packet we send.
           */
          try {
            InetAddress addr = InetAddress.getByName(hostname);
            if(logMINOR) Logger.minor(this, "Look up got '"+addr+ '\'');
            if( addr != null ) {
              /*
               * cache the answer since getHandshakeAddress()
               * doesn't use the cached value, thus
               * getHandshakeIPs() should always get the
               * latest value from DNS (minus Java's caching)
               */
              this._address = InetAddress.getByAddress(addr.getAddress());
              if(logMINOR) Logger.minor(this, "Setting address to "+_address);
            }
            return addr;
          } catch (UnknownHostException e) {
            if(logMINOR) Logger.minor(this, "DNS said hostname '"+hostname+"' is an unknown host, returning null");
              return null;
          }
      }
  }

  @Override
  public int hashCode() {
    if(hostname != null) {
      return hostname.hashCode(); // Was set at creation, so it can safely be used here.
    } else {
      return _address.hashCode(); // Can be null, but if so, hostname will be non-null.
    }
  }
 
  @Override
  public String toString() {
    if(hostname != null) {
      return hostname;
    } else {
      return _address.getHostAddress();
    }
  }
 
  public String toStringPrefNumeric() {
    if(_address != null)
      return _address.getHostAddress();
    else
      return hostname;
  }

  public void writeToDataOutputStream(DataOutputStream dos) throws IOException {
    InetAddress addr = this.getAddress();
    if (addr == null) throw new UnknownHostException();
    byte[] data = addr.getAddress();
    if(data.length == 4)
      dos.write(0);
    else
      dos.write(255);
    dos.write(data);
    if(hostname != null)
      dos.writeUTF(hostname);
    else
      dos.writeUTF("");
  }

  /**
   * Return the hostname or the IP address of the given InetAddress.
   * Does not attempt to do a reverse lookup; if the hostname is
   * known, return it, otherwise return the textual IP address.
   */
  public static String getHostName(InetAddress primaryIPAddress) {
    if(primaryIPAddress == null) return null;
    String s = primaryIPAddress.toString();
    String addr = s.substring(0, s.indexOf('/')).trim();
    if(addr.length() == 0)
      return primaryIPAddress.getHostAddress();
    else
      return addr;
  }

  public boolean isRealInternetAddress(boolean lookup, boolean defaultVal, boolean allowLocalAddresses) {
    if(_address != null) {
      return IPUtil.isValidAddress(_address, allowLocalAddresses);
    } else {
      if(lookup) {
        InetAddress a = getAddress();
        if(a != null)
          return IPUtil.isValidAddress(a, allowLocalAddresses);
      }
      return defaultVal; 
    }
  }

  /**
   * Get a new <code>FreenetInetAddress</code> with host name removed.
   *
   * @return a new <code>FreenetInetAddress</code> with host name removed; or {@code null} if no
   *         known ip address is associated with this object. You may want to do a
   *         <code>getAddress(true)</code> before calling this.
   */
  public FreenetInetAddress dropHostname() {
    if(_address == null) {
      Logger.error(this, "Can't dropHostname() if no address!");
      return null;
    }
    if(hostname != null) {
      return new FreenetInetAddress(_address);
    } else return this;
  }

  public boolean hasHostnameNoIP() {
    return hostname != null && hostname.length() > 0 && _address == null;
  }

  public boolean isIPv6(boolean defaultValue) {
    if(_address == null)
      return defaultValue;
    else
      return (_address instanceof Inet6Address);
  }
}
TOP

Related Classes of freenet.io.comm.FreenetInetAddress

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.