001: /**
002: * Copyright (C) 2007 Jive Software. All rights reserved.
003: *
004: * This software is published under the terms of the GNU Public License (GPL),
005: * a copy of which is included in this distribution.
006: */package org.jivesoftware.openfire.stun;
007:
008: import de.javawi.jstun.test.demo.StunServer;
009: import org.dom4j.Element;
010: import org.jivesoftware.openfire.IQHandlerInfo;
011: import org.jivesoftware.openfire.XMPPServer;
012: import org.jivesoftware.openfire.auth.UnauthorizedException;
013: import org.jivesoftware.openfire.container.BasicModule;
014: import org.jivesoftware.openfire.handler.IQHandler;
015: import org.jivesoftware.util.JiveGlobals;
016: import org.jivesoftware.util.Log;
017: import org.jivesoftware.util.PropertyEventDispatcher;
018: import org.jivesoftware.util.PropertyEventListener;
019: import org.xmpp.packet.IQ;
020: import org.xmpp.packet.PacketError;
021:
022: import java.net.InetAddress;
023: import java.net.NetworkInterface;
024: import java.net.SocketException;
025: import java.net.UnknownHostException;
026: import java.util.ArrayList;
027: import java.util.Enumeration;
028: import java.util.List;
029: import java.util.Map;
030:
031: /**
032: * STUN server and service module. It provides address discovery for p2p sessions to be
033: * used for media transmission and receiving of UDP packets. It's especially useful for
034: * clients behind NAT devices.
035: *
036: * @author Thiago Camargo
037: */
038: public class STUNService extends BasicModule {
039:
040: private static final String ELEMENT_NAME = "stun";
041: private static final String NAMESPACE = "google:jingleinfo";
042: private static final String DEFAULT_EXTERNAL_ADDRESSES = "stun.xten.net:3478;"
043: + "jivesoftware.com:3478;"
044: + "igniterealtime.org:3478;"
045: + "stun.fwdnet.net:3478";
046:
047: private IQHandler stunIQHandler;
048: private StunServer stunServer = null;
049: private boolean enabled = false;
050: private boolean localEnabled = false;
051:
052: private String primaryAddress = null;
053: private String secondaryAddress = null;
054: private int primaryPort;
055: private int secondaryPort;
056:
057: private List<StunServerAddress> externalServers = null;
058:
059: /**
060: * Constructs a new STUN Service
061: */
062: public STUNService() {
063: super ("STUN Service");
064: }
065:
066: public void destroy() {
067: super .destroy();
068: stunServer = null;
069: }
070:
071: public void initialize(XMPPServer server) {
072: super .initialize(server);
073:
074: this .enabled = JiveGlobals.getBooleanProperty("stun.enabled",
075: true);
076:
077: primaryAddress = JiveGlobals
078: .getProperty("stun.address.primary");
079: secondaryAddress = JiveGlobals
080: .getProperty("stun.address.secondary");
081:
082: String addresses = JiveGlobals
083: .getProperty("stun.external.addresses");
084: // If no custom external addresses are defined, use the defaults.
085: if (addresses == null) {
086: addresses = DEFAULT_EXTERNAL_ADDRESSES;
087: }
088: externalServers = getStunServerAddresses(addresses);
089:
090: primaryPort = JiveGlobals.getIntProperty("stun.port.primary",
091: 3478);
092: secondaryPort = JiveGlobals.getIntProperty(
093: "stun.port.secondary", 3479);
094:
095: this .localEnabled = JiveGlobals.getBooleanProperty(
096: "stun.local.enabled", false);
097: // If the local server is supposed to be enabled, ensure that primary and secondary
098: // addresses are defined.
099: if (localEnabled) {
100: if (primaryAddress == null || secondaryAddress == null) {
101: Log
102: .warn("STUN server cannot be enabled. Primary and secondary addresses must be defined.");
103: localEnabled = false;
104: }
105: }
106:
107: // Add listeners for STUN service being enabled and disabled via manual property changes.
108: PropertyEventDispatcher
109: .addListener(new PropertyEventListener() {
110:
111: public void propertySet(String property,
112: Map<String, Object> params) {
113: if (property.equals("stun.enabled")) {
114: boolean oldValue = enabled;
115: enabled = JiveGlobals.getBooleanProperty(
116: "stun.enabled", true);
117: //
118: if (enabled && !oldValue) {
119: startSTUNService();
120: } else if (!enabled && oldValue) {
121: stop();
122: }
123: } else if (property
124: .equals("stun.local.enabled")) {
125: localEnabled = JiveGlobals
126: .getBooleanProperty(
127: "stun.local.enabled", false);
128: }
129: }
130:
131: public void propertyDeleted(String property,
132: Map<String, Object> params) {
133: if (property.equals("stun.enabled")) {
134: enabled = true;
135: } else if (property
136: .equals("stun.local.enabled")) {
137: localEnabled = false;
138: }
139: }
140:
141: public void xmlPropertySet(String property,
142: Map<String, Object> params) {
143: // Ignore.
144: }
145:
146: public void xmlPropertyDeleted(String property,
147: Map<String, Object> params) {
148: // Ignore.
149: }
150: });
151: }
152:
153: public void start() {
154: if (isEnabled()) {
155: startSTUNService();
156: if (isLocalEnabled()) {
157: startLocalServer();
158: }
159: }
160: }
161:
162: private void startLocalServer() {
163: try {
164: InetAddress primary = InetAddress.getByName(primaryAddress);
165: InetAddress secondary = InetAddress
166: .getByName(secondaryAddress);
167:
168: if (primary != null && secondary != null) {
169: stunServer = new StunServer(primaryPort, primary,
170: secondaryPort, secondary);
171: stunServer.start();
172: } else {
173: setLocalEnabled(false);
174: }
175: } catch (SocketException e) {
176: Log.error("Disabling STUN server", e);
177: setLocalEnabled(false);
178: } catch (UnknownHostException e) {
179: Log.error("Disabling STUN server", e);
180: setLocalEnabled(false);
181: }
182: }
183:
184: private void startSTUNService() {
185: XMPPServer server = XMPPServer.getInstance();
186: // Register the STUN feature in disco.
187: server.getIQDiscoInfoHandler().addServerFeature(NAMESPACE);
188: // Add an IQ handler.
189: stunIQHandler = new STUNIQHandler();
190: server.getIQRouter().addHandler(stunIQHandler);
191: }
192:
193: private void stopSTUNService() {
194: XMPPServer server = XMPPServer.getInstance();
195: server.getIQDiscoInfoHandler().removeServerFeature(NAMESPACE);
196: if (stunIQHandler != null) {
197: server.getIQRouter().removeHandler(stunIQHandler);
198: stunIQHandler = null;
199: }
200: }
201:
202: public void stop() {
203: super .stop();
204: this .enabled = false;
205: stopSTUNService();
206: stopLocal();
207: }
208:
209: private void stopLocal() {
210: if (stunServer != null) {
211: stunServer.stop();
212: }
213: stunServer = null;
214: }
215:
216: public String getName() {
217: return "stun";
218: }
219:
220: public List<StunServerAddress> getExternalServers() {
221: return externalServers;
222: }
223:
224: public void addExternalServer(String server, String port) {
225: externalServers.add(new StunServerAddress(server, port));
226:
227: String property = "";
228: for (StunServerAddress stunServerAddress : externalServers) {
229: if (!property.equals("")) {
230: property += ";";
231: }
232: property += stunServerAddress.getServer() + ":"
233: + stunServerAddress.getPort();
234: }
235: JiveGlobals.setProperty("stun.external.addresses", property);
236: }
237:
238: public void removeExternalServer(int index) {
239: externalServers.remove(index);
240: String property = "";
241: for (StunServerAddress stunServerAddress : externalServers) {
242: if (!property.equals("")) {
243: property += ";";
244: }
245: property += stunServerAddress.getServer() + ":"
246: + stunServerAddress.getPort();
247: }
248: JiveGlobals.setProperty("stun.external.addresses", property);
249: }
250:
251: /**
252: * Returns true if the service is enabled.
253: *
254: * @return enabled
255: */
256: public boolean isEnabled() {
257: return enabled;
258: }
259:
260: /**
261: * Returns true if the local STUN server is enabled.
262: *
263: * @return enabled
264: */
265: public boolean isLocalEnabled() {
266: return localEnabled;
267: }
268:
269: /**
270: * Set the service enable status.
271: *
272: * @param enabled boolean to enable or disable
273: * @param localEnabled local Server enable or disable
274: */
275: public void setEnabled(boolean enabled, boolean localEnabled) {
276: if (enabled && !this .enabled) {
277: startSTUNService();
278: if (isLocalEnabled()) {
279: startLocalServer();
280: }
281: } else {
282: stopSTUNService();
283: }
284: this .enabled = enabled;
285: this .localEnabled = localEnabled;
286: }
287:
288: /**
289: * Set the Local STUN Server enable status.
290: *
291: * @param enabled boolean to enable or disable
292: */
293: public void setLocalEnabled(boolean enabled) {
294: this .localEnabled = enabled;
295: if (isLocalEnabled()) {
296: startLocalServer();
297: } else {
298: stopLocal();
299: }
300: }
301:
302: /**
303: * Get the secondary Port used by the STUN server
304: *
305: * @return secondary Port used by the STUN server
306: */
307: public int getSecondaryPort() {
308: return secondaryPort;
309: }
310:
311: /**
312: * Get the primary Port used by the STUN server
313: *
314: * @return primary Port used by the STUN server
315: */
316: public int getPrimaryPort() {
317: return primaryPort;
318: }
319:
320: /**
321: * Get the secondary Address used by the STUN server
322: *
323: * @return secondary Address used by the STUN server
324: */
325: public String getSecondaryAddress() {
326: return secondaryAddress;
327: }
328:
329: /**
330: * Get the primary Address used by the STUN server
331: *
332: * @return primary Address used by the STUN server
333: */
334: public String getPrimaryAddress() {
335: return primaryAddress;
336: }
337:
338: public List<InetAddress> getAddresses() {
339: List<InetAddress> list = new ArrayList<InetAddress>();
340: try {
341: Enumeration<NetworkInterface> ifaces = NetworkInterface
342: .getNetworkInterfaces();
343: while (ifaces.hasMoreElements()) {
344: NetworkInterface iface = ifaces.nextElement();
345: Enumeration<InetAddress> iaddresses = iface
346: .getInetAddresses();
347: while (iaddresses.hasMoreElements()) {
348: InetAddress iaddress = iaddresses.nextElement();
349: if (!iaddress.isLoopbackAddress()
350: && !iaddress.isLinkLocalAddress()) {
351: list.add(iaddress);
352: }
353: }
354: }
355: } catch (Exception e) {
356: // Do Nothing
357: }
358: return list;
359: }
360:
361: /**
362: * Abstraction method used to convert a String into a STUN Server Address List
363: *
364: * @param addresses the String representation of server addresses with their
365: * respective ports (server1:port1;server2:port2).
366: * @return STUN server addresses list.
367: */
368: private List<StunServerAddress> getStunServerAddresses(
369: String addresses) {
370:
371: List<StunServerAddress> list = new ArrayList<StunServerAddress>();
372:
373: if (addresses.equals("")) {
374: return list;
375: }
376:
377: String servers[] = addresses.split(";");
378:
379: for (String server : servers) {
380: String address[] = server.split(":");
381: StunServerAddress aux = new StunServerAddress(address[0],
382: address[1]);
383: if (!list.contains(aux)) {
384: list.add(aux);
385: }
386: }
387: return list;
388: }
389:
390: /**
391: * An IQ handler for STUN requests.
392: */
393: private class STUNIQHandler extends IQHandler {
394:
395: public STUNIQHandler() {
396: super ("stun");
397: }
398:
399: public IQ handleIQ(IQ iq) throws UnauthorizedException {
400: IQ reply = IQ.createResultIQ(iq);
401: Element childElement = iq.getChildElement();
402: String namespace = childElement.getNamespaceURI();
403: Element childElementCopy = iq.getChildElement()
404: .createCopy();
405: reply.setChildElement(childElementCopy);
406:
407: if (NAMESPACE.equals(namespace)) {
408: if (isEnabled()) {
409: Element stun = childElementCopy.addElement("stun");
410: // If the local server is configured as a STUN server, send it as the first item.
411: if (isLocalEnabled()) {
412: StunServerAddress local;
413: local = new StunServerAddress(primaryAddress,
414: String.valueOf(primaryPort));
415: if (!externalServers.contains(local)) {
416: Element server = stun.addElement("server");
417: server.addAttribute("host", local
418: .getServer());
419: server.addAttribute("udp", local.getPort());
420: }
421: }
422: // Add any external STUN servers that are specified.
423: for (StunServerAddress stunServerAddress : externalServers) {
424: Element server = stun.addElement("server");
425: server.addAttribute("host", stunServerAddress
426: .getServer());
427: server.addAttribute("udp", stunServerAddress
428: .getPort());
429: }
430: try {
431: String ip = sessionManager.getSession(
432: iq.getFrom()).getHostAddress();
433: if (ip != null) {
434: Element publicIp = childElementCopy
435: .addElement("publicip");
436: publicIp.addAttribute("ip", ip);
437: }
438: } catch (UnknownHostException e) {
439: e.printStackTrace();
440: }
441: }
442:
443: } else {
444: // Answer an error since the server can't handle the requested namespace
445: reply
446: .setError(PacketError.Condition.service_unavailable);
447: }
448:
449: try {
450: Log.debug("STUNService: RETURNED:" + reply.toXML());
451: } catch (Exception e) {
452: Log.error(e);
453: }
454: return reply;
455: }
456:
457: public IQHandlerInfo getInfo() {
458: return new IQHandlerInfo(ELEMENT_NAME, NAMESPACE);
459: }
460: }
461: }
|