Package ants.p2p.utils.net

Source Code of ants.p2p.utils.net.UPnPManager$StaleCleaner

//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


package ants.p2p.utils.net;

import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.Random;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cybergarage.upnp.Action;
import org.cybergarage.upnp.Argument;
import org.cybergarage.upnp.ControlPoint;
import org.cybergarage.upnp.Device;
import org.cybergarage.upnp.DeviceList;
import org.cybergarage.upnp.Service;
import org.cybergarage.upnp.device.DeviceChangeListener;
import org.cybergarage.upnp.control.*;

import ants.p2p.gui.FrameAnt;
import org.apache.log4j.*;
import org.cybergarage.upnp.StateVariable;
import java.io.File;
import org.cybergarage.upnp.device.InvalidDescriptionException;
import org.cybergarage.upnp.ServiceList;
import ants.p2p.filesharing.WarriorAnt;

/**
*
* According to the UPnP Standards, Internet Gateway Devices must have a
* specific hierarchy.  The parts of that hierarchy that we care about are:
*
* Device: urn:schemas-upnp-org:device:InternetGatewayDevice:1
*    SubDevice: urn:schemas-upnp-org:device:WANDevice:1
*     SubDevice: urn:schemas-upnp-org:device:WANConnectionDevice:1
*        Service: urn:schemas-upnp-org:service:WANIPConnection:1
*
* Every port mapping is a tuple of:
*  - External address ("" is wildcard)
*  - External port
*  - Internal address
*  - Internal port
*  - Protocol (TCP|UDP)
*  - Description
*
* Port mappings can be removed, but that is a blocking network operation which will
* slow down the shutdown process of Limewire.  It is safe to let port mappings persist
* between limewire sessions. In the meantime however, the NAT may assign a different
* ip address to the local node.  That's why we need to find any previous mappings
* the node has created and update them with our new address. In order to uniquely
* distinguish which mappings were made by us, we put part of our client GUID in the
* description field.
*
* For the TCP mapping, we use the following description: "ANts/TCP:<cliengGUID>"
*
* NOTES:
*
* Not all NATs support mappings with different external port and internal ports. Therefore
* if we were unable to map our desired port but were able to map another one, we should
* pass this information on to Acceptor.
*
* Some buggy NATs do not distinguish mappings by the Protocol field.  Therefore
* we first map the UDP port, and then the TCP port since it is more important should the
* first mapping get overwritten.
*
* The cyberlink library uses an internal thread that tries to discover any UPnP devices.
* After we discover a router or give up on trying to, we should call stop().
*
*/
public class UPnPManager extends ControlPoint implements DeviceChangeListener, ActionListener, QueryListener {

  private static Logger _logger = Logger.getLogger(UPnPManager.class.getName());

  private final static String DESCRIPTION_FILE_NAME = WarriorAnt.workingPath+"upnpdescriptor/description.xml";
  private final static String PRESENTATION_URI = "http://antsp2p.sourceforge.net";

  public final static String ANTS_DEVICE_TYPE = "urn:schemas-upnp-org:device:antsp2p:1";
  public final static String ANTS_LAN_ADDRESS_SERVICE_TYPE = "urn:schemas-upnp-org:service:address:1";
  public final static String ANTS_LAN_ADDRESS_ACTION_TYPE = "GetLANAddress";

  private StateVariable serverInfo;

  private Device antsDev = null;

    /** some schemas */
    private static final String ROUTER_DEVICE=
            "urn:schemas-upnp-org:device:InternetGatewayDevice:1";
    private static final String WAN_DEVICE =
            "urn:schemas-upnp-org:device:WANDevice:1";
    private static final String WANCON_DEVICE=
            "urn:schemas-upnp-org:device:WANConnectionDevice:1";
    private static final String SERVICE_TYPE =
            "urn:schemas-upnp-org:service:WANIPConnection:1";

    /** prefixes and a suffix for the descriptions of our TCP and UDP mappings */
    private static final String TCP_PREFIX = "ANtsTCP";
    //private static final String UDP_PREFIX = "ANtsUDP";
    private String _guidSuffix;

    private static UPnPManager INSTANCE = null;

    public static synchronized UPnPManager instance() {
        if(INSTANCE == null){
          UPnPDescriptor.createDirectoryStructure();
          return INSTANCE = new UPnPManager();
        }else{
          return INSTANCE;
        }
    }

    /**
     * the router we have and the sub-device necessary for port mapping
     *  LOCKING: this
     */
    private Device _router;

    /**
     * The port-mapping service we'll use.  LOCKING: this
     */
    private Service _service;

    /** The tcp and udp mappings created this session */
    private Mapping _tcp;//, _udp;

    private UPnPManager(){
        super();
        _logger.info("Starting UPnP Service Manager.");
        try{
            addDeviceChangeListener(this);
            start();
        }catch(Exception bad) {
            bad.printStackTrace();
        }
    }

    public Device getDevice(){
      return this.antsDev;
    }

    public void setCurrentLanAddress(ants.p2p.query.ServerInfo si){
      try{
        if (si == null || this.serverInfo == null)
          return;
        this.serverInfo.setValue(si.toString());
      }catch(Exception e){
        _logger.error("Error in setting UPnP LAN address", e);
      }
    }

    public ants.p2p.query.ServerInfo getCurrentLanAddress(){
      try{
        if(this.serverInfo == null) return null;
        String[] addressPort = this.serverInfo.getValue().split(":");
        ants.p2p.query.ServerInfo si = new ants.p2p.query.ServerInfo("", addressPort[0], new Integer(addressPort[1]),"");
        return si;
      }catch(Exception e){
        _logger.error("Error in retrieving UPnP LAN address", e);
        return null;
      }
    }

    public void startDevice(){
      stopDevice();
      try {
        antsDev = new Device(DESCRIPTION_FILE_NAME);

        Action getTimeAction = antsDev.getAction("GetLANAddress");
        getTimeAction.setActionListener(this);

        ServiceList serviceList = antsDev.getServiceList();
        Service service = serviceList.getService(0);
        service.setQueryListener(this);

        serverInfo = antsDev.getStateVariable("LANAddress");

        antsDev.setLeaseTime(60);
        antsDev.start();
      }
      catch (InvalidDescriptionException e) {
        _logger.error("Invalid description file for ANtsP2P UPnP Service", e);
      }
    }

    public void stopDevice(){
      if(antsDev != null) {
        antsDev.byebye();
        antsDev.stop();
        this.antsDev = null;
        this.serverInfo = null;
      }
    }

    public boolean queryControlReceived(StateVariable stateVar){
            //MUST SET THE LAN ADDRESS HERE (SERVER INFO)!!!!
            stateVar.setValue(serverInfo.getValue());
            return true;
    }

    public boolean actionControlReceived(Action action){
            String actionName = action.getName();
            if (actionName.equals("GetLANAddress") == true) {
                    Argument timeArg = action.getArgument("LANAddress");
                    timeArg.setValue(serverInfo.getValue());
                    return true;
            }
            return false;
    }

    public void update(String newValue) {
            serverInfo.setValue(newValue);
    }

    /**
     * @return whether we are behind an UPnP-enabled NAT/router
     */
    public synchronized boolean isNATPresent() {
            return _router != null && _service != null;
    }

    /**
     * @return whether we have created mappings this session
     */
    public /*synchronized*/ boolean mappingsExist() {
            return _tcp != null /*&& _udp != null*/;
    }

    /**
     * @return the external address the NAT thinks we have.  Blocking.
     * null if we can't find it.
     */
    public InetAddress getNATAddress() throws UnknownHostException {
            Action getIP;

        synchronized(this) {
                if (!isNATPresent())
                        return null;
                getIP = _service.getAction("GetExternalIPAddress");
        }

        if(getIP == null) {
            _logger.info("Couldn't find GetExternalIPAddress action!");
            return null;
        }


        if (!getIP.postControlAction()) {
                _logger.info("couldn't get our external address");
                return null;
        }

        Argument ret = getIP.getOutputArgumentList().getArgument("NewExternalIPAddress");
        return InetAddress.getByName(ret.getValue());
    }

    /**
     * this method will be called when we discover a UPnP device.
     */
    public synchronized void deviceAdded(Device dev) {
        _logger.info("Device added: " + dev.getFriendlyName());

            // we've found what we need
            if (_service != null && _router != null) {
                    _logger.info("we already have a router");
                    return;
            }

            // did we find a router?
            if (dev.getDeviceType().equals(ROUTER_DEVICE) && dev.isRootDevice())
                    _router = dev;

            if (_router == null) {
                    _logger.info("didn't get router device");
                    return;
            }

            discoverService();

            // did we find the service we need?
            if (_service == null) {
                    _logger.info("couldn't find service");
                    _router=null;
            } else {
                    _logger.info("Found service, router: " + _router.getFriendlyName() + ", service: " + _service);
                    //stop();
            }
  }

  /**
   * Traverses the structure of the router device looking for
   * the port mapping service.
   */
  private void discoverService() {

    for (Iterator iter = _router.getDeviceList().iterator();iter.hasNext();) {
      Device current = (Device)iter.next();
      if (!current.getDeviceType().equals(WAN_DEVICE))
        continue;

      DeviceList l = current.getDeviceList();
        _logger.info("found "+current.getDeviceType()+", size: "+l.size() + ", on: " + current.getFriendlyName());

      for (int i=0;i<current.getDeviceList().size();i++) {
        Device current2 = l.getDevice(i);

        if (!current2.getDeviceType().equals(WANCON_DEVICE))
          continue;

          _logger.info("found "+current2.getDeviceType() + ", on: " + current2.getFriendlyName());

        _service = current2.getService(SERVICE_TYPE);
        return;
      }
    }
  }

  /**
   * adds a mapping on the router to the specified port
   * @return the external port that was actually mapped. 0 if failed
   */
  public int mapPort(String address, int port) {
          _logger.info("Attempting to map port: " + port);

    Random gen=null;
               
                String localAddress = address;
    int localPort = port;

    // try adding new mappings with the same port
    /*Mapping udp = new Mapping("",
        port,
        localAddress,
        localPort,
        "UDP",
        UDP_PREFIX + getGUIDSuffix());

    // add udp first in case it gets overwritten.
    // if we can't add, update or find an appropriate port
    // give up after 20 tries
    int tries = 20;
    while (!addMapping(udp)) {
      if (tries<0)
        break;
      tries--;

      // try a random port
      if (gen == null)
        gen = new Random();
      port = gen.nextInt(50000)+2000;
      udp = new Mapping("",
          port,
          localAddress,
          localPort,
          "UDP",
          UDP_PREFIX + getGUIDSuffix());
    }

    if (tries < 0) {
      _logger.info("couldn't map a port :(");
      return 0;
    }*/

    // at this stage, the variable port will point to the port the UDP mapping
    // got mapped to.  Since we have to have the same port for UDP and tcp,
    // we can't afford to change the port here.  So if mapping to this port on tcp
    // fails, we give up and clean up the udp mapping.
    Mapping tcp = new Mapping("",
        port,
        localAddress,
        localPort,
        "TCP",
        TCP_PREFIX + getGUIDSuffix());
    if (!addMapping(tcp)) {
      _logger.info(" couldn't map tcp to whatever udp was mapped. cleaning up...");
      port = 0;
      tcp = null;
      //udp = null;
    }

    // save a ref to the mappings
    synchronized(this) {
      _tcp = tcp;
      //_udp = udp;
    }

    // we're good - start a thread to clean up any potentially stale mappings
    Thread staleCleaner = new Thread(new StaleCleaner());
    staleCleaner.setDaemon(true);
    staleCleaner.setName("Stale Mapping Cleaner");
    staleCleaner.start();

    return port;
  }

  /**
   * @param m Port mapping to send to the NAT
   * @return the error code
   */
  private boolean addMapping(Mapping m) {

      _logger.info("adding "+m);

    Action add;
    synchronized(this) {
      add = _service.getAction("AddPortMapping");
    }

    if(add == null) {
        _logger.info("Couldn't find AddPortMapping action!");
        return false;
    }


    add.setArgumentValue("NewRemoteHost",m._externalAddress);
    add.setArgumentValue("NewExternalPort",m._externalPort);
    add.setArgumentValue("NewInternalClient",m._internalAddress);
    add.setArgumentValue("NewInternalPort",m._internalPort);
    add.setArgumentValue("NewProtocol",m._protocol);
    add.setArgumentValue("NewPortMappingDescription",m._description);
    add.setArgumentValue("NewEnabled","1");
    add.setArgumentValue("NewLeaseDuration",0);

    boolean success = add.postControlAction();
        _logger.info("Post succeeded: " + success);
    return success;
  }

  /**
   * @param m the mapping to remove from the NAT
   * @return whether it worked or not
   */
  private boolean removeMapping(Mapping m) {

      _logger.info("removing "+m);

    Action remove;
    synchronized(this) {
      remove = _service.getAction("DeletePortMapping");
    }

    if(remove == null) {
        _logger.info("Couldn't find DeletePortMapping action!");
        return false;
      }

    remove.setArgumentValue("NewRemoteHost",m._externalAddress);
    remove.setArgumentValue("NewExternalPort",m._externalPort);
    remove.setArgumentValue("NewProtocol",m._protocol);

    boolean success = remove.postControlAction();
        _logger.info("Remove succeeded: " + success);
    return success;
  }

  /**
   * schedules a shutdown hook which will clear the mappings created
   * this session.
   */
  public void clearMappingsOnShutdown() {
    final Mapping tcp/*, udp*/;
    synchronized(this) {
      tcp = _tcp;
      //udp = _udp;
    }

    final Thread cleaner = new Thread() {
      public void run() {
        _logger.info("start cleaning");
        removeMapping(tcp);
        //removeMapping(udp);
        _logger.info("done cleaning");
      }
    };

    Thread waiter = new Thread() {
        public void run() {
            try{
                _logger.info("waiting for UPnP cleaners to finish");
                cleaner.join(30000); // wait at most 30 seconds.
            }catch(InterruptedException ignored){}
            _logger.info("UPnP cleaners done");
        }
    };
    waiter.setName("shutdown mapping waiter");

    try {
        Runtime.getRuntime().addShutdownHook(waiter);
    } catch (IllegalStateException ignored){}

    cleaner.setName("shutdown mapping cleaner");
    cleaner.setDaemon(true);
    cleaner.start();
    Thread.yield(); // let it start.
  }

  public void finalize() {
    stop();
  }

  private synchronized String getGUIDSuffix() {
      if (_guidSuffix == null)
                _guidSuffix = FrameAnt.getInstance(null).getGuiAnt().getConnectionAntPanel().getWarriorAnt().getIdent().substring(0,10);
      return _guidSuffix;
  }
  /**
   * stub
   */
  public void deviceRemoved(Device dev) {}

  /**
   *  @return A non-loopback IPv4 address of a network interface on the
         * local host.
         * @throws UnknownHostException
         */
     public static InetAddress getLocalAddress()
         throws UnknownHostException {
            InetAddress addr = InetAddress.getLocalHost();
            if (addr instanceof Inet4Address && !addr.isLoopbackAddress())
                return addr;

            try {
               Enumeration interfaces =
                   NetworkInterface.getNetworkInterfaces();

               if (interfaces != null) {
                   while (interfaces.hasMoreElements()) {
                       Enumeration addresses =
                            ((NetworkInterface)interfaces.nextElement()).getInetAddresses();
                       while (addresses.hasMoreElements()) {
         addr = (InetAddress) addresses.nextElement();
                           if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
                               return addr;
                           }
                       }
       }
         }
            } catch (SocketException se) {}

             throw new UnknownHostException(
               "localhost has no interface with a non-loopback IPv4 address");
     }

  private final class Mapping {
    public final String _externalAddress;
    public final int _externalPort;
    public final String _internalAddress;
    public final int _internalPort;
    public final String _protocol,_description;

    // network constructor
    public Mapping(String externalAddress,String externalPort,
        String internalAddress, String internalPort,
        String protocol, String description) throws NumberFormatException{
      _externalAddress=externalAddress;
      _externalPort=Integer.parseInt(externalPort);
      _internalAddress=internalAddress;
      _internalPort=Integer.parseInt(internalPort);
      _protocol=protocol;
      _description=description;
    }

    // internal constructor
    public Mapping(String externalAddress,int externalPort,
        String internalAddress, int internalPort,
        String protocol, String description) {
      _externalAddress=externalAddress;
      _externalPort=externalPort;
      _internalAddress=internalAddress;
      _internalPort=internalPort;
      _protocol=protocol;
      _description=description;
    }

    public String toString() {
      return _externalAddress+":"+_externalPort+"->"+_internalAddress+":"+_internalPort+
        "@"+_protocol+" desc: "+_description;
    }

  }

  /**
   * This thread reads all the existing mappings on the NAT and if it finds
   * a mapping which appears to be created by us but points to a different
   * address (i.e. is stale) it removes the mapping.
   *
   * It can take several minutes to finish, depending on how many mappings there are.
   */
  private class StaleCleaner implements Runnable {

      // TODO: remove
      private String list(java.util.List l) {
          String s = "";
          for(Iterator i = l.iterator(); i.hasNext(); ) {
              Argument next = (Argument)i.next();
              s += next.getName() + "->" + next.getValue() + ", ";
          }
          return s;
      }

    public void run() {

        _logger.info("Looking for stale mappings...");

      Set mappings = new HashSet();
      Action getGeneric;
      synchronized(UPnPManager.this) {
        getGeneric = _service.getAction("GetGenericPortMappingEntry");
      }

      if(getGeneric == null) {
          _logger.info("Couldn't find GetGenericPortMappingEntry action!");
          return;
      }

      // get all the mappings
      try {
        for (int i=0;;i++) {
            getGeneric.setArgumentValue("NewPortMappingIndex",i);
                _logger.info("Stale Iteration: " + i + ", generic.input: " + list(getGeneric.getInputArgumentList()) + ", generic.output: " + list(getGeneric.getOutputArgumentList()));

          if (!getGeneric.postControlAction())
            break;

          mappings.add(new Mapping(
              getGeneric.getArgumentValue("NewRemoteHost"),
              getGeneric.getArgumentValue("NewExternalPort"),
              getGeneric.getArgumentValue("NewInternalClient"),
              getGeneric.getArgumentValue("NewInternalPort"),
              getGeneric.getArgumentValue("NewProtocol"),
              getGeneric.getArgumentValue("NewPortMappingDescription")));
            // TODO: erase output arguments.

        }
      }catch(NumberFormatException bad) {
          _logger.error("NFE reading mappings!", bad);
        //router broke.. can't do anything.
        return;
      }

        _logger.info("Stale cleaner found "+mappings.size()+ " total mappings");

      // iterate and clean up
      for (Iterator iter = mappings.iterator();iter.hasNext();) {
        Mapping current = (Mapping)iter.next();
            _logger.info("Analyzing: " + current);

        if(current._description == null)
            continue;

        // does it have our description?
        if (current._description.equals(TCP_PREFIX+getGUIDSuffix())/* ||
            current._description.equals(UDP_PREFIX+getGUIDSuffix())*/) {

          // is it not the same as the mappings we created this session?
          synchronized(this) {

            if (_tcp != null && /*_udp != null &&*/
                                                    current._externalPort == _tcp._externalPort &&
                                                    current._internalAddress.equals(_tcp._internalAddress) &&
                                                    current._internalPort == _tcp._internalPort)
              continue;
          }

          // remove it.
            _logger.info("mapping "+current+" appears to be stale");
          removeMapping(current);
        }
      }
    }
  }
}
TOP

Related Classes of ants.p2p.utils.net.UPnPManager$StaleCleaner

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.
.