PageRenderTime 73ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/edu/thu/thss/yxy/network/AddressDiagnosticsKit.java

http://gophone.googlecode.com/
Java | 386 lines | 244 code | 49 blank | 93 comment | 21 complexity | decaaa16648d1668535eb55ecdaf934e MD5 | raw file
  1. package edu.thu.thss.yxy.network;
  2. import java.net.*;
  3. import edu.thu.thss.yxy.util.Logger;
  4. import net.java.stun4j.*;
  5. import net.java.stun4j.attribute.*;
  6. import net.java.stun4j.message.*;
  7. /**
  8. * Runs a separate thread of diagnostics for a given network address. The
  9. * diagnostics thread would discover NAT bindings through stun, update bindings
  10. * lifetime test connectivity and etc.
  11. *
  12. * @author Emil Ivov
  13. */
  14. public class AddressDiagnosticsKit
  15. extends Thread
  16. {
  17. private static final Logger logger =
  18. Logger.getLogger(AddressDiagnosticsKit.class);
  19. public static final int DIAGNOSTICS_STATUS_OFF = 1;
  20. public static final int DIAGNOSTICS_STATUS_DISOVERING_CONFIG = 2;
  21. public static final int DIAGNOSTICS_STATUS_RESOLVING = 3;
  22. public static final int DIAGNOSTICS_STATUS_COMPLETED = 4;
  23. public static final int DIAGNOSTICS_STATUS_DISOVERING_BIND_LIFETIME = 5;
  24. public static final int DIAGNOSTICS_STATUS_TERMINATED = 6;
  25. private int diagnosticsStatus = DIAGNOSTICS_STATUS_OFF;
  26. /**
  27. * These are used by (to my knowledge) mac and windows boxes when dhcp
  28. * fails and are only usable with other boxes using the same address
  29. * in the same net segment. That's why they get their low preference.
  30. */
  31. // TODO Remove after confirmation that this not used
  32. // private static final AddressPreference ADDR_PREF_LOCAL_IPV4_AUTOCONF
  33. // = new AddressPreference(40);
  34. /**
  35. * Local IPv6 addresses are assigned by default to any network iface running
  36. * an ipv6 stack. Theya are one of our last resorts since an internet
  37. * connected node would have generally configured sth else as well.
  38. */
  39. private static final AddressPreference ADDR_PREF_LOCAL_IPV6
  40. = new AddressPreference(40);
  41. /**
  42. * Local IPv4 addresses are either assigned by DHCP or manually configured
  43. * which means that even if they're unresolved to a globally routable
  44. * address they're still there for a reason (let the reason be ...) and this
  45. * reason might very well be purposeful so they should get a preference
  46. * higher than local IPv6 (even though I'm an IPv6 fan :) )
  47. */
  48. private static final AddressPreference ADDR_PREF_PRIVATE_IPV4
  49. = new AddressPreference(50);
  50. /**
  51. * Global IPv4 Addresses are a good think when they work. We are therefore
  52. * setting a high preference that will then be corrected by.
  53. */
  54. private static final AddressPreference ADDR_PREF_GLOBAL_IPV4
  55. = new AddressPreference(60);
  56. /**
  57. * There are many reasons why global IPv6 addresses should have the highest
  58. * preference. A global IPv6 address is most often delivered through
  59. * stateless address autoconfiguration which means an active router and
  60. * might also mean an active net connection.
  61. */
  62. private static final AddressPreference ADDR_PREF_GLOBAL_IPV6
  63. = new AddressPreference(70);
  64. /**
  65. * The address of the stun server to query
  66. */
  67. private StunAddress primaryStunServerAddress =
  68. new StunAddress("stun01.sipphone.com", 3478);
  69. /**
  70. * The address pool entry that this kit is diagnosing.
  71. */
  72. private AddressPoolEntry addressEntry = null;
  73. /**
  74. * Specifies whether stun should be used or not.
  75. * This field is updated during runtime to conform to the configuration.
  76. */
  77. private boolean useStun = true;
  78. private StunClient stunClient = null;
  79. /**
  80. * The port to be used locally for sending generic stun queries.
  81. */
  82. static final int LOCAL_STUN_PORT = 55126;
  83. private int bindRetries = 10;
  84. public AddressDiagnosticsKit(AddressPoolEntry addressEntry)
  85. {
  86. this.addressEntry = addressEntry;
  87. setDiagnosticsStatus(DIAGNOSTICS_STATUS_OFF);
  88. }
  89. /**
  90. * Sets the current status of the address diagnostics process
  91. * @param status int
  92. */
  93. private void setDiagnosticsStatus(int status)
  94. {
  95. this.diagnosticsStatus = status;
  96. }
  97. /**
  98. * Returns the current status of this diagnosics process.
  99. * @return int
  100. */
  101. public int getDiagnosticsStatus()
  102. {
  103. return this.diagnosticsStatus;
  104. }
  105. /**
  106. * The diagnostics code itself.
  107. */
  108. public void run()
  109. {
  110. logger.debug("Started a diag kit for entry: " + addressEntry);
  111. //implements the algorithm from AssigningAddressPreferences.png
  112. setDiagnosticsStatus(DIAGNOSTICS_STATUS_DISOVERING_CONFIG);
  113. InetAddress address = addressEntry.getInetAddress();
  114. //is this an ipv6 address
  115. if (addressEntry.isIPv6())
  116. {
  117. if (addressEntry.isLinkLocal())
  118. {
  119. addressEntry.setAddressPreference(ADDR_PREF_LOCAL_IPV6);
  120. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  121. return;
  122. }
  123. if (addressEntry.is6to4())
  124. {
  125. //right now we don't support these. we should though ... one day
  126. addressEntry.setAddressPreference(AddressPreference.MIN);
  127. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  128. return;
  129. }
  130. //if we get here then we are a globally routable ipv6 addr
  131. addressEntry.setAddressPreference(ADDR_PREF_GLOBAL_IPV6);
  132. setDiagnosticsStatus(DIAGNOSTICS_STATUS_COMPLETED);
  133. //should do some connectivity testing here and proceed with firewall
  134. //discovery but since stun4j does not support ipv6 yet, this too
  135. //will happen another day.
  136. return;
  137. }
  138. //from now on we're only dealing with IPv4
  139. if (addressEntry.isIPv4LinkLocalAutoconf())
  140. {
  141. //not sure whether these are used for anything.
  142. addressEntry.setAddressPreference(AddressPreference.MIN);
  143. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  144. return;
  145. }
  146. //first try and see what we can infer from just looking at the
  147. //address
  148. if (addressEntry.isLinkLocalIPv4Address())
  149. {
  150. addressEntry.setAddressPreference(ADDR_PREF_PRIVATE_IPV4);
  151. }
  152. else
  153. {
  154. //public address
  155. addressEntry.setAddressPreference(ADDR_PREF_GLOBAL_IPV4);
  156. }
  157. if (!useStun)
  158. {
  159. //if we're configured not to run stun - we're done.
  160. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  161. return;
  162. }
  163. //start stunning
  164. for(int i = 0; i < bindRetries; i++){
  165. StunAddress localStunAddress = new StunAddress(
  166. address, 1024 + (int) (Math.random() * 64512));
  167. try
  168. {
  169. stunClient = new StunClient(localStunAddress);
  170. stunClient.start();
  171. logger.debug("Successfully started StunClient for "
  172. + localStunAddress + ".");
  173. break;
  174. }
  175. catch (StunException ex)
  176. {
  177. if (ex.getCause() instanceof SocketException
  178. && i < bindRetries)
  179. {
  180. logger.debug("Failed to bind to "
  181. + localStunAddress + ". Retrying ...");
  182. logger.debug("Exception was ", ex);
  183. continue;
  184. }
  185. logger.error("Failed to start a stun client for address entry ["
  186. + addressEntry.toString()+"]:"
  187. +localStunAddress.getPort() + ". Ceasing attempts",
  188. ex);
  189. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  190. return;
  191. }
  192. }
  193. //De Stun Test I
  194. StunMessageEvent event = null;
  195. try
  196. {
  197. event = stunClient.doStunTestI(
  198. primaryStunServerAddress);
  199. }
  200. catch (StunException ex)
  201. {
  202. logger.error("Failed to perform STUN Test I for address entry"
  203. + addressEntry.toString(), ex);
  204. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  205. stunClient.shutDown();
  206. return;
  207. }
  208. if(event == null)
  209. {
  210. //didn't get a response - we either don't have connectivity or the
  211. //server is down
  212. /** @todo if possible try another stun server here. we should
  213. * support multiple stun servers*/
  214. logger.debug("There seems to be no inet connectivity for "
  215. + addressEntry);
  216. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  217. stunClient.shutDown();
  218. logger.debug("stun test 1 failed");
  219. return;
  220. }
  221. //the moment of the truth - are we behind a NAT?
  222. boolean isPublic;
  223. Message stunResponse = event.getMessage();
  224. Attribute mappedAttr = stunResponse.getAttribute(Attribute.MAPPED_ADDRESS);
  225. StunAddress mappedAddrFromTestI = ((MappedAddressAttribute)mappedAttr).getAddress();
  226. Attribute changedAddressAttributeFromTestI
  227. = stunResponse.getAttribute(Attribute.CHANGED_ADDRESS);
  228. StunAddress secondaryStunServerAddress =
  229. ((ChangedAddressAttribute)changedAddressAttributeFromTestI).
  230. getAddress();
  231. /** @todo verify whether the stun server returned the same address for
  232. * the primary and secondary server and act accordingly
  233. * */
  234. if(mappedAddrFromTestI == null){
  235. logger.error(
  236. "Stun Server did not return a mapped address for entry "
  237. + addressEntry.toString());
  238. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  239. return;
  240. }
  241. if(mappedAddrFromTestI.equals(event.getSourceAccessPoint().getAddress()))
  242. {
  243. isPublic = true;
  244. }
  245. else
  246. {
  247. isPublic = false;
  248. }
  249. //do STUN Test II
  250. try
  251. {
  252. event = stunClient.doStunTestII(primaryStunServerAddress);
  253. }
  254. catch (StunException ex)
  255. {
  256. logger.error("Failed to perform STUN Test II for address entry"
  257. + addressEntry.toString(), ex);
  258. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  259. stunClient.shutDown();
  260. logger.debug("stun test 2 failed");
  261. return;
  262. }
  263. if(event != null){
  264. logger.error("Secondary STUN server is down"
  265. + addressEntry.toString());
  266. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  267. stunClient.shutDown();
  268. return;
  269. }
  270. //might mean that either the secondary stun server is down
  271. //or that we are behind a restrictive firewall. Let's find out
  272. //which.
  273. try
  274. {
  275. event = stunClient.doStunTestI(secondaryStunServerAddress);
  276. logger.debug("stun test 1 succeeded with s server 2");
  277. }
  278. catch (StunException ex)
  279. {
  280. logger.error("Failed to perform STUN Test I for address entry"
  281. + addressEntry.toString(), ex);
  282. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  283. stunClient.shutDown();
  284. return;
  285. }
  286. if (event == null)
  287. {
  288. //secondary stun server is down
  289. logger.error("Secondary STUN server is down"
  290. + addressEntry.toString());
  291. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  292. stunClient.shutDown();
  293. return;
  294. }
  295. //we are at least behind a port restricted nat
  296. stunResponse = event.getMessage();
  297. mappedAttr = stunResponse.getAttribute(Attribute.MAPPED_ADDRESS);
  298. StunAddress mappedAddrFromSecServer =
  299. ((MappedAddressAttribute)mappedAttr).getAddress();
  300. if(!mappedAddrFromTestI.equals(mappedAddrFromSecServer))
  301. {
  302. //secondary stun server is down
  303. logger.debug("We are behind a symmetric nat"
  304. + addressEntry.toString());
  305. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  306. stunClient.shutDown();
  307. return;
  308. }
  309. //now let's run test III so that we could guess whether or not we're
  310. //behind a port restricted nat/fw or simply a restricted one.
  311. try
  312. {
  313. event = stunClient.doStunTestIII(primaryStunServerAddress);
  314. logger.debug("stun test 3 succeeded with s server 1");
  315. }
  316. catch (StunException ex)
  317. {
  318. logger.error("Failed to perform STUN Test III for address entry"
  319. + addressEntry.toString(), ex);
  320. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  321. stunClient.shutDown();
  322. return;
  323. }
  324. if (event == null)
  325. {
  326. logger.debug("We are behind a port restricted NAT or fw"
  327. + addressEntry.toString());
  328. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  329. stunClient.shutDown();
  330. return;
  331. }
  332. logger.debug("We are behind a restricted NAT or fw"
  333. + addressEntry.toString());
  334. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED);
  335. stunClient.shutDown();
  336. }
  337. }