org.ice4j.ice.harvest.UPNPHarvester Maven / Gradle / Ivy
/*
* ice4j, the OpenSource Java Solution for NAT and Firewall Traversal.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ice4j.ice.harvest;
import java.net.*;
import java.util.*;
import java.util.logging.*;
import org.ice4j.*;
import org.ice4j.ice.*;
import org.ice4j.socket.*;
import org.bitlet.weupnp.*;
/**
* Implements a CandidateHarvester which gathers Candidates
* for a specified {@link Component} using UPnP.
*
* @author Sebastien Vincent
*/
public class UPNPHarvester
extends AbstractCandidateHarvester
{
/**
* The logger.
*/
private static final Logger logger =
Logger.getLogger(UPNPHarvester.class.getName());
/**
* Maximum port to try to allocate.
*/
private static final int MAX_RETRIES = 5;
/**
* ST search field for WANIPConnection.
*/
private static final String stIP =
"urn:schemas-upnp-org:service:WANIPConnection:1";
/**
* ST search field for WANPPPConnection.
*/
private static final String stPPP =
"urn:schemas-upnp-org:service:WANPPPConnection:1";
/**
* Synchronization object.
*/
private final Object rootSync = new Object();
/**
* Gateway device.
*/
private GatewayDevice device = null;
/**
* Number of UPnP discover threads that have finished.
*/
private int finishThreads = 0;
/**
* Gathers UPnP candidates for all host Candidates that are
* already present in the specified component. This method relies
* on the specified component to already contain all its host
* candidates so that it would resolve them.
*
* @param component the {@link Component} that we'd like to gather candidate
* UPnP Candidates for
* @return the LocalCandidates gathered by this
* CandidateHarvester
*/
public synchronized Collection harvest(Component component)
{
Collection candidates = new HashSet<>();
int retries = 0;
logger.fine("Begin UPnP harvesting");
try
{
if (device == null)
{
// do it only once
if (finishThreads == 0)
{
try
{
UPNPThread wanIPThread = new UPNPThread(stIP);
UPNPThread wanPPPThread = new UPNPThread(stPPP);
wanIPThread.start();
wanPPPThread.start();
synchronized(rootSync)
{
while (finishThreads != 2)
{
rootSync.wait();
}
}
if (wanIPThread.getDevice() != null)
{
device = wanIPThread.getDevice();
}
else if (wanPPPThread.getDevice() != null)
{
device = wanPPPThread.getDevice();
}
}
catch(Throwable e)
{
logger.info("UPnP discovery failed: " + e);
}
}
if (device == null)
return candidates;
}
InetAddress localAddress = device.getLocalAddress();
String externalIPAddress = device.getExternalIPAddress();
PortMappingEntry portMapping = new PortMappingEntry();
IceSocketWrapper socket = new IceUdpSocketWrapper(
new MultiplexingDatagramSocket(0, localAddress));
int port = socket.getLocalPort();
int externalPort = socket.getLocalPort();
while (retries < MAX_RETRIES)
{
if (!device.getSpecificPortMappingEntry(port, "UDP",
portMapping))
{
if (device.addPortMapping(
externalPort,
port,
localAddress.getHostAddress(),
"UDP",
"ice4j.org: " + port))
{
List cands = createUPNPCandidate(socket,
externalIPAddress, externalPort, component, device);
logger.info("Add UPnP port mapping: " +
externalIPAddress + " " + externalPort);
// we have to add the UPNPCandidate and also the base.
// if we don't add the base, we won't be able to add
// peer reflexive candidate if someone contact us on the
// UPNPCandidate
for (LocalCandidate cand : cands)
{
//try to add the candidate to the component and then
//only add it to the harvest not redundant
if (component.addLocalCandidate(cand))
{
candidates.add(cand);
}
}
break;
}
else
{
port++;
}
}
else
{
port++;
}
retries++;
}
}
catch(Throwable e)
{
logger.info("Exception while gathering UPnP candidates: " + e);
}
return candidates;
}
/**
* Create a UPnP candidate.
*
* @param socket local socket
* @param externalIP external IP address
* @param port local port
* @param component parent component
* @param device the UPnP gateway device
* @return a new UPNPCandidate instance which
* represents the specified TransportAddress
*/
private List createUPNPCandidate(IceSocketWrapper socket,
String externalIP, int port, Component component, GatewayDevice device)
{
List ret = new ArrayList<>();
TransportAddress addr
= new TransportAddress(externalIP, port, Transport.UDP);
HostCandidate base = new HostCandidate(socket, component);
UPNPCandidate candidate
= new UPNPCandidate(addr, base, component, device);
IceSocketWrapper stunSocket = candidate.getStunSocket(null);
candidate.getStunStack().addSocket(stunSocket);
ComponentSocket componentSocket = component.getComponentSocket();
if (componentSocket != null)
{
componentSocket.add(candidate.getCandidateIceSocketWrapper());
}
ret.add(candidate);
ret.add(base);
return ret;
}
/**
* UPnP discover thread.
*/
private class UPNPThread
extends Thread
{
/**
* Gateway device.
*/
private GatewayDevice device = null;
/**
* ST search field.
*/
private final String st;
/**
* Constructor.
*
* @param st ST search field
*/
public UPNPThread(String st)
{
this.st = st;
}
/**
* Returns gateway device.
*
* @return gateway device
*/
public GatewayDevice getDevice()
{
return device;
}
/**
* Thread Entry point.
*/
public void run()
{
try
{
GatewayDiscover gd = new GatewayDiscover(st);
gd.discover();
if (gd.getValidGateway() != null)
{
device = gd.getValidGateway();
}
}
catch(Throwable e)
{
logger.info("Failed to harvest UPnP: " + e);
/*
* The Javadoc on ThreadDeath says: If ThreadDeath is caught by
* a method, it is important that it be rethrown so that the
* thread actually dies.
*/
if (e instanceof ThreadDeath)
throw (ThreadDeath)e;
}
finally
{
synchronized(rootSync)
{
finishThreads++;
rootSync.notify();
}
}
}
}
/**
* Returns a String representation of this harvester containing its
* name.
*
* @return a String representation of this harvester containing its
* name.
*/
@Override
public String toString()
{
return getClass().getSimpleName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy