Thread: [Piper-net-devel] SF.net SVN: piper-net:[11] trunk/jnatlib/src
Status: Pre-Alpha
Brought to you by:
rdodgen
From: <ben...@us...> - 2009-01-04 01:27:22
|
Revision: 11 http://piper-net.svn.sourceforge.net/piper-net/?rev=11&view=rev Author: benhiller Date: 2009-01-04 01:27:17 +0000 (Sun, 04 Jan 2009) Log Message: ----------- Initial check-in of STUN library Added Paths: ----------- trunk/jnatlib/src/net/ trunk/jnatlib/src/net/jnatlib/ trunk/jnatlib/src/net/jnatlib/stun/ trunk/jnatlib/src/net/jnatlib/stun/StunClient.java trunk/jnatlib/src/net/jnatlib/stun/StunMessage.java trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java Added: trunk/jnatlib/src/net/jnatlib/stun/StunClient.java =================================================================== --- trunk/jnatlib/src/net/jnatlib/stun/StunClient.java (rev 0) +++ trunk/jnatlib/src/net/jnatlib/stun/StunClient.java 2009-01-04 01:27:17 UTC (rev 11) @@ -0,0 +1,107 @@ +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +package net.jnatlib.stun; + +import java.io.*; +import java.net.*; +import java.util.Enumeration; +import java.util.Hashtable; + +import net.jnatlib.stun.StunMessage.MessageType; +import net.jnatlib.stun.StunMessageAttribute.AttributeType; + + +public class StunClient { + + public static void main(String[] args) { + // Get server name, port in args + String host = args[0]; + int port = Integer.parseInt(args[1]); + StunMessage received = StunClient.getSTUNResponse(host, port); + System.out.println(received.getMessageType()); + byte[] transactionID = received.getTransactionID(); + System.out.print("Transaction ID: "); + for(int i = 0; i < transactionID.length; i++) + System.out.print(transactionID[i] + " "); + System.out.println(""); + Hashtable<AttributeType, StunMessageAttribute> attributes = received.getAttributes(); + Enumeration<AttributeType> keys = attributes.keys(); + while(keys.hasMoreElements()) { + AttributeType key = keys.nextElement(); + System.out.println(key + ": "); + Hashtable<String, Object> values = attributes.get(key).getValues(); + Enumeration<String> keysForValues = values.keys(); + while(keysForValues.hasMoreElements()) { + String keyForValue = keysForValues.nextElement(); + if(keyForValue.equals("IP Address")) { + System.out.print("\t " + keyForValue + ": "); + int[] ip = (int[])values.get(keyForValue); + for(int i = 0; i < ip.length-1; i++) { + System.out.print(ip[i] + "."); + } + System.out.println(ip[ip.length-1]); + } else { + System.out.println("\t " + keyForValue + ": " + values.get(keyForValue)); + } + } + } + } + + public static StunMessage getSTUNResponse(String host, int port) { + DatagramSocket NATServerConnection; + StunMessage bindRequest = new StunMessage(MessageType.BIND_REQUEST); + StunMessage bindResponse = null; + int retransmissionTimeOutSeconds = 1; + boolean retransmit = false; + do { + try { + System.out.println("Opening socket"); + InetSocketAddress socketAddr = + new InetSocketAddress(host, port); + NATServerConnection = new DatagramSocket(54322); + NATServerConnection.setSoTimeout(10000); + NATServerConnection.connect(socketAddr); + System.out.println("Writing to socket"); + byte[] message = bindRequest.getMessage(); + DatagramPacket packet = new DatagramPacket(message, + message.length, socketAddr); + byte[] buffer = new byte[2048]; + NATServerConnection.send(packet); + System.out.println("Reading from socket"); + DatagramPacket p = new DatagramPacket(buffer, 2048); + NATServerConnection.receive(p); + bindResponse = StunMessage.parseByteMessage(buffer); + NATServerConnection.close(); + retransmit = false; + } catch (SocketTimeoutException e) { + System.out.println(e); + try { + Thread.sleep(retransmissionTimeOutSeconds*1000); + } catch (InterruptedException e1) { + System.out.println(e1); + } + retransmit = true; + } catch (IOException e) { + System.out.println(e); + } + if(bindResponse == null) { + try { + Thread.sleep(retransmissionTimeOutSeconds*1000); + } catch (InterruptedException e) { + System.out.println(e); + } + retransmissionTimeOutSeconds *= 2; + } else { + break; + } + } while (retransmit); + return bindResponse; + } +} Added: trunk/jnatlib/src/net/jnatlib/stun/StunMessage.java =================================================================== --- trunk/jnatlib/src/net/jnatlib/stun/StunMessage.java (rev 0) +++ trunk/jnatlib/src/net/jnatlib/stun/StunMessage.java 2009-01-04 01:27:17 UTC (rev 11) @@ -0,0 +1,246 @@ +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +package net.jnatlib.stun; + +import java.util.Random; +import java.util.Hashtable; + +import net.jnatlib.stun.StunMessageAttribute.AttributeType; + + +public class StunMessage { + public enum MessageType { + BIND_REQUEST(0x0001), + BIND_RESPONSE(0x0101), + BIND_ERROR_RESPONSE(0x0111), + SHARED_SECRET_REQUEST(0x0002), //This is now reserved + SHARED_SECRET_RESPONSE(0x0102), //This is now reserved + SHARED_SECRET_ERROR_RESPONSE(0x0112); //This is now reserved + + private final int hexRepresentation; + + MessageType(int hex) { + this.hexRepresentation = hex; + } + + public int getHexRepresentation() { + return hexRepresentation; + } + } + + private static final int MAGIC_COOKIE = 0x2112A442; + private static final int TRANSACTION_ID_LENGTH = 12; + private static final int HEADER_LENGTH = 20; + + private MessageType messageType; + private int length; + private byte[] transactionID; + private Hashtable<AttributeType, StunMessageAttribute> messageAttributes; + private byte[] originalAttributes; + + public StunMessage(MessageType messageType, byte[] transactionID, int length) { + this.messageType = messageType; + this.length = length; + this.transactionID = new byte[TRANSACTION_ID_LENGTH]; + System.arraycopy(transactionID, 0, this.transactionID, 0, TRANSACTION_ID_LENGTH); + messageAttributes = new Hashtable<AttributeType, StunMessageAttribute>(); + } + + public StunMessage(MessageType messageType, byte[] transactionID) { + this(messageType, transactionID, 0); + } + + public StunMessage(MessageType messageType) { + this(messageType, StunMessage.createTransactionID()); + } + + /** + * This creates a randomly generated transaction ID + * + * @return a byte array representing a new transaction ID + */ + public static byte[] createTransactionID() { + String transactionID = ""; + char[] letters = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' }; + Random rng = new Random(); + for (int i = 0; i < TRANSACTION_ID_LENGTH; i++) + transactionID += letters[rng.nextInt(letters.length)]; + return transactionID.getBytes(); + } + + public MessageType getMessageType() { + return messageType; + } + + public byte[] getTransactionID() { + // We make a copy so that a malicious user cannot + // modify the transaction ID + byte[] transactionIDCopy = new byte[TRANSACTION_ID_LENGTH]; + System.arraycopy(transactionID, 0, transactionIDCopy, 0, TRANSACTION_ID_LENGTH); + return transactionIDCopy; + } + + public Hashtable<AttributeType, StunMessageAttribute> getAttributes() { + return messageAttributes; + } + + public void setMessageType(MessageType newMessageType) { + messageType = newMessageType; + } + + public void addAttribute(StunMessageAttribute newMessageAttribute) { + // The RFC specifies if an attribute is given more then once, all + // duplicates after the first are to be ignored + if(!messageAttributes.containsKey(newMessageAttribute.getAttributeType())) + messageAttributes.put(newMessageAttribute.getAttributeType(), newMessageAttribute); + } + + public void setTransactionID(byte[] newTransactionID) { + // Check that this is a valid transaction ID + if (newTransactionID.length == TRANSACTION_ID_LENGTH) + System.arraycopy(transactionID, 0, this.transactionID, 0, TRANSACTION_ID_LENGTH); + } + + public byte[] getMessage() { + byte[] message = new byte[length + HEADER_LENGTH]; + message[0] = (byte) ((messageType.getHexRepresentation() >>> 8) & 0xFF); + message[1] = (byte) (messageType.getHexRepresentation() & 0xFF); + message[2] = (byte) ((length >>> 8) & 0xFF); + message[3] = (byte) (length & 0xFF); + message[4] = (byte) ((MAGIC_COOKIE >>> 24) & 0xFF); + message[5] = (byte) ((MAGIC_COOKIE >>> 16) & 0xFF); + message[6] = (byte) ((MAGIC_COOKIE >>> 8) & 0xFF); + message[7] = (byte) (MAGIC_COOKIE & 0xFF); + System.arraycopy(transactionID, 0, message, 8, TRANSACTION_ID_LENGTH); + if(originalAttributes != null) + System.arraycopy(originalAttributes, 0, message, 8+TRANSACTION_ID_LENGTH, originalAttributes.length); + return message; + } + + public void setOriginalAttributes(byte[] originalAttributes) { + this.originalAttributes = new byte[originalAttributes.length]; + System.arraycopy(originalAttributes, 0, this.originalAttributes, 0, originalAttributes.length); + } + + public static StunMessage parseByteMessage(byte[] message) { + MessageType messageType = null; + // Find out the message type + int typeHexRepresentation = ((message[0] & 0xFF) << 8) + (message[1] & 0xFF); + for (MessageType m : MessageType.values()) + if (m.hexRepresentation == typeHexRepresentation) { + messageType = m; + break; + } + // See the length of the message + int length = ((message[2] & 0xFF) << 8) + (message[3] & 0xFF); + if (message[2] != 0) + return null; + // Make sure that the magic cookie is contained in the message + int magicCookie = ((message[4] & 0xFF) << 24) + ((message[5] & 0xFF) << 16) + ((message[6] & 0xFF) << 8) + + (message[7] & 0xFF); + // If the magic cookie is not contained, we ignore this message + //TODO: Is this proper? + //if (magicCookie != MAGIC_COOKIE) + // return null; + // Find the transaction ID + byte[] transactionID = new byte[16]; + System.arraycopy(message, 8, transactionID, 0, TRANSACTION_ID_LENGTH); + byte[] originalAttributes = new byte[length]; + System.arraycopy(message, 8+TRANSACTION_ID_LENGTH, originalAttributes, 0, length); + //Start looking for attributes after the header + int i = HEADER_LENGTH; + //Create the new message so that we can add attributes to it + StunMessage stunMessage = new StunMessage(messageType, transactionID, length); + stunMessage.setOriginalAttributes(originalAttributes); + //Continue looking through the message while we have valid content left + while (i < length) { + StunMessageAttribute newMessageAttribute = null; + // Find the type of this attribute + int attrTypeHexRepresentation = ((message[i] & 0xFF) << 8) + (message[++i] & 0xFF); + AttributeType type = null; + for (AttributeType t : AttributeType.values()) { + if (t.getHexRepresentation() == attrTypeHexRepresentation) { + type = t; + break; + } + } + // Find the length of this attribute + int attrLength = ((message[++i] & 0xFF) << 8) + (message[++i] & 0xFF); + newMessageAttribute = new StunMessageAttribute(type, length); + if(type != null && !stunMessage.getAttributes().containsKey(type)) { + // I created a different case for each type of attribute + switch (type) { + case MAPPED_ADDRESS: + { + i++; + int IPVersion = message[++i]; + int port = ((message[++i] & 0xFF) << 8) + (message[++i] & 0xFF); + int[] IPAddress = null; + if (IPVersion == 1) { + IPAddress = new int[4]; + for (int j = 0; j < 4; j++) + IPAddress[j] = message[++i] & 0xFF; + } else { + IPAddress = new int[8]; + for (int j = 0; j < 8; j++) + IPAddress[j] = ((message[++i] & 0xFF) << 8) + (message[++i] & 0xFF); + } + newMessageAttribute.addValue("Port", port); + newMessageAttribute.addValue("IP Address", IPAddress); + break; + } + case XOR_MAPPED_ADDRESS: + { + i++; + int IPVersion = message[++i]; + //Convert the magic cookie to an array since it is usseful for XORing the port and IP + byte[] magicCookieAsArray = new byte[4]; + magicCookieAsArray[0] = (byte) ((MAGIC_COOKIE >>> 24) & 0xFF); + magicCookieAsArray[1] = (byte) ((MAGIC_COOKIE >>> 16) & 0xFF); + magicCookieAsArray[2] = (byte) ((MAGIC_COOKIE >>> 8) & 0xFF); + magicCookieAsArray[3] = (byte) (MAGIC_COOKIE & 0xFF); + int[] portArray = new int[2]; + for (int j = 0; j < 2; j++) + portArray[j] = (message[++i] & 0xFF) ^ magicCookieAsArray[j]; + int port = (portArray[0] << 8) + portArray[1]; + int[] IPAddress = null; + if (IPVersion == 1) { + IPAddress = new int[4]; + for (int j = 0; j < 4; j++) + IPAddress[j] = ((message[++i] & 0xFF) ^ magicCookieAsArray[j]) & 0xFF; + } else { + //NEED IPV6 SUPPORT + } + newMessageAttribute.addValue("Port", port); + newMessageAttribute.addValue("IP Address", IPAddress); + break; + } + case USERNAME: + case MESSAGE_INTEGRITY: + case ERROR_CODE: + case UNKNOWN_ATTRIBUTES: + case REALM: + case NONCE: + default: + i += attrLength; + break; + } + i++; + stunMessage.addAttribute(newMessageAttribute); + } else { + i += attrLength; + } + //The RFC specifies each attribute must end on an index divisible by 4 + i += i % 4; + } + return stunMessage; + } +} Added: trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java =================================================================== --- trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java (rev 0) +++ trunk/jnatlib/src/net/jnatlib/stun/StunMessageAttribute.java 2009-01-04 01:27:17 UTC (rev 11) @@ -0,0 +1,61 @@ +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +package net.jnatlib.stun; + +import java.util.Hashtable; + +public class StunMessageAttribute { + public enum AttributeType { + MAPPED_ADDRESS (0x0001), + RESPONSE_ADDRESS (0x0002), //This is now reserved + CHANGE_REQUEST (0x0003), //This is now reserved + SOURCE_ADDRESS (0x0004), //This is now reserved + CHANGED_ADDRESS(0x0005), //This is now reserved + USERNAME (0x0006), + PASSWORD (0x0007), //This is now reserved + MESSAGE_INTEGRITY (0x0008), + ERROR_CODE (0x0009), + UNKNOWN_ATTRIBUTES (0x000A), + REFLECTED_FROM (0x000B), //This is now reserved + REALM (0x0014), + NONCE (0x0015), + XOR_MAPPED_ADDRESS (0x0020); + + private final int hexRepresentation; + + AttributeType(int hex) { + this.hexRepresentation = hex; + } + + public int getHexRepresentation() { return hexRepresentation; } + } + + AttributeType attributeType; + int length; + Hashtable<String, Object> values; + + public AttributeType getAttributeType() { + return attributeType; + } + + public Hashtable<String, Object> getValues() { + return values; + } + + public void addValue(String key, Object value) { + values.put(key, value); + } + + StunMessageAttribute(AttributeType type, int length) { + attributeType = type; + this.length = length; + values = new Hashtable<String, Object>(); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |