Unsupported library that attempts to punch holes through NAT
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DiscoveryTest.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /*
  2. * This file is part of JSTUN.
  3. *
  4. * Copyright (c) 2005 Thomas King <king@t-king.de> - All rights
  5. * reserved.
  6. *
  7. * This software is licensed under either the GNU Public License (GPL),
  8. * or the Apache 2.0 license. Copies of both license agreements are
  9. * included in this distribution.
  10. */
  11. package de.javawi.jstun.test;
  12. import java.io.IOException;
  13. import java.net.DatagramPacket;
  14. import java.net.DatagramSocket;
  15. import java.net.InetAddress;
  16. import java.net.InetSocketAddress;
  17. import java.net.SocketException;
  18. import java.net.SocketTimeoutException;
  19. import java.net.UnknownHostException;
  20. import java.util.logging.Logger;
  21. import de.javawi.jstun.attribute.ChangeRequest;
  22. import de.javawi.jstun.attribute.ChangedAddress;
  23. import de.javawi.jstun.attribute.ErrorCode;
  24. import de.javawi.jstun.attribute.MappedAddress;
  25. import de.javawi.jstun.attribute.MessageAttribute;
  26. import de.javawi.jstun.attribute.MessageAttributeException;
  27. import de.javawi.jstun.attribute.MessageAttributeParsingException;
  28. import de.javawi.jstun.header.MessageHeader;
  29. import de.javawi.jstun.header.MessageHeaderParsingException;
  30. import de.javawi.jstun.util.UtilityException;
  31. public class DiscoveryTest {
  32. private static Logger logger = Logger.getLogger("de.javawi.stun.test.DiscoveryTest");
  33. InetAddress iaddress;
  34. String stunServer;
  35. int port;
  36. int timeoutInitValue = 300; //ms
  37. MappedAddress ma = null;
  38. ChangedAddress ca = null;
  39. boolean nodeNatted = true;
  40. DatagramSocket socketTest1 = null;
  41. DiscoveryInfo di = null;
  42. public DiscoveryTest(InetAddress iaddress , String stunServer, int port) {
  43. super();
  44. this.iaddress = iaddress;
  45. this.stunServer = stunServer;
  46. this.port = port;
  47. }
  48. public DiscoveryInfo test() throws UtilityException, SocketException, UnknownHostException, IOException, MessageAttributeParsingException, MessageAttributeException, MessageHeaderParsingException{
  49. ma = null;
  50. ca = null;
  51. nodeNatted = true;
  52. socketTest1 = null;
  53. di = new DiscoveryInfo(iaddress);
  54. if (test1()) {
  55. if (test2()) {
  56. if (test1Redo()) {
  57. test3();
  58. }
  59. }
  60. }
  61. socketTest1.close();
  62. return di;
  63. }
  64. private boolean test1() throws UtilityException, SocketException, UnknownHostException, IOException, MessageAttributeParsingException, MessageHeaderParsingException {
  65. int timeSinceFirstTransmission = 0;
  66. int timeout = timeoutInitValue;
  67. while (true) {
  68. try {
  69. // Test 1 including response
  70. socketTest1 = new DatagramSocket(new InetSocketAddress(iaddress, 0));
  71. socketTest1.setReuseAddress(true);
  72. socketTest1.connect(InetAddress.getByName(stunServer), port);
  73. socketTest1.setSoTimeout(timeout);
  74. MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
  75. sendMH.generateTransactionID();
  76. ChangeRequest changeRequest = new ChangeRequest();
  77. sendMH.addMessageAttribute(changeRequest);
  78. byte[] data = sendMH.getBytes();
  79. DatagramPacket send = new DatagramPacket(data, data.length);
  80. socketTest1.send(send);
  81. logger.finer("Test 1: Binding Request sent.");
  82. MessageHeader receiveMH = new MessageHeader();
  83. while (!(receiveMH.equalTransactionID(sendMH))) {
  84. DatagramPacket receive = new DatagramPacket(new byte[200], 200);
  85. socketTest1.receive(receive);
  86. receiveMH = MessageHeader.parseHeader(receive.getData());
  87. receiveMH.parseAttributes(receive.getData());
  88. }
  89. ma = (MappedAddress) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.MappedAddress);
  90. ca = (ChangedAddress) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.ChangedAddress);
  91. ErrorCode ec = (ErrorCode) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.ErrorCode);
  92. if (ec != null) {
  93. di.setError(ec.getResponseCode(), ec.getReason());
  94. logger.config("Message header contains an Errorcode message attribute.");
  95. return false;
  96. }
  97. if ((ma == null) || (ca == null)) {
  98. di.setError(700, "The server is sending an incomplete response (Mapped Address and Changed Address message attributes are missing). The client should not retry.");
  99. logger.config("Response does not contain a Mapped Address or Changed Address message attribute.");
  100. return false;
  101. } else {
  102. di.setPublicIP(ma.getAddress().getInetAddress());
  103. if ((ma.getPort() == socketTest1.getLocalPort()) && (ma.getAddress().getInetAddress().equals(socketTest1.getLocalAddress()))) {
  104. logger.fine("Node is not natted.");
  105. nodeNatted = false;
  106. } else {
  107. logger.fine("Node is natted.");
  108. }
  109. return true;
  110. }
  111. } catch (SocketTimeoutException ste) {
  112. if (timeSinceFirstTransmission < 7900) {
  113. logger.finer("Test 1: Socket timeout while receiving the response.");
  114. timeSinceFirstTransmission += timeout;
  115. int timeoutAddValue = (timeSinceFirstTransmission * 2);
  116. if (timeoutAddValue > 1600) timeoutAddValue = 1600;
  117. timeout = timeoutAddValue;
  118. } else {
  119. // node is not capable of udp communication
  120. logger.finer("Test 1: Socket timeout while receiving the response. Maximum retry limit exceed. Give up.");
  121. di.setBlockedUDP();
  122. logger.fine("Node is not capable of UDP communication.");
  123. return false;
  124. }
  125. }
  126. }
  127. }
  128. private boolean test2() throws UtilityException, SocketException, UnknownHostException, IOException, MessageAttributeParsingException, MessageAttributeException, MessageHeaderParsingException {
  129. int timeSinceFirstTransmission = 0;
  130. int timeout = timeoutInitValue;
  131. while (true) {
  132. try {
  133. // Test 2 including response
  134. DatagramSocket sendSocket = new DatagramSocket(new InetSocketAddress(iaddress, 0));
  135. sendSocket.connect(InetAddress.getByName(stunServer), port);
  136. sendSocket.setSoTimeout(timeout);
  137. MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
  138. sendMH.generateTransactionID();
  139. ChangeRequest changeRequest = new ChangeRequest();
  140. changeRequest.setChangeIP();
  141. changeRequest.setChangePort();
  142. sendMH.addMessageAttribute(changeRequest);
  143. byte[] data = sendMH.getBytes();
  144. DatagramPacket send = new DatagramPacket(data, data.length);
  145. sendSocket.send(send);
  146. logger.finer("Test 2: Binding Request sent.");
  147. int localPort = sendSocket.getLocalPort();
  148. InetAddress localAddress = sendSocket.getLocalAddress();
  149. sendSocket.close();
  150. DatagramSocket receiveSocket = new DatagramSocket(localPort, localAddress);
  151. receiveSocket.connect(ca.getAddress().getInetAddress(), ca.getPort());
  152. receiveSocket.setSoTimeout(timeout);
  153. MessageHeader receiveMH = new MessageHeader();
  154. while(!(receiveMH.equalTransactionID(sendMH))) {
  155. DatagramPacket receive = new DatagramPacket(new byte[200], 200);
  156. receiveSocket.receive(receive);
  157. receiveMH = MessageHeader.parseHeader(receive.getData());
  158. receiveMH.parseAttributes(receive.getData());
  159. }
  160. ErrorCode ec = (ErrorCode) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.ErrorCode);
  161. if (ec != null) {
  162. di.setError(ec.getResponseCode(), ec.getReason());
  163. logger.config("Message header contains an Errorcode message attribute.");
  164. return false;
  165. }
  166. if (!nodeNatted) {
  167. di.setOpenAccess();
  168. logger.fine("Node has open access to the Internet (or, at least the node is behind a full-cone NAT without translation).");
  169. } else {
  170. di.setFullCone();
  171. logger.fine("Node is behind a full-cone NAT.");
  172. }
  173. return false;
  174. } catch (SocketTimeoutException ste) {
  175. if (timeSinceFirstTransmission < 7900) {
  176. logger.finer("Test 2: Socket timeout while receiving the response.");
  177. timeSinceFirstTransmission += timeout;
  178. int timeoutAddValue = (timeSinceFirstTransmission * 2);
  179. if (timeoutAddValue > 1600) timeoutAddValue = 1600;
  180. timeout = timeoutAddValue;
  181. } else {
  182. logger.finer("Test 2: Socket timeout while receiving the response. Maximum retry limit exceed. Give up.");
  183. if (!nodeNatted) {
  184. di.setSymmetricUDPFirewall();
  185. logger.fine("Node is behind a symmetric UDP firewall.");
  186. return false;
  187. } else {
  188. // not is natted
  189. // redo test 1 with address and port as offered in the changed-address message attribute
  190. return true;
  191. }
  192. }
  193. }
  194. }
  195. }
  196. private boolean test1Redo() throws UtilityException, SocketException, UnknownHostException, IOException, MessageAttributeParsingException, MessageHeaderParsingException{
  197. int timeSinceFirstTransmission = 0;
  198. int timeout = timeoutInitValue;
  199. while (true) {
  200. // redo test 1 with address and port as offered in the changed-address message attribute
  201. try {
  202. // Test 1 with changed port and address values
  203. socketTest1.connect(ca.getAddress().getInetAddress(), ca.getPort());
  204. socketTest1.setSoTimeout(timeout);
  205. MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
  206. sendMH.generateTransactionID();
  207. ChangeRequest changeRequest = new ChangeRequest();
  208. sendMH.addMessageAttribute(changeRequest);
  209. byte[] data = sendMH.getBytes();
  210. DatagramPacket send = new DatagramPacket(data, data.length);
  211. socketTest1.send(send);
  212. logger.finer("Test 1 redo with changed address: Binding Request sent.");
  213. MessageHeader receiveMH = new MessageHeader();
  214. while (!(receiveMH.equalTransactionID(sendMH))) {
  215. DatagramPacket receive = new DatagramPacket(new byte[200], 200);
  216. socketTest1.receive(receive);
  217. receiveMH = MessageHeader.parseHeader(receive.getData());
  218. receiveMH.parseAttributes(receive.getData());
  219. }
  220. MappedAddress ma2 = (MappedAddress) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.MappedAddress);
  221. ErrorCode ec = (ErrorCode) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.ErrorCode);
  222. if (ec != null) {
  223. di.setError(ec.getResponseCode(), ec.getReason());
  224. logger.config("Message header contains an Errorcode message attribute.");
  225. return false;
  226. }
  227. if (ma2 == null) {
  228. di.setError(700, "The server is sending an incomplete response (Mapped Address message attribute is missing). The client should not retry.");
  229. logger.config("Response does not contain a Mapped Address message attribute.");
  230. return false;
  231. } else {
  232. if ((ma.getPort() != ma2.getPort()) || (!(ma.getAddress().getInetAddress().equals(ma2.getAddress().getInetAddress())))) {
  233. di.setSymmetric();
  234. logger.fine("Node is behind a symmetric NAT.");
  235. return false;
  236. }
  237. }
  238. return true;
  239. } catch (SocketTimeoutException ste2) {
  240. if (timeSinceFirstTransmission < 7900) {
  241. logger.config("Test 1 redo with changed address: Socket timeout while receiving the response.");
  242. timeSinceFirstTransmission += timeout;
  243. int timeoutAddValue = (timeSinceFirstTransmission * 2);
  244. if (timeoutAddValue > 1600) timeoutAddValue = 1600;
  245. timeout = timeoutAddValue;
  246. } else {
  247. logger.config("Test 1 redo with changed address: Socket timeout while receiving the response. Maximum retry limit exceed. Give up.");
  248. return false;
  249. }
  250. }
  251. }
  252. }
  253. private void test3() throws UtilityException, SocketException, UnknownHostException, IOException, MessageAttributeParsingException, MessageAttributeException, MessageHeaderParsingException {
  254. int timeSinceFirstTransmission = 0;
  255. int timeout = timeoutInitValue;
  256. while (true) {
  257. try {
  258. // Test 3 including response
  259. DatagramSocket sendSocket = new DatagramSocket(new InetSocketAddress(iaddress, 0));
  260. sendSocket.connect(InetAddress.getByName(stunServer), port);
  261. sendSocket.setSoTimeout(timeout);
  262. MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
  263. sendMH.generateTransactionID();
  264. ChangeRequest changeRequest = new ChangeRequest();
  265. changeRequest.setChangePort();
  266. sendMH.addMessageAttribute(changeRequest);
  267. byte[] data = sendMH.getBytes();
  268. DatagramPacket send = new DatagramPacket(data, data.length);
  269. sendSocket.send(send);
  270. logger.finer("Test 3: Binding Request sent.");
  271. int localPort = sendSocket.getLocalPort();
  272. InetAddress localAddress = sendSocket.getLocalAddress();
  273. sendSocket.close();
  274. DatagramSocket receiveSocket = new DatagramSocket(localPort, localAddress);
  275. receiveSocket.connect(InetAddress.getByName(stunServer), ca.getPort());
  276. receiveSocket.setSoTimeout(timeout);
  277. MessageHeader receiveMH = new MessageHeader();
  278. while (!(receiveMH.equalTransactionID(sendMH))) {
  279. DatagramPacket receive = new DatagramPacket(new byte[200], 200);
  280. receiveSocket.receive(receive);
  281. receiveMH = MessageHeader.parseHeader(receive.getData());
  282. receiveMH.parseAttributes(receive.getData());
  283. }
  284. ErrorCode ec = (ErrorCode) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.ErrorCode);
  285. if (ec != null) {
  286. di.setError(ec.getResponseCode(), ec.getReason());
  287. logger.config("Message header contains an Errorcode message attribute.");
  288. return;
  289. }
  290. if (nodeNatted) {
  291. di.setRestrictedCone();
  292. logger.fine("Node is behind a restricted NAT.");
  293. return;
  294. }
  295. } catch (SocketTimeoutException ste) {
  296. if (timeSinceFirstTransmission < 7900) {
  297. logger.finer("Test 3: Socket timeout while receiving the response.");
  298. timeSinceFirstTransmission += timeout;
  299. int timeoutAddValue = (timeSinceFirstTransmission * 2);
  300. if (timeoutAddValue > 1600) timeoutAddValue = 1600;
  301. timeout = timeoutAddValue;
  302. } else {
  303. logger.finer("Test 3: Socket timeout while receiving the response. Maximum retry limit exceed. Give up.");
  304. di.setPortRestrictedCone();
  305. logger.fine("Node is behind a port restricted NAT.");
  306. return;
  307. }
  308. }
  309. }
  310. }
  311. }