package net.java.sip.communicator.impl.netaddr;
import net.java.stun4j.*;
import net.java.stun4j.attribute.*;
import net.java.stun4j.message.*;
import net.java.stun4j.stack.*;
/**
* <p>
* This class implements the STUN Discovery Process as described by section 10.1
* of rfc 3489.
* </p><p>
* The flow makes use of three tests. In test I, the client sends a
* STUN Binding Request to a server, without any flags set in the
* CHANGE-REQUEST attribute, and without the RESPONSE-ADDRESS attribute.
* This causes the server to send the response back to the address and
* port that the request came from. In test II, the client sends a
* Binding Request with both the "change IP" and "change port" flags
* from the CHANGE-REQUEST attribute set. In test III, the client sends
* a Binding Request with only the "change port" flag set.
* </p><p>
* The client begins by initiating test I. If this test yields no
* response, the client knows right away that it is not capable of UDP
* connectivity. If the test produces a response, the client examines
* the MAPPED-ADDRESS attribute. If this address and port are the same
* as the local IP address and port of the socket used to send the
* request, the client knows that it is not natted. It executes test
* II.
* </p><p>
* If a response is received, the client knows that it has open access
* to the Internet (or, at least, its behind a firewall that behaves
* like a full-cone NAT, but without the translation). If no response
* is received, the client knows its behind a symmetric UDP firewall.
* </p><p>
* In the event that the IP address and port of the socket did not match
* the MAPPED-ADDRESS attribute in the response to test I, the client
* knows that it is behind a NAT. It performs test II. If a response
* is received, the client knows that it is behind a full-cone NAT. If
* no response is received, it performs test I again, but this time,
* does so to the address and port from the CHANGED-ADDRESS attribute
* from the response to test I. If the IP address and port returned in
* the MAPPED-ADDRESS attribute are not the same as the ones from the
* first test I, the client knows its behind a symmetric NAT. If the
* address and port are the same, the client is either behind a
* restricted or port restricted NAT. To make a determination about
* which one it is behind, the client initiates test III. If a response
* is received, its behind a restricted NAT, and if no response is
* received, its behind a port restricted NAT.
* </p><p>
* This procedure yields substantial information about the operating
* condition of the client application. In the event of multiple NATs
* between the client and the Internet, the type that is discovered will
* be the type of the most restrictive NAT between the client and the
* Internet. The types of NAT, in order of restrictiveness, from most
* to least, are symmetric, port restricted cone, restricted cone, and
* full cone.
* </p><p>
* Typically, a client will re-do this discovery process periodically to
* detect changes, or look for inconsistent results. It is important to
* note that when the discovery process is redone, it should not
* generally be done from the same local address and port used in the
* previous discovery process. If the same local address and port are
* reused, bindings from the previous test may still be in existence,
* and these will invalidate the results of the test. Using a different
* local address and port for subsequent tests resolves this problem.
* An alternative is to wait sufficiently long to be confident that the
* old bindings have expired (half an hour should more than suffice).
* </p><p>
* <p>Organisation: <p> Louis Pasteur University, Strasbourg, France</p>
* <p>Network Research Team (http://www-r2.u-strasbg.fr)</p></p>
* @author Emil Ivov
* @version 0.1
*/
public class StunClient
{
/**
* Indicates whether the underlying stack has been initialized and started
* and that the discoverer is operational.
*/
private boolean started = false;
/**
* The stack to use for STUN communication.
*/
private StunStack stunStack = null;
/**
* The provider to send our messages through
*/
private StunProvider stunProvider = null;
/**
* The point where we'll be listening.
*/
private NetAccessPointDescriptor apDescriptor = null;
/**
* The address of the stun server
*/
private StunAddress serverAddress = null;
/**
* A utility used to flatten the multithreaded architecture of the Stack
* and execute the discovery process in a synchronized manner
*/
private BlockingRequestSender requestSender = null;
/**
* Creates a StunAddressDiscoverer. In order to use it one must start the
* discoverer.
* @param localAddress the address where the stach should bind.
*/
public StunClient(StunAddress localAddress)
{
apDescriptor = new NetAccessPointDescriptor(localAddress);
}
/**
* Creates a StunAddressDiscoverer. In order to use it one must start the
* discoverer.
* @param apDescriptor the address where the stach should bind.
* @param serverAddress the address of the server to interrogate.
*/
public StunClient(NetAccessPointDescriptor apDescriptor,
StunAddress serverAddress)
{
this.apDescriptor = apDescriptor;
this.serverAddress = serverAddress;
}
/**
* Shuts down the underlying stack and prepares the object for garbage
* collection.
*/
public void shutDown()
{
// try
// {
// stunStack.removeNetAccessPoint(apDescriptor);
// }
// catch (StunException ex)
// {
// logger.warn("Failed to remove NetAP: "+apDescriptor.toString(), ex);
// }
// stunStack = null;
// stunProvider = null;
// apDescriptor = null;
// requestSender = null;
//
// this.started = false;
}
/**
* Puts the discoverer into an operational state.
* @throws StunException if we fail to bind or some other error occurs.
*/
public void start() throws StunException
{
// stunStack = StunStack.getInstance();
// //stunStack.start();
//
// stunStack.installNetAccessPoint(apDescriptor);
//
// stunProvider = stunStack.getProvider();
//
// requestSender = new BlockingRequestSender(stunProvider, apDescriptor);
//
// started = true;
}
/**
* Sends a binding request to the specified server address. Both change IP
* and change port flags are set to false.
* @param serverAddress the address where to send the bindingRequest.
* @return The returned message encapsulating event or null if no message
* was received.
* @throws StunException if an exception occurs while sending the messge
*/
public StunMessageEvent doStunTestI(StunAddress serverAddress) throws
StunException
{
Request request = MessageFactory.createBindingRequest();
ChangeRequestAttribute changeRequest =
(ChangeRequestAttribute) request.getAttribute(Attribute.
CHANGE_REQUEST);
changeRequest.setChangeIpFlag(false);
changeRequest.setChangePortFlag(false);
StunMessageEvent evt = null;
// requestSender.sendRequestAndWaitForResponse(request, serverAddress);
return evt;
}
/**
* Sends a binding request to the specified server address with both change
* IP and change port flags are set to true.
* @param serverAddress the address where to send the bindingRequest.
* @return The returned message encapsulating event or null if no message
* was received.
* @throws StunException if an exception occurs while sending the messge
*/
public StunMessageEvent doStunTestII(StunAddress serverAddress) throws
StunException
{
Request request = MessageFactory.createBindingRequest();
ChangeRequestAttribute changeRequest =
(ChangeRequestAttribute) request.getAttribute(Attribute.
CHANGE_REQUEST);
changeRequest.setChangeIpFlag(true);
changeRequest.setChangePortFlag(true);
StunMessageEvent evt = null;
// requestSender.sendRequestAndWaitForResponse(request, serverAddress);
return evt;
}
/**
* Sends a binding request to the specified server address with only change
* port flag set to true and change IP flag - to false.
* @param serverAddress the address where to send the bindingRequest.
* @return The returned message encapsulating event or null if no message
* was received.
* @throws StunException if an exception occurs while sending the messge
*/
public StunMessageEvent doStunTestIII(StunAddress serverAddress) throws
StunException
{
Request request = MessageFactory.createBindingRequest();
ChangeRequestAttribute changeRequest = (ChangeRequestAttribute) request.
getAttribute(Attribute.CHANGE_REQUEST);
changeRequest.setChangeIpFlag(false);
changeRequest.setChangePortFlag(true);
StunMessageEvent evt = null;
// requestSender.sendRequestAndWaitForResponse(request, serverAddress);
return evt;
}
/**
* Makes shure the discoverer is operational and throws an
* StunException.ILLEGAL_STATE if that is not the case.
* @throws StunException ILLEGAL_STATE if the discoverer is not operational.
*/
private void checkStarted() throws StunException
{
if (!started)
throw new StunException(StunException.ILLEGAL_STATE,
"The Discoverer must be started before "
+ "launching the discovery process!");
}
}
/**
* Sample run results.
*
* TEST I res=/69.0.209.22:3478 - stun01bak.sipphone.com
* mapped address is=193.108.24.226./193.108.24.226:5678, name=193.108.24.226.
* backup server address is=69.0.208.27./69.0.208.27:3478, name=69.0.208.27.
* NO RESPONSE received to Test II.
* TEST I res=/69.0.208.27:3478 - stun01.sipphone.com
* NO RESPONSE received to Test III.
* The detected network configuration is: Port Restricted Cone NAT
* Your mapped public address is: 193.108.24.226./193.
*/