package com.limegroup.gnutella.net.address; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import org.limewire.inject.EagerSingleton; import org.limewire.io.Address; import org.limewire.io.Connectable; import org.limewire.io.NetworkUtils; import org.limewire.listener.EventBroadcaster; import org.limewire.listener.ListenerSupport; import org.limewire.listener.RegisteringEventListener; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.net.ConnectivityChangeEvent; import org.limewire.net.SocketsManager; import org.limewire.net.address.AddressEvent; import org.limewire.net.address.AddressResolutionObserver; import org.limewire.net.address.AddressResolver; import org.limewire.net.address.FirewalledAddress; import com.google.inject.Inject; import com.limegroup.gnutella.NetworkManager; /** * Detects if a firewalled address is behind the same NAT and on the same * local network as this client and resolves to the local address. Otherwise marks the * firewalled address as resolved. */ @EagerSingleton public class SameNATAddressResolver implements AddressResolver, RegisteringEventListener<AddressEvent> { private final static Log LOG = LogFactory.getLog(SameNATAddressResolver.class, LOGGING_CATEGORY); private final NetworkManager networkManager; private final EventBroadcaster<ConnectivityChangeEvent> connectivityEventBroadcaster; /** * Ensures that {@link ConnectivityChangeEvent} is only thrown once. */ private final AtomicBoolean localAddressEventGuard = new AtomicBoolean(false); @Inject public SameNATAddressResolver(NetworkManager networkManager, EventBroadcaster<ConnectivityChangeEvent> connectivityEventBroadcaster) { this.networkManager = networkManager; this.connectivityEventBroadcaster = connectivityEventBroadcaster; } @Inject public void register(ListenerSupport<AddressEvent> addressEventListenerSupport) { addressEventListenerSupport.addListener(this); } @Inject public void register(SocketsManager socketsManager) { socketsManager.registerResolver(this); } public void handleEvent(AddressEvent event) { if (areLocalAddressesKnown()) { if (localAddressEventGuard.compareAndSet(false, true)) { connectivityEventBroadcaster.broadcast(new ConnectivityChangeEvent()); } } } /** * Returns true if the local private and public addresses are known, * and the address is a {@link FirewalledAddress} and it is behind the * same NAT. */ @Override public boolean canResolve(Address address) { if (address instanceof FirewalledAddress) { if (areLocalAddressesKnown()) { return isBehindThisNAT((FirewalledAddress)address); } else { LOG.debugf("can not resolve remote address {0} because local address is not known", address); return false; } } LOG.debugf("can not resolve remote address {0}", address); return false; } private boolean areLocalAddressesKnown() { return NetworkUtils.isValidAddress(networkManager.getExternalAddress()) && NetworkUtils.isValidAddress(networkManager.getNonForcedAddress()); } private boolean isBehindThisNAT(FirewalledAddress address) { byte[] thisPublicAddress = networkManager.getExternalAddress(); if (!Arrays.equals(address.getPublicAddress().getInetAddress().getAddress(), thisPublicAddress)) { if (LOG.isDebugEnabled()) { LOG.debugf("different public address: local = {0}, remote = {1}", NetworkUtils.ip2string(thisPublicAddress), address.getPublicAddress()); } return false; } byte[] thisPrivateAddress = networkManager.getNonForcedAddress(); if (!NetworkUtils.areInSameSiteLocalNetwork(address.getPrivateAddress().getInetAddress().getAddress(), thisPrivateAddress)) { if (LOG.isDebugEnabled()) { LOG.debugf("different site local networks: local = {0}, remote = {1}", NetworkUtils.ip2string(thisPrivateAddress), address.getPrivateAddress()); } return false; } LOG.debug("addresses behind same NAT!"); return true; } /** * Resolves a {@link FirewalledAddress} to the {@link Connectable} of its * {@link FirewalledAddress#getPrivateAddress() private address} if this peer * and the peer the address belongs to are behind the same firewall. * <p> * Otherwise resolves the address to a {@link ResolvedFirewalledAddress} to * mark it as resolved. */ @Override public <T extends AddressResolutionObserver> T resolve(Address addr, T observer) { FirewalledAddress address = (FirewalledAddress)addr; assert isBehindThisNAT(address) : "not behind same NAT: " + address; LOG.debugf("resolved remote address {0} to {1}", address, address.getPrivateAddress()); observer.resolved(address.getPrivateAddress()); return observer; } }