[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.
|