From 943b670738c91b15e4f9a491e93a2c4295682ba4 Mon Sep 17 00:00:00 2001 From: Michael Christen Date: Tue, 6 Dec 2011 11:37:37 +0100 Subject: [PATCH] less terrible warning if uPnP fails --- .../net/yacy/upnp/DiscoveryAdvertisement.java | 647 ++++++++++-------- 1 file changed, 348 insertions(+), 299 deletions(-) diff --git a/source/net/yacy/upnp/DiscoveryAdvertisement.java b/source/net/yacy/upnp/DiscoveryAdvertisement.java index 5423bf211..e088cebbc 100644 --- a/source/net/yacy/upnp/DiscoveryAdvertisement.java +++ b/source/net/yacy/upnp/DiscoveryAdvertisement.java @@ -47,324 +47,373 @@ */ package net.yacy.upnp; -import java.io.*; -import java.net.*; -import java.util.*; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; -import org.apache.commons.logging.*; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** - * SSDP messages listener Thread, notify registered objects implementing the interface DiscoveryEventHandler
- * when a device joins the networks or leaves it.
- * The listener thread is set to only accept matching device description and broadcast message sender IP - * to avoid a security flaw with the protocol. If you are not happy with such behaviour - * you can set the net.yacy.upnp.ddos.matchip system property to false to avoid this check. + * SSDP messages listener Thread, notify registered objects implementing the interface + * DiscoveryEventHandler
when a device joins the networks or leaves it.
+ * The listener thread is set to only accept matching device description and broadcast message sender IP to + * avoid a security flaw with the protocol. If you are not happy with such behaviour you can set the + * net.yacy.upnp.ddos.matchip system property to false to avoid this check. + * * @author SuperBonBon * @version 1.0 */ -public class DiscoveryAdvertisement implements Runnable { - - private final static Log log = LogFactory.getLog( DiscoveryAdvertisement.class ); - - private static boolean MATCH_IP = true; - - static { - String prop = System.getProperty( "net.yacy.upnp.ddos.matchip" ); - if ( prop != null && prop.equals( "false" ) ) MATCH_IP = false; - } - - private static final int DEFAULT_TIMEOUT = 250; - - public final static int EVENT_SSDP_ALIVE = 0; - public final static int EVENT_SSDP_BYE_BYE = 1; +public class DiscoveryAdvertisement implements Runnable +{ - private final static String NTS_SSDP_ALIVE = "ssdp:alive"; - private final static String NTS_SSDP_BYE_BYE = "ssdp:byebye"; - private final static String NT_ALL_EVENTS = "DiscoveryAdvertisement:nt:allevents"; - - private Map> byeByeRegistered = new HashMap>(); - private Map> aliveRegistered = new HashMap>(); - private Map USNPerIP = new HashMap(); - - private final Object REGISTRATION_PROCESS = new Object(); - - private final static DiscoveryAdvertisement singleton = new DiscoveryAdvertisement(); - private boolean inService = false; - private boolean daemon = true; - - private java.net.MulticastSocket skt; - private DatagramPacket input; - - private DiscoveryAdvertisement() { - } - - public final static DiscoveryAdvertisement getInstance() { - return singleton; - } - - public void setDaemon( boolean daemon ) { - this.daemon = daemon; - } - - /** - * Registers an event category sent by UPNP devices - * @param notificationEvent the event type, either DiscoveryAdvertisement.EVENT_SSDP_ALIVE - * or DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE - * @param nt the type of device advertisement, upnp:rootdevice will return you all advertisement in relation with nt upnp:rootdevice - * a null value specify that all nt type are wanted - * @param eventHandler the events handler, this objet will receive notifications.. - * @throws IOException if an error ocurs when the SSDP events listeners threads starts - */ - public void registerEvent( int notificationEvent, String nt, DiscoveryEventHandler eventHandler ) throws IOException { - synchronized( REGISTRATION_PROCESS ) { - if ( !inService ) startDevicesListenerThread(); - if ( nt == null ) nt = NT_ALL_EVENTS; - if ( notificationEvent == EVENT_SSDP_ALIVE ) { - Set handlers = aliveRegistered.get( nt ); - if ( handlers == null ) { - handlers = new HashSet(); - aliveRegistered.put( nt, handlers ); + private final static Log log = LogFactory.getLog(DiscoveryAdvertisement.class); + + private static boolean MATCH_IP = true; + + static { + String prop = System.getProperty("net.yacy.upnp.ddos.matchip"); + if ( prop != null && prop.equals("false") ) { + MATCH_IP = false; } - handlers.add( eventHandler ); - } else if ( notificationEvent == EVENT_SSDP_BYE_BYE ) { - Set handlers = byeByeRegistered.get( nt ); - if ( handlers == null ) { - handlers = new HashSet(); - byeByeRegistered.put( nt, handlers ); + } + + private static final int DEFAULT_TIMEOUT = 250; + + public final static int EVENT_SSDP_ALIVE = 0; + public final static int EVENT_SSDP_BYE_BYE = 1; + + private final static String NTS_SSDP_ALIVE = "ssdp:alive"; + private final static String NTS_SSDP_BYE_BYE = "ssdp:byebye"; + private final static String NT_ALL_EVENTS = "DiscoveryAdvertisement:nt:allevents"; + + private final Map> byeByeRegistered = + new HashMap>(); + private final Map> aliveRegistered = + new HashMap>(); + private final Map USNPerIP = new HashMap(); + + private final Object REGISTRATION_PROCESS = new Object(); + + private final static DiscoveryAdvertisement singleton = new DiscoveryAdvertisement(); + private boolean inService = false; + private boolean daemon = true; + + private java.net.MulticastSocket skt; + private DatagramPacket input; + + private DiscoveryAdvertisement() { + } + + public final static DiscoveryAdvertisement getInstance() { + return singleton; + } + + public void setDaemon(boolean daemon) { + this.daemon = daemon; + } + + /** + * Registers an event category sent by UPNP devices + * + * @param notificationEvent the event type, either DiscoveryAdvertisement.EVENT_SSDP_ALIVE or + * DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE + * @param nt the type of device advertisement, upnp:rootdevice will return you all advertisement in + * relation with nt upnp:rootdevice a null value specify that all nt type are wanted + * @param eventHandler the events handler, this objet will receive notifications.. + * @throws IOException if an error ocurs when the SSDP events listeners threads starts + */ + public void registerEvent(int notificationEvent, String nt, DiscoveryEventHandler eventHandler) + throws IOException { + synchronized ( this.REGISTRATION_PROCESS ) { + if ( !this.inService ) { + startDevicesListenerThread(); + } + if ( nt == null ) { + nt = NT_ALL_EVENTS; + } + if ( notificationEvent == EVENT_SSDP_ALIVE ) { + Set handlers = this.aliveRegistered.get(nt); + if ( handlers == null ) { + handlers = new HashSet(); + this.aliveRegistered.put(nt, handlers); + } + handlers.add(eventHandler); + } else if ( notificationEvent == EVENT_SSDP_BYE_BYE ) { + Set handlers = this.byeByeRegistered.get(nt); + if ( handlers == null ) { + handlers = new HashSet(); + this.byeByeRegistered.put(nt, handlers); + } + handlers.add(eventHandler); + } else { + throw new IllegalArgumentException("Unknown notificationEvent type"); + } } - handlers.add( eventHandler ); - } else { - throw new IllegalArgumentException( "Unknown notificationEvent type" ); - } } - } - - /** - * Unregisters an event category sent by UPNP devices - * @param notificationEvent the event type, either DiscoveryAdvertisement.EVENT_SSDP_ALIVE - * or DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE - * @param nt the type of device advertisement, upnp:rootdevice will unregister all advertisement in relation with nt upnp:rootdevice - * a null value specify that all nt type are unregistered - * @param eventHandler the events handler that needs to be unregistred. - */ - public void unRegisterEvent( int notificationEvent, String nt, DiscoveryEventHandler eventHandler ) { - synchronized( REGISTRATION_PROCESS ) { - if ( nt == null ) nt = NT_ALL_EVENTS; - if ( notificationEvent == EVENT_SSDP_ALIVE ) { - Set handlers = aliveRegistered.get( nt ); - if ( handlers != null ) { - handlers.remove( eventHandler ); - if (handlers.isEmpty()) { - aliveRegistered.remove( nt ); - } + + /** + * Unregisters an event category sent by UPNP devices + * + * @param notificationEvent the event type, either DiscoveryAdvertisement.EVENT_SSDP_ALIVE or + * DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE + * @param nt the type of device advertisement, upnp:rootdevice will unregister all advertisement in + * relation with nt upnp:rootdevice a null value specify that all nt type are unregistered + * @param eventHandler the events handler that needs to be unregistred. + */ + public void unRegisterEvent(int notificationEvent, String nt, DiscoveryEventHandler eventHandler) { + synchronized ( this.REGISTRATION_PROCESS ) { + if ( nt == null ) { + nt = NT_ALL_EVENTS; + } + if ( notificationEvent == EVENT_SSDP_ALIVE ) { + Set handlers = this.aliveRegistered.get(nt); + if ( handlers != null ) { + handlers.remove(eventHandler); + if ( handlers.isEmpty() ) { + this.aliveRegistered.remove(nt); + } + } + } else if ( notificationEvent == EVENT_SSDP_BYE_BYE ) { + Set handlers = this.byeByeRegistered.get(nt); + if ( handlers != null ) { + handlers.remove(eventHandler); + if ( handlers.isEmpty() ) { + this.byeByeRegistered.remove(nt); + } + } + } else { + throw new IllegalArgumentException("Unknown notificationEvent type"); + } + if ( this.aliveRegistered.isEmpty() && this.byeByeRegistered.isEmpty() ) { + stopDevicesListenerThread(); + } } - } else if ( notificationEvent == EVENT_SSDP_BYE_BYE ) { - Set handlers = byeByeRegistered.get( nt ); - if ( handlers != null ) { - handlers.remove( eventHandler ); - if (handlers.isEmpty()) { - byeByeRegistered.remove( nt ); - } + } + + private void startDevicesListenerThread() throws IOException { + synchronized ( singleton ) { + if ( !this.inService ) { + this.startMultiCastSocket(); + Thread deamon = new Thread(this, "DiscoveryAdvertisement daemon"); + deamon.setDaemon(this.daemon); + deamon.start(); + // wait for the thread to be started + while ( !this.inService ) { + // let's wait a few ms + try { + Thread.sleep(2); + } catch ( InterruptedException ex ) { + // don t care + } + } + } } - } else { - throw new IllegalArgumentException( "Unknown notificationEvent type" ); - } - if (aliveRegistered.isEmpty() && byeByeRegistered.isEmpty()) { - stopDevicesListenerThread(); - } } - } - - private void startDevicesListenerThread() throws IOException { - synchronized( singleton ) { - if ( !inService ) { - this.startMultiCastSocket(); - Thread deamon = new Thread( this, "DiscoveryAdvertisement daemon" ); - deamon.setDaemon( daemon ); - deamon.start(); - // wait for the thread to be started - while( !inService ) { - // let's wait a few ms - try { - Thread.sleep( 2 ); - } catch( InterruptedException ex ) { - // don t care - } + + private void stopDevicesListenerThread() { + synchronized ( singleton ) { + this.inService = false; } - } } - } - - private void stopDevicesListenerThread() { - synchronized( singleton ) { - inService = false; + + private void startMultiCastSocket() throws IOException { + + this.skt = new java.net.MulticastSocket(null); + this.skt.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), Discovery.SSDP_PORT)); + this.skt.setTimeToLive(Discovery.DEFAULT_TTL); + this.skt.setSoTimeout(DEFAULT_TIMEOUT); + this.skt.joinGroup(InetAddress.getByName(Discovery.SSDP_IP)); + + byte[] buf = new byte[2048]; + this.input = new DatagramPacket(buf, buf.length); + } - } - - private void startMultiCastSocket() throws IOException { - skt = new java.net.MulticastSocket( null ); - skt.bind( new InetSocketAddress( InetAddress.getByName( "0.0.0.0" ), Discovery.SSDP_PORT ) ); - skt.setTimeToLive( Discovery.DEFAULT_TTL ); - skt.setSoTimeout( DEFAULT_TIMEOUT ); - skt.joinGroup( InetAddress.getByName( Discovery.SSDP_IP ) ); - - byte[] buf = new byte[2048]; - input = new DatagramPacket( buf, buf.length ); + @Override + public void run() { + if ( !Thread.currentThread().getName().equals("DiscoveryAdvertisement daemon") ) { + throw new RuntimeException("No right to call this method"); + } + this.inService = true; + while ( this.inService ) { + try { + listenBroadCast(); + } catch ( SocketTimeoutException ex ) { + // ignoring + } catch ( IOException ioEx ) { + log.warn("IO Exception during UPNP DiscoveryAdvertisement messages listening thread"); + } catch ( Exception ex ) { + log + .warn("Fatal Error during UPNP DiscoveryAdvertisement messages listening thread, thread will exit"); + this.inService = false; + this.aliveRegistered.clear(); + this.byeByeRegistered.clear(); + this.USNPerIP.clear(); + } + } - } - - public void run() { - if ( !Thread.currentThread().getName().equals( "DiscoveryAdvertisement daemon" ) ) { - throw new RuntimeException( "No right to call this method" ); + try { + this.skt.leaveGroup(InetAddress.getByName(Discovery.SSDP_IP)); + this.skt.close(); + } catch ( Exception ex ) { + // ignoring + } } - inService = true; - while ( inService ) { - try { - listenBroadCast(); - } catch ( SocketTimeoutException ex ) { - // ignoring - } catch ( IOException ioEx ) { - log.error( "IO Exception during UPNP DiscoveryAdvertisement messages listening thread", ioEx ); - } catch( Exception ex ) { - log.error( "Fatal Error during UPNP DiscoveryAdvertisement messages listening thread, thread will exit", ex ); - inService = false; - aliveRegistered.clear(); - byeByeRegistered.clear(); - USNPerIP.clear(); - } + + private void listenBroadCast() throws IOException { + + this.skt.receive(this.input); + InetAddress from = this.input.getAddress(); + String received = new String(this.input.getData(), this.input.getOffset(), this.input.getLength()); + HttpResponse msg = null; + try { + msg = new HttpResponse(received); + } catch ( IllegalArgumentException ex ) { + // crappy http sent + if ( log.isDebugEnabled() ) { + log.debug("Skipping uncompliant HTTP message " + received); + } + return; + } + String header = msg.getHeader(); + if ( header != null && header.startsWith("NOTIFY") ) { + if ( log.isDebugEnabled() ) { + log.debug(received); + } + String ntsField = msg.getHTTPHeaderField("nts"); + if ( ntsField == null || ntsField.trim().length() == 0 ) { + if ( log.isDebugEnabled() ) { + log.debug("Skipping SSDP message, missing HTTP header 'ntsField' field"); + } + return; + } + if ( ntsField.equals(NTS_SSDP_ALIVE) ) { + String deviceDescrLoc = msg.getHTTPHeaderField("location"); + if ( deviceDescrLoc == null || deviceDescrLoc.trim().length() == 0 ) { + if ( log.isDebugEnabled() ) { + log.debug("Skipping SSDP message, missing HTTP header 'location' field"); + } + return; + } + URL loc = new URL(deviceDescrLoc); + if ( MATCH_IP ) { + InetAddress locHost = InetAddress.getByName(loc.getHost()); + if ( !from.equals(locHost) ) { + log.warn("Discovery message sender IP " + + from + + " does not match device description IP " + + locHost + + " skipping message, set the net.yacy.upnp.ddos.matchip system property" + + " to false to avoid this check"); + return; + } + } + + String nt = msg.getHTTPHeaderField("nt"); + if ( nt == null || nt.trim().length() == 0 ) { + if ( log.isDebugEnabled() ) { + log.debug("Skipping SSDP message, missing HTTP header 'nt' field"); + } + return; + } + String maxAge = msg.getHTTPFieldElement("Cache-Control", "max-age"); + if ( maxAge == null || maxAge.trim().length() == 0 ) { + if ( log.isDebugEnabled() ) { + log.debug("Skipping SSDP message, missing HTTP header 'max-age' field"); + } + return; + } + String usn = msg.getHTTPHeaderField("usn"); + if ( usn == null || usn.trim().length() == 0 ) { + if ( log.isDebugEnabled() ) { + log.debug("Skipping SSDP message, missing HTTP header 'usn' field"); + } + return; + } + + this.USNPerIP.put(usn, from); + String udn = usn; + int index = udn.indexOf("::"); + if ( index != -1 ) { + udn = udn.substring(0, index); + } + synchronized ( this.REGISTRATION_PROCESS ) { + Set handlers = this.aliveRegistered.get(NT_ALL_EVENTS); + if ( handlers != null ) { + for ( DiscoveryEventHandler eventHandler : handlers ) { + eventHandler.eventSSDPAlive(usn, udn, nt, maxAge, loc); + } + } + handlers = this.aliveRegistered.get(nt); + if ( handlers != null ) { + for ( DiscoveryEventHandler eventHandler : handlers ) { + eventHandler.eventSSDPAlive(usn, udn, nt, maxAge, loc); + } + } + } + } else if ( ntsField.equals(NTS_SSDP_BYE_BYE) ) { + String usn = msg.getHTTPHeaderField("usn"); + if ( usn == null || usn.trim().length() == 0 ) { + if ( log.isDebugEnabled() ) { + log.debug("Skipping SSDP message, missing HTTP header 'usn' field"); + } + return; + } + String nt = msg.getHTTPHeaderField("nt"); + if ( nt == null || nt.trim().length() == 0 ) { + if ( log.isDebugEnabled() ) { + log.debug("Skipping SSDP message, missing HTTP header 'nt' field"); + } + return; + } + + InetAddress originalAliveSenderIp = this.USNPerIP.get(usn); + if ( originalAliveSenderIp != null ) { + // we check that the sender ip of message for the usn + // match the sender ip of the alive message for wich the usn + // has been received + if ( !originalAliveSenderIp.equals(from) ) { + // someone else is trying to say that the usn is leaving + // since IP do not match we skip the message + return; + } + } + + String udn = usn; + int index = udn.indexOf("::"); + if ( index != -1 ) { + udn = udn.substring(0, index); + } + synchronized ( this.REGISTRATION_PROCESS ) { + Set handlers = this.byeByeRegistered.get(NT_ALL_EVENTS); + if ( handlers != null ) { + for ( DiscoveryEventHandler eventHandler : handlers ) { + eventHandler.eventSSDPByeBye(usn, udn, nt); + } + } + handlers = this.byeByeRegistered.get(nt); + if ( handlers != null ) { + for ( DiscoveryEventHandler eventHandler : handlers ) { + eventHandler.eventSSDPByeBye(usn, udn, nt); + } + } + } + } else { + log + .warn("Unvalid NTS field value (" + + ntsField + + ") received in NOTIFY message :" + + received); + } + } } - - try { - skt.leaveGroup( InetAddress.getByName( Discovery.SSDP_IP ) ); - skt.close(); - } catch ( Exception ex ) { - // ignoring - } - } - - private void listenBroadCast() throws IOException { - - skt.receive( input ); - InetAddress from = input.getAddress(); - String received = new String( input.getData(), input.getOffset(), input.getLength() ); - HttpResponse msg = null; - try { - msg = new HttpResponse( received ); - } catch (IllegalArgumentException ex ) { - // crappy http sent - if ( log.isDebugEnabled() ) log.debug( "Skipping uncompliant HTTP message " + received ); - return; - } - String header = msg.getHeader(); - if ( header != null && header.startsWith( "NOTIFY" ) ) { - if ( log.isDebugEnabled() ) log.debug( received ); - String ntsField = msg.getHTTPHeaderField( "nts" ); - if( ntsField == null || ntsField.trim().length() == 0 ) { - if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'ntsField' field" ); - return; - } - if ( ntsField.equals( NTS_SSDP_ALIVE ) ) { - String deviceDescrLoc = msg.getHTTPHeaderField( "location" ); - if( deviceDescrLoc == null || deviceDescrLoc.trim().length() == 0 ) { - if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'location' field" ); - return; - } - URL loc = new URL( deviceDescrLoc ); - if ( MATCH_IP ) { - InetAddress locHost = InetAddress.getByName( loc.getHost() ); - if ( !from.equals( locHost ) ) { - log.warn( "Discovery message sender IP " + from + - " does not match device description IP " + locHost + - " skipping message, set the net.yacy.upnp.ddos.matchip system property" + - " to false to avoid this check" ); - return; - } - } - - String nt = msg.getHTTPHeaderField( "nt" ); - if( nt == null || nt.trim().length() == 0 ) { - if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'nt' field" ); - return; - } - String maxAge = msg.getHTTPFieldElement( "Cache-Control", "max-age" ); - if( maxAge == null || maxAge.trim().length() == 0 ) { - if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'max-age' field" ); - return; - } - String usn = msg.getHTTPHeaderField( "usn" ); - if( usn == null || usn.trim().length() == 0 ) { - if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'usn' field" ); - return; - } - - USNPerIP.put( usn, from ); - String udn = usn; - int index = udn.indexOf( "::" ); - if ( index != -1 ) udn = udn.substring( 0, index ); - synchronized( REGISTRATION_PROCESS ) { - Set handlers = aliveRegistered.get( NT_ALL_EVENTS ); - if ( handlers != null ) { - for ( Iterator i = handlers.iterator(); i.hasNext(); ) { - DiscoveryEventHandler eventHandler = i.next(); - eventHandler.eventSSDPAlive( usn, udn, nt, maxAge, loc ); - } - } - handlers = aliveRegistered.get( nt ); - if ( handlers != null ) { - for ( Iterator i = handlers.iterator(); i.hasNext(); ) { - DiscoveryEventHandler eventHandler = i.next(); - eventHandler.eventSSDPAlive( usn, udn, nt, maxAge, loc ); - } - } - } - } else if ( ntsField.equals( NTS_SSDP_BYE_BYE ) ) { - String usn = msg.getHTTPHeaderField( "usn" ); - if( usn == null || usn.trim().length() == 0 ) { - if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'usn' field" ); - return; - } - String nt = msg.getHTTPHeaderField( "nt" ); - if( nt == null || nt.trim().length() == 0 ) { - if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'nt' field" ); - return; - } - - InetAddress originalAliveSenderIp = USNPerIP.get( usn ); - if ( originalAliveSenderIp != null ) { - // we check that the sender ip of message for the usn - // match the sender ip of the alive message for wich the usn - // has been received - if ( !originalAliveSenderIp.equals( from ) ) { - // someone else is trying to say that the usn is leaving - // since IP do not match we skip the message - return; - } - } - - String udn = usn; - int index = udn.indexOf( "::" ); - if ( index != -1 ) udn = udn.substring( 0, index ); - synchronized( REGISTRATION_PROCESS ) { - Set handlers = byeByeRegistered.get( NT_ALL_EVENTS ); - if ( handlers != null ) { - for ( Iterator i = handlers.iterator(); i.hasNext(); ) { - DiscoveryEventHandler eventHandler = i.next(); - eventHandler.eventSSDPByeBye( usn, udn, nt ); - } - } - handlers = byeByeRegistered.get( nt ); - if ( handlers != null ) { - for ( Iterator i = handlers.iterator(); i.hasNext(); ) { - DiscoveryEventHandler eventHandler = i.next(); - eventHandler.eventSSDPByeBye( usn, udn, nt ); - } - } - } - } else { - log.warn( "Unvalid NTS field value (" + ntsField + ") received in NOTIFY message :" + received ); - } - } - } }