package com.limegroup.gnutella; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Properties; import java.util.Set; import org.limewire.concurrent.ManagedThread; import org.limewire.core.api.connection.FirewallStatusEvent; import org.limewire.core.api.connection.FirewallTransferStatus; import org.limewire.core.api.connection.FirewallTransferStatusEvent; import org.limewire.core.settings.ConnectionSettings; import org.limewire.core.settings.LimeProps; import org.limewire.core.settings.SearchSettings; import org.limewire.i18n.I18nMarker; import org.limewire.inject.EagerSingleton; import org.limewire.io.Address; import org.limewire.io.Connectable; import org.limewire.io.ConnectableImpl; import org.limewire.io.GUID; import org.limewire.io.NetworkInstanceUtils; import org.limewire.io.NetworkUtils; import org.limewire.listener.BroadcastPolicy; import org.limewire.listener.CachingEventMulticasterImpl; import org.limewire.listener.EventListener; import org.limewire.listener.EventMulticaster; import org.limewire.listener.ListenerSupport; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.net.ProxySettings; import org.limewire.net.ProxySettings.ProxyType; import org.limewire.net.address.AddressEvent; import org.limewire.net.address.FirewalledAddress; import org.limewire.nio.ByteBufferCache; import org.limewire.nio.ssl.SSLEngineTest; import org.limewire.nio.ssl.SSLUtils; import org.limewire.rudp.RUDPUtils; import org.limewire.service.ErrorService; import org.limewire.setting.BooleanSetting; import com.google.inject.Inject; import com.google.inject.Provider; import com.limegroup.gnutella.connection.RoutedConnection; import com.limegroup.gnutella.dht.DHTManager; import com.limegroup.gnutella.handshaking.HeaderNames; import com.limegroup.gnutella.messages.vendor.CapabilitiesVMFactory; import com.limegroup.gnutella.messages.vendor.HeaderUpdateVendorMessage; import com.limegroup.gnutella.statistics.OutOfBandStatistics; @EagerSingleton public class NetworkManagerImpl implements NetworkManager { private static final Log LOG = LogFactory.getLog(NetworkManagerImpl.class); private final Provider<UDPService> udpService; private final Provider<Acceptor> acceptor; private final Provider<DHTManager> dhtManager; private final Provider<ConnectionManager> connectionManager; private final OutOfBandStatistics outOfBandStatistics; private final NetworkInstanceUtils networkInstanceUtils; private final Provider<CapabilitiesVMFactory> capabilitiesVMFactory; private final Provider<ByteBufferCache> bbCache; private final Object addressLock = new Object(); private volatile Connectable directAddress; private volatile FirewalledAddress firewalledAddress; /** True if TLS is supported for this session. */ private volatile boolean tlsSupported = true; private final EventMulticaster<AddressEvent> listeners = new CachingEventMulticasterImpl<AddressEvent>(BroadcastPolicy.IF_NOT_EQUALS); private final ApplicationServices applicationServices; private volatile boolean started; /** * Set of cached proxies, if proxies are known before the external address is. */ private volatile Set<Connectable> cachedProxies; private final ProxySettings proxySettings; @Inject public NetworkManagerImpl(Provider<UDPService> udpService, Provider<Acceptor> acceptor, Provider<DHTManager> dhtManager, Provider<ConnectionManager> connectionManager, OutOfBandStatistics outOfBandStatistics, NetworkInstanceUtils networkInstanceUtils, Provider<CapabilitiesVMFactory> capabilitiesVMFactory, Provider<ByteBufferCache> bbCache, ApplicationServices applicationServices, ProxySettings proxySettings) { this.udpService = udpService; this.acceptor = acceptor; this.dhtManager = dhtManager; this.connectionManager = connectionManager; this.outOfBandStatistics = outOfBandStatistics; this.networkInstanceUtils = networkInstanceUtils; this.capabilitiesVMFactory = capabilitiesVMFactory; this.bbCache = bbCache; this.applicationServices = applicationServices; this.proxySettings = proxySettings; } @Inject void register(org.limewire.lifecycle.ServiceRegistry registry) { registry.register(this); } @Inject void register(ListenerSupport<FirewallStatusEvent> firewallStatusSupport, ListenerSupport<FirewallTransferStatusEvent> firewallTransferStatusSupport) { firewallStatusSupport.addListener(new EventListener<FirewallStatusEvent>() { @Override public void handleEvent(FirewallStatusEvent event) { if(started) { // TODO use event maybeFireNewDirectConnectionAddress(); } } }); firewallTransferStatusSupport.addListener(new EventListener<FirewallTransferStatusEvent>() { private volatile FirewallTransferStatus lastStatus = null; @Override public void handleEvent(FirewallTransferStatusEvent event) { if(started && lastStatus != event.getData()) { lastStatus = event.getData(); updateCapabilities(); } } }); } public void start() { if(isIncomingTLSEnabled() || isOutgoingTLSEnabled()) { if(applicationServices.isNewInstall() || applicationServices.isNewJavaVersion() || !SSLSettings.TLS_WORKED_LAST_TIME.getValue()) { //block if new install or new java version, or tls did not work last time we ran limewire. validateTLS(); } else { new ManagedThread(new Runnable(){ @Override public void run() { validateTLS(); } }, "NetworkManagerImpl.testTLS").start(); } } started = true; } @Override public void validateTLS() { if(isIncomingTLSEnabled() || isOutgoingTLSEnabled()) { SSLEngineTest sslTester = new SSLEngineTest(SSLUtils.getTLSContext(), SSLUtils.getTLSCipherSuites(), bbCache.get()); if(!sslTester.go()) { Throwable t = sslTester.getLastFailureCause(); setTLSNotSupported(t); if(!SSLSettings.IGNORE_SSL_EXCEPTIONS.getValue() && !sslTester.isIgnorable(t)) ErrorService.error(t); } SSLSettings.TLS_WORKED_LAST_TIME.setValue(tlsSupported); } } public void stop() { started = false; ConnectionSettings.EVER_ACCEPTED_INCOMING.setValue(acceptedIncomingConnection()); } public void initialize() { } public String getServiceName() { return I18nMarker.marktr("Network Management"); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#isIpPortValid() */ public boolean isIpPortValid() { return (NetworkUtils.isValidAddress(getAddress()) && NetworkUtils.isValidPort(getPort())); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#getUDPConnectBackGUID() */ public GUID getUDPConnectBackGUID() { return udpService.get().getConnectBackGUID(); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#isOOBCapable() */ public boolean isOOBCapable() { if(SearchSettings.FORCE_OOB.getValue()) return true; return isGUESSCapable() && outOfBandStatistics.isSuccessRateGood() && !networkInstanceUtils.isPrivate() && SearchSettings.OOB_ENABLED.getValue() && acceptor.get().isAddressExternal() && isIpPortValid(); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#isGUESSCapable() */ public boolean isGUESSCapable() { return udpService.get().isGUESSCapable() && !isProxyEnabled(); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#getNonForcedPort() */ public int getNonForcedPort() { return acceptor.get().getPort(false); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#getPort() */ public int getPort() { return acceptor.get().getPort(true); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#getNonForcedAddress() */ public byte[] getNonForcedAddress() { return acceptor.get().getAddress(false); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#getAddress() */ public byte[] getAddress() { return acceptor.get().getAddress(true); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#getExternalAddress() */ public byte[] getExternalAddress() { return acceptor.get().getExternalAddress(); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#incomingStatusChanged() */ public boolean incomingStatusChanged() { updateCapabilities(); // Only continue if the current address/port is valid & not private. byte addr[] = getAddress(); int port = getPort(); if(!NetworkUtils.isValidAddress(addr)) return false; if(networkInstanceUtils.isPrivateAddress(addr)) return false; if(!NetworkUtils.isValidPort(port)) return false; return true; } private void updateCapabilities() { capabilitiesVMFactory.get().updateCapabilities(); if (connectionManager.get().isShieldedLeaf()) connectionManager.get().sendUpdatedCapabilities(); synchronized (addressLock) { FirewalledAddress address = firewalledAddress; if (address != null) { newPushProxies(address.getPushProxies()); } } } public boolean addressChanged() { // Only continue if the current address/port is valid & not private. byte addr[] = getAddress(); int port = getPort(); if(!NetworkUtils.isValidAddress(addr)) return false; if(networkInstanceUtils.isPrivateAddress(addr)) return false; if(!NetworkUtils.isValidPort(port)) return false; // reset the last connect back time so the next time the TCP/UDP // validators run they try to connect back. acceptor.get().resetLastConnectBackTime(); // Notify the DHT dhtManager.get().addressChanged(); Properties props = new Properties(); props.put(HeaderNames.LISTEN_IP,NetworkUtils.ip2string(addr)+":"+port); HeaderUpdateVendorMessage huvm = new HeaderUpdateVendorMessage(props); for(RoutedConnection c : connectionManager.get().getInitializedConnections()) { if (c.getConnectionCapabilities().remoteHostSupportsHeaderUpdate() >= HeaderUpdateVendorMessage.VERSION) c.send(huvm); } for(RoutedConnection c : connectionManager.get().getInitializedClientConnections()) { if (c.getConnectionCapabilities().remoteHostSupportsHeaderUpdate() >= HeaderUpdateVendorMessage.VERSION) c.send(huvm); } // TODO //fireEvent(new AddressEvent(null, Address.EventType.ADDRESS_CHANGED)); return true; } public void externalAddressChanged() { maybeFireNewDirectConnectionAddress(); } private void maybeFireNewDirectConnectionAddress() { Connectable newDirectAddress = null; boolean fireEvent = false; Set<Connectable> proxies = null; synchronized (addressLock) { if(isDirectConnectionCapable()) { newDirectAddress = getPublicAddress(false); if (directAddress == null || ConnectableImpl.COMPARATOR.compare(directAddress, newDirectAddress) != 0) { directAddress = newDirectAddress; fireEvent = true; assert NetworkUtils.isValidIpPort(newDirectAddress); } } else { directAddress = null; proxies = cachedProxies; cachedProxies = null; } } if (fireEvent) { fireAddressChange(newDirectAddress); } else if (proxies != null) { newPushProxies(proxies); } } private boolean isDirectConnectionCapable() { return NetworkUtils.isValidAddress(getExternalAddress()) && acceptedIncomingConnection() && NetworkUtils.isValidPort(getPort()); } public void portChanged() { maybeFireNewDirectConnectionAddress(); } public void newPushProxies(Set<Connectable> pushProxies) { Connectable publicAddress = getPublicAddress(canDoFWT()); if (!NetworkUtils.isValidIpPort(publicAddress)) { cachedProxies = pushProxies; return; } FirewalledAddress newAddress = new FirewalledAddress(publicAddress, getPrivateAddress(), new GUID(applicationServices.getMyGUID()), pushProxies, supportsFWTVersion()); boolean changed = false; synchronized (addressLock) { if (!newAddress.equals(firewalledAddress) && directAddress == null) { firewalledAddress = newAddress; // ensure that we have a valid public address if we support fwts assert firewalledAddress.getFwtVersion() == 0 || NetworkUtils.isValidIpPort(firewalledAddress.getPublicAddress()); changed = true; } } if (changed) { fireAddressChange(newAddress); } } /** * @param udpPort uses stable udp port if true otherwise {@link #getPort()} */ private Connectable getPublicAddress(boolean udpPort) { try { return new ConnectableImpl(NetworkUtils.ip2string(getExternalAddress()), udpPort ? getStableUDPPort() : getPort(), isIncomingTLSEnabled()); } catch (UnknownHostException e) { throw new RuntimeException(e); } } @Override public Connectable getPublicAddress() { return getPublicAddress(!acceptedIncomingConnection()); } private Connectable getPrivateAddress() { byte[] privateAddress = getNonForcedAddress(); try { return new ConnectableImpl(new InetSocketAddress(InetAddress.getByAddress(privateAddress), getNonForcedPort()), isIncomingTLSEnabled()); } catch (UnknownHostException e) { throw new RuntimeException(e); } } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#acceptedIncomingConnection() */ public boolean acceptedIncomingConnection() { return acceptor.get().acceptedIncoming(); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#setListeningPort(int) */ public void setListeningPort(int port) throws IOException { acceptor.get().setListeningPort(port); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#canReceiveUnsolicited() */ public boolean canReceiveUnsolicited() { return udpService.get().canReceiveUnsolicited() && !isProxyEnabled(); } private boolean isProxyEnabled() { return proxySettings.getCurrentProxyType() != ProxyType.NONE; } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#canReceiveSolicited() */ public boolean canReceiveSolicited() { return udpService.get().canReceiveSolicited() && !isProxyEnabled(); } /* (non-Javadoc) * @see com.limegroup.gnutella.NetworkManager#canDoFWT() */ public boolean canDoFWT() { return udpService.get().canDoFWT() && !isProxyEnabled(); } public int getStableUDPPort() { return udpService.get().getStableUDPPort(); } public GUID getSolicitedGUID() { return udpService.get().getSolicitedGUID(); } public int supportsFWTVersion() { return canDoFWT() ? RUDPUtils.VERSION : 0; } public boolean isPrivateAddress(byte[] addr) { return networkInstanceUtils.isPrivateAddress(addr); } /** Disables TLS for this session. */ private void setTLSNotSupported(Throwable reason) { tlsSupported = false; if(reason != null) { StringWriter writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); reason.printStackTrace(pw); pw.flush(); } } /** Returns true if TLS is disabled for this session. */ public boolean isTLSSupported() { return tlsSupported && !isProxyEnabled(); } /** Whether or not incoming TLS is allowed. */ public boolean isIncomingTLSEnabled() { return isTLSSupported() && SSLSettings.TLS_INCOMING.getValue(); } public void setIncomingTLSEnabled(boolean enabled) { SSLSettings.TLS_INCOMING.setValue(enabled); } /** Whether or not outgoing TLS is allowed. */ public boolean isOutgoingTLSEnabled() { return isTLSSupported() && SSLSettings.TLS_OUTGOING.getValue(); } public void setOutgoingTLSEnabled(boolean enabled) { SSLSettings.TLS_OUTGOING.setValue(enabled); } public void addListener(EventListener<AddressEvent> listener) { listeners.addListener(listener); } public boolean removeListener(EventListener<AddressEvent> listener) { return listeners.removeListener(listener); } private void fireAddressChange(Address newAddress) { LOG.debugf("firing new address: {0}", newAddress); listeners.broadcast(new AddressEvent(newAddress, AddressEvent.Type.ADDRESS_CHANGED)); } private static class SSLSettings extends LimeProps { private SSLSettings() {} /** * Whether or not TLS worked the last time it was tested. */ public static final BooleanSetting TLS_WORKED_LAST_TIME = FACTORY.createBooleanSetting("TLS_WORKED_LAST_TIME", false); /** Whether or not we want to accept incoming TLS connections. */ public static final BooleanSetting TLS_INCOMING = FACTORY.createBooleanSetting("TLS_INCOMING", true); /** Whether or not we want to make outgoing connections with TLS. */ public static final BooleanSetting TLS_OUTGOING = FACTORY.createBooleanSetting("TLS_OUTGOING", true); /** False if we want to report exceptions in TLS handling. */ public static final BooleanSetting IGNORE_SSL_EXCEPTIONS = FACTORY.createRemoteBooleanSetting("IGNORE_SSL_EXCEPTIONS", true); } }