From a4d16af95d36d3df3f8a1a22fb66660f721bfc96 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Thu, 2 Apr 2009 08:22:31 +0000 Subject: [PATCH 01/14] SAM version 3 : - Raw and Datagram sessions implemented - option "SILENT=true" added to the stream protocol - java 6 warnings removed ministreaming : - java 6 warnings removed ministreaming and streaming : - added functions : I2PServerSocket.waitIncoming(long timeout) I2PServerSocket.accept(boolean block) --- .../i2p/client/streaming/I2PServerSocket.java | 30 + .../client/streaming/I2PServerSocketImpl.java | 107 ++- .../i2p/client/streaming/I2PSocketImpl.java | 1 - .../streaming/I2PSocketManagerFactory.java | 1 - .../streaming/I2PSocketManagerImpl.java | 19 +- .../net/i2p/client/streaming/TestSwarm.java | 14 + apps/sam/Demos/datagramTests/README.txt | 15 + apps/sam/Demos/datagramTests/samForward.py | 35 + apps/sam/Demos/datagramTests/samIn.py | 29 + apps/sam/Demos/datagramTests/samOut.py | 31 + apps/sam/Demos/rawTests/README.txt | 15 + apps/sam/Demos/rawTests/samForward.py | 36 + apps/sam/Demos/rawTests/samIn.py | 31 + apps/sam/Demos/rawTests/samOut.py | 31 + apps/sam/Demos/streamTests/README.txt | 24 + apps/sam/Demos/streamTests/samForward.py | 58 ++ apps/sam/Demos/streamTests/samIn.py | 89 +++ apps/sam/Demos/streamTests/samOut.py | 52 ++ .../sam/Demos/streamTests/samOutWithNaming.py | 51 ++ apps/sam/Demos/streamTests/server.py | 41 + apps/sam/doc/protocol-v3.txt | 17 + apps/sam/java/src/net/i2p/sam/SAMBridge.java | 36 +- .../src/net/i2p/sam/SAMDatagramSession.java | 9 +- .../java/src/net/i2p/sam/SAMException.java | 6 +- apps/sam/java/src/net/i2p/sam/SAMHandler.java | 46 +- .../src/net/i2p/sam/SAMHandlerFactory.java | 21 +- .../i2p/sam/SAMInvalidDirectionException.java | 3 +- .../src/net/i2p/sam/SAMMessageSession.java | 3 +- .../java/src/net/i2p/sam/SAMRawSession.java | 2 +- .../src/net/i2p/sam/SAMStreamReceiver.java | 3 +- .../src/net/i2p/sam/SAMStreamSession.java | 27 +- apps/sam/java/src/net/i2p/sam/SAMUtils.java | 18 + .../java/src/net/i2p/sam/SAMv1Handler.java | 103 ++- .../java/src/net/i2p/sam/SAMv2Handler.java | 6 +- .../src/net/i2p/sam/SAMv2StreamSession.java | 18 +- .../src/net/i2p/sam/SAMv3DatagramSession.java | 90 +++ .../java/src/net/i2p/sam/SAMv3Handler.java | 748 ++++++++++++++++++ .../java/src/net/i2p/sam/SAMv3RawSession.java | 88 +++ .../src/net/i2p/sam/SAMv3StreamSession.java | 389 +++++++++ .../net/i2p/sam/client/SAMEventHandler.java | 6 +- .../src/net/i2p/sam/client/SAMStreamSend.java | 11 +- .../src/net/i2p/sam/client/SAMStreamSink.java | 11 +- .../client/streaming/ConnectionHandler.java | 46 +- .../client/streaming/I2PServerSocketFull.java | 44 ++ 44 files changed, 2278 insertions(+), 183 deletions(-) create mode 100644 apps/sam/Demos/datagramTests/README.txt create mode 100755 apps/sam/Demos/datagramTests/samForward.py create mode 100755 apps/sam/Demos/datagramTests/samIn.py create mode 100755 apps/sam/Demos/datagramTests/samOut.py create mode 100644 apps/sam/Demos/rawTests/README.txt create mode 100755 apps/sam/Demos/rawTests/samForward.py create mode 100755 apps/sam/Demos/rawTests/samIn.py create mode 100755 apps/sam/Demos/rawTests/samOut.py create mode 100644 apps/sam/Demos/streamTests/README.txt create mode 100755 apps/sam/Demos/streamTests/samForward.py create mode 100755 apps/sam/Demos/streamTests/samIn.py create mode 100755 apps/sam/Demos/streamTests/samOut.py create mode 100755 apps/sam/Demos/streamTests/samOutWithNaming.py create mode 100755 apps/sam/Demos/streamTests/server.py create mode 100644 apps/sam/doc/protocol-v3.txt create mode 100644 apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java create mode 100644 apps/sam/java/src/net/i2p/sam/SAMv3Handler.java create mode 100644 apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java create mode 100644 apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java index d0028fdb8..e7db251fa 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java @@ -30,6 +30,36 @@ public interface I2PServerSocket { */ public I2PSocket accept() throws I2PException, ConnectException, SocketTimeoutException; + /** + * accept(true) has the same behaviour as accept(). + * accept(false) does not wait for a socket connecting. If a socket is + * available in the queue, it is accepted. Else, null is returned. + * + * @param true if the call should block until a socket is available + * + * @return a connected I2PSocket, or null + * + * @throws I2PException if there is a problem with reading a new socket + * from the data available (aka the I2PSession closed, etc) + * @throws ConnectException if the I2PServerSocket is closed + * @throws SocketTimeoutException + */ + public I2PSocket accept(boolean blocking) throws I2PException, ConnectException, SocketTimeoutException; + + /** + * Waits until there is a socket waiting for acception or the timeout is + * reached. + * + * @param timeoutMs timeout in ms. A negative value waits forever. + * + * @return true if a socket is available, false if not + * + * @throws I2PException if there is a problem with reading a new socket + * from the data available (aka the I2PSession closed, etc) + * @throws ConnectException if the I2PServerSocket is closed + */ + public boolean waitIncoming(long timeoutMs) throws I2PException, ConnectException, InterruptedException; + /** * Set Sock Option accept timeout * @param x timeout in ms diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java index 93db8595b..a2c075e3e 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java @@ -20,7 +20,7 @@ class I2PServerSocketImpl implements I2PServerSocket { private final static Log _log = new Log(I2PServerSocketImpl.class); private I2PSocketManager mgr; /** list of sockets waiting for the client to accept them */ - private List pendingSockets = Collections.synchronizedList(new ArrayList(4)); + private List pendingSockets = Collections.synchronizedList(new ArrayList(4)); /** have we been closed */ private volatile boolean closing = false; @@ -49,7 +49,90 @@ class I2PServerSocketImpl implements I2PServerSocket { this.mgr = mgr; } + + + + + /** + * Waits until there is a socket waiting for acception or the timeout is + * reached. + * + * @param timeoutMs timeout in ms. A negative value waits forever. + * + * @return true if a socket is available, false if not + * + * @throws I2PException if there is a problem with reading a new socket + * from the data available (aka the I2PSession closed, etc) + * @throws ConnectException if the I2PServerSocket is closed + */ + public boolean waitIncoming(long timeoutMs) throws I2PException, ConnectException { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("waitIncoming() called, pending: " + pendingSockets.size()); + + boolean isTimed = (timeoutMs>=0); + if (isTimed) { + Clock clock = I2PAppContext.getGlobalContext().clock(); + long now = clock.now(); + long end = now + timeoutMs; + while (pendingSockets.size() <= 0 && now0); + } + + + /** + * accept(true) has the same behaviour as accept(). + * accept(false) does not wait for a socket connecting. If a socket is + * available in the queue, it is accepted. Else, null is returned. + * + * @param true if the call should block until a socket is available + * + * @return a connected I2PSocket, or null + * + * @throws I2PException if there is a problem with reading a new socket + * from the data available (aka the I2PSession closed, etc) + * @throws ConnectException if the I2PServerSocket is closed + */ + + public I2PSocket accept(boolean blocking) throws I2PException, ConnectException { + I2PSocket ret = null; + + if (blocking) { + ret = accept(); + } else { + synchronized (pendingSockets) { + if (pendingSockets.size() > 0) { + ret = (I2PSocket)pendingSockets.remove(0); + } + } + if (ret != null) { + synchronized (socketAcceptedLock) { + socketAcceptedLock.notifyAll(); + } + } + } + return ret; + } + + /** * Waits for the next socket connecting. If a remote user tried to make a * connection and the local application wasn't .accept()ing new connections, * they should get refused (if .accept() doesnt occur in some small period - @@ -68,24 +151,10 @@ class I2PServerSocketImpl implements I2PServerSocket { I2PSocket ret = null; while ( (ret == null) && (!closing) ){ - while (pendingSockets.size() <= 0) { - if (closing) throw new ConnectException("I2PServerSocket closed"); - try { - synchronized(socketAddedLock) { - socketAddedLock.wait(); - } - } catch (InterruptedException ie) {} - } - synchronized (pendingSockets) { - if (pendingSockets.size() > 0) { - ret = (I2PSocket)pendingSockets.remove(0); - } - } - if (ret != null) { - synchronized (socketAcceptedLock) { - socketAcceptedLock.notifyAll(); - } - } + + this.waitIncoming(-1); + + ret = accept(false); } if (_log.shouldLog(Log.DEBUG)) diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketImpl.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketImpl.java index dc7a48fd4..b37bf1085 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketImpl.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketImpl.java @@ -350,7 +350,6 @@ class I2PSocketImpl implements I2PSocket { read = bc.startToByteArray(len); bc.notifyAll(); } - boolean timedOut = false; while ( (read.length == 0) && (!inStreamClosed) ) { synchronized (flagLock) { diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java index 33477a4a8..2cf23f134 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java @@ -13,7 +13,6 @@ import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; -import net.i2p.data.Destination; import net.i2p.util.Log; /** diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java index 406f71847..a272e99df 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java @@ -43,12 +43,12 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener { private I2PSession _session; private I2PServerSocketImpl _serverSocket = null; private Object lock = new Object(); // for locking socket lists - private HashMap _outSockets; - private HashMap _inSockets; + private HashMap _outSockets; + private HashMap _inSockets; private I2PSocketOptions _defaultOptions; private long _acceptTimeout; private String _name; - private List _listeners; + private List _listeners; private static int __managerId = 0; public static final short ACK = 0x51; @@ -76,10 +76,10 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener { _name = name; _context = context; _log = _context.logManager().getLog(I2PSocketManager.class); - _inSockets = new HashMap(16); - _outSockets = new HashMap(16); + _inSockets = new HashMap(16); + _outSockets = new HashMap(16); _acceptTimeout = ACCEPT_TIMEOUT_DEFAULT; - _listeners = new ArrayList(1); + _listeners = new ArrayList(1); setSession(session); setDefaultOptions(buildOptions(opts)); _context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); @@ -113,9 +113,9 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener { public void disconnected(I2PSession session) { _log.info(getName() + ": Disconnected from the session"); destroySocketManager(); - List listeners = null; + List listeners = null; synchronized (_listeners) { - listeners = new ArrayList(_listeners); + listeners = new ArrayList(_listeners); _listeners.clear(); } for (int i = 0; i < listeners.size(); i++) { @@ -130,7 +130,6 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener { public void messageAvailable(I2PSession session, int msgId, long size) { try { - I2PSocketImpl s; byte msg[] = session.receiveMessage(msgId); if (msg.length == 1 && msg[0] == -1) { _log.debug(getName() + ": Ping received"); @@ -660,7 +659,7 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener { * */ public Set listSockets() { - Set sockets = new HashSet(8); + Set sockets = new HashSet(8); synchronized (lock) { sockets.addAll(_inSockets.values()); sockets.addAll(_outSockets.values()); diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/TestSwarm.java b/apps/ministreaming/java/src/net/i2p/client/streaming/TestSwarm.java index 1c3ef54e9..a29b9f6f1 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/TestSwarm.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/TestSwarm.java @@ -28,6 +28,10 @@ public class TestSwarm { private String _conOptions; // unused? used elsewhere? private boolean _dead; // unused? used elsewhere? + public void antiCompilationWarnings() { + _log.debug(""+_conOptions+_dead); + } + public static void main(String args[]) { if (args.length < 1) { System.err.println("Usage: TestSwarm myDestFile [peerDestFile ]*"); @@ -131,6 +135,14 @@ public class TestSwarm { _context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 }); } + public void antiCompilationWarnings() { + _log.debug(""+this._lastReceived+this._lastReceivedOn+this._started); + } + public void antiCompilationWarnings(long x, long y) { + if (false) + _log.debug(""+x+y); + } + public Flooder(I2PSocket socket) { _socket = socket; _remoteDestination = socket.getPeerDestination(); @@ -154,6 +166,8 @@ public class TestSwarm { _context.random().nextBytes(data); long value = 0; long lastSend = _context.clock().now(); + this.antiCompilationWarnings(value, lastSend); + if (_socket == null) { try { _socket = _manager.connect(_remoteDestination); diff --git a/apps/sam/Demos/datagramTests/README.txt b/apps/sam/Demos/datagramTests/README.txt new file mode 100644 index 000000000..8e79434f9 --- /dev/null +++ b/apps/sam/Demos/datagramTests/README.txt @@ -0,0 +1,15 @@ +# test example + +#in a first terminal, launch : + ./samIn.py inTest + +#in a second terminal, launch : + ./samForward.py 25000 forward + +#in a third terminal, launch : +l=0 +while [ $l -lt 1000 ] +do + l=$((l+1)) + ./samOut.py forward this is message n. $l +done diff --git a/apps/sam/Demos/datagramTests/samForward.py b/apps/sam/Demos/datagramTests/samForward.py new file mode 100755 index 000000000..56590e7ef --- /dev/null +++ b/apps/sam/Demos/datagramTests/samForward.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +import socket +import sys + +# create a forward style SAM datagram session +# that forwards messages on specified port (default port : 25000) +# creates a standard datagram server that listens on this port forever +# usage : ./samForward.py [port [SAM session name]] + +if len(sys.argv)>=2 : + port = eval(sys.argv[1]) +else : + port = 25000 + +if len(sys.argv)==3 : + name = sys.argv[2] +else : + name = "essaiSamForward" + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=DATAGRAM PORT="+str(port)+" ID="+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n") +sys.stdout.write(sess.recv(10000)) + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(("", port)) +print "waiting on port:", port +while 1: + data, addr = s.recvfrom(40000) + print data, " received from ", addr, "length=", len(data) + diff --git a/apps/sam/Demos/datagramTests/samIn.py b/apps/sam/Demos/datagramTests/samIn.py new file mode 100755 index 000000000..c2c0589f3 --- /dev/null +++ b/apps/sam/Demos/datagramTests/samIn.py @@ -0,0 +1,29 @@ +#!/usr/bin/python + + +# create a SAM datagram session that writes incoming messages on its master session stream +# and a listen forever +# usage : ./samIn.py [session name] + +import socket +import sys + +if len(sys.argv)==2 : + name = sys.argv[1] +else : + name = "datagramSamIn" + + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=DATAGRAM ID="+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n") +sys.stdout.write(sess.recv(1000)) +while 1 : + chunk = sess.recv(10000) + sys.stdout.write(chunk+'\n') + if not chunk : break +print + diff --git a/apps/sam/Demos/datagramTests/samOut.py b/apps/sam/Demos/datagramTests/samOut.py new file mode 100755 index 000000000..bb9be4db2 --- /dev/null +++ b/apps/sam/Demos/datagramTests/samOut.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +# sends a message to datagram destinations opened by samForward.py and samIn.py, using specified sending session name +# at least samForward.py should be running for results to be seen +# usage : ./samOut.py [ sendingSessionName [ message ... ] ] +# sendingSessionName : default = datagramSamForward +# message : default = "this is nice message" + +import socket +import sys +import time + +if len(sys.argv)>=2 : + name = sys.argv[1] +else : + name = "datagramSamForward" + +if len(sys.argv)>2 : + message = ''.join([s+' ' for s in sys.argv[2:]]).strip() +else : + message = "This is a nice message" + + +# client.py +port = 7655 +host = "localhost" +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(("", 0)) +s.sendto(name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port)) +s.sendto(name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port)) + diff --git a/apps/sam/Demos/rawTests/README.txt b/apps/sam/Demos/rawTests/README.txt new file mode 100644 index 000000000..8e79434f9 --- /dev/null +++ b/apps/sam/Demos/rawTests/README.txt @@ -0,0 +1,15 @@ +# test example + +#in a first terminal, launch : + ./samIn.py inTest + +#in a second terminal, launch : + ./samForward.py 25000 forward + +#in a third terminal, launch : +l=0 +while [ $l -lt 1000 ] +do + l=$((l+1)) + ./samOut.py forward this is message n. $l +done diff --git a/apps/sam/Demos/rawTests/samForward.py b/apps/sam/Demos/rawTests/samForward.py new file mode 100755 index 000000000..6d55da5f7 --- /dev/null +++ b/apps/sam/Demos/rawTests/samForward.py @@ -0,0 +1,36 @@ +#!/usr/bin/python + +import socket +import sys + +# create a forward style SAM raw datagram session +# that forwards messages on specified port (default port : 25000) +# creates a standard datagram server that listens on this port forever +# usage : ./samForward.py [port [SAM session name]] + +if len(sys.argv)>=2 : + port = eval(sys.argv[1]) +else : + port = 25000 + +if len(sys.argv)==3 : + name = sys.argv[2] +else : + name = "essaiSamForward" + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=RAW PORT="+str(port)+" ID="+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n") +sys.stdout.write(sess.recv(10000)) + +# listening server +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(("", port)) +print "waiting on port:", port +while 1: + data, addr = s.recvfrom(40000) + print data, " received from ", addr, "length=", len(data) + diff --git a/apps/sam/Demos/rawTests/samIn.py b/apps/sam/Demos/rawTests/samIn.py new file mode 100755 index 000000000..0f89deb17 --- /dev/null +++ b/apps/sam/Demos/rawTests/samIn.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + + +# create a SAM datagram session that writes incoming messages on its master session stream +# and a listen forever +# usage : ./samIn.py [session name] + +import socket +import sys + +if len(sys.argv)==2 : + name = sys.argv[1] +else : + name = "datagramSamIn" + + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=RAW ID="+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n") +sys.stdout.write(sess.recv(1000)) + +# listen incoming messages +while 1 : + chunk = sess.recv(10000) + sys.stdout.write(chunk+'\n') + if not chunk : break +print + diff --git a/apps/sam/Demos/rawTests/samOut.py b/apps/sam/Demos/rawTests/samOut.py new file mode 100755 index 000000000..bb9be4db2 --- /dev/null +++ b/apps/sam/Demos/rawTests/samOut.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +# sends a message to datagram destinations opened by samForward.py and samIn.py, using specified sending session name +# at least samForward.py should be running for results to be seen +# usage : ./samOut.py [ sendingSessionName [ message ... ] ] +# sendingSessionName : default = datagramSamForward +# message : default = "this is nice message" + +import socket +import sys +import time + +if len(sys.argv)>=2 : + name = sys.argv[1] +else : + name = "datagramSamForward" + +if len(sys.argv)>2 : + message = ''.join([s+' ' for s in sys.argv[2:]]).strip() +else : + message = "This is a nice message" + + +# client.py +port = 7655 +host = "localhost" +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(("", 0)) +s.sendto(name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port)) +s.sendto(name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port)) + diff --git a/apps/sam/Demos/streamTests/README.txt b/apps/sam/Demos/streamTests/README.txt new file mode 100644 index 000000000..6ce7ae57b --- /dev/null +++ b/apps/sam/Demos/streamTests/README.txt @@ -0,0 +1,24 @@ +# test example + +#in a first terminal, launch : + ./samIn.py inTest + +#in a second terminal, launch : + ./samOut.py + +#and again + ./samOut.py + +########## + +# test example n°2 + +#in a first terminal, launch : + ./samForward.py inTest + +#in a second terminal, launch : + ./server.py + +#in a third terminal, launch : + ./samOut.py + diff --git a/apps/sam/Demos/streamTests/samForward.py b/apps/sam/Demos/streamTests/samForward.py new file mode 100755 index 000000000..c12753405 --- /dev/null +++ b/apps/sam/Demos/streamTests/samForward.py @@ -0,0 +1,58 @@ +#!/usr/bin/python + +import socket +import sys + +# create a master SAM stream session that opens a destination in I2P world +# then open another session that tells SAM to forward incoming connections +# to the specified address +# +# usage : +# ./samForward.py [ silent [ port [ sessionName [ host ] ] ] ] +# +# silent : should the first line of incoming socket contain the peer destination (true or false) +# port : port to which connections are forwarded (default : 25000) +# sessionName : session id (default : "forward") +# host : host to which connections are forwarded (default : this host) + +if len(sys.argv)>=2 : + silent = " SILENT="+sys.argv[1] +else : silent = " SILENT=false" + +if len(sys.argv)>=3 : + port = " PORT="+sys.argv[2] +else : port = " PORT=25000" + +if len(sys.argv)>=4 : + name = " ID="+sys.argv[3] +else : name = " ID=forward" + +if len(sys.argv)>=5 : + host = " HOST="+sys.argv[4] +else : host = "" + + + + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=STREAM"+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n") +sys.stdout.write(sess.recv(1000)) + +sock = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sock.connect(("127.0.0.1",7656)); +sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sock.recv(1000)) +sock.send("STREAM FORWARD" + name + host + port + silent + "\n") +sys.stdout.write(sock.recv(1000)) + +l=0 +while 1 : + chunk = sock.recv(100) + sys.stdout.write(chunk) + if not chunk : break + diff --git a/apps/sam/Demos/streamTests/samIn.py b/apps/sam/Demos/streamTests/samIn.py new file mode 100755 index 000000000..2edfd65c6 --- /dev/null +++ b/apps/sam/Demos/streamTests/samIn.py @@ -0,0 +1,89 @@ +#!/usr/bin/python + + +# create an stream session +# then an "accept" stream connected to this session +# then another "accept" stream from the same session +# then listen from the first stream and then listen from the second +# usage : ./samIn.py [ silent [ name ] ] +# name : the session id ( defaults to InTest ) +# silent : true or false : tells wether we want to receive the incoming stream destination +# as first line + +import socket +import sys +import time + +if len(sys.argv)>=2 : + silent = " SILENT="+sys.argv[1] +else : silent = " SILENT=false" + +if len(sys.argv)>=3 : + name = sys.argv[2] +else : name = "inTest" + + + + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=STREAM ID="+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n") +sys.stdout.write(sess.recv(1000)) + + + + + +def accept() : + sock = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) + sock.connect(("127.0.0.1",7656)); + sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n") + sys.stdout.write(sock.recv(1000)) + sock.send("STREAM ACCEPT ID=" + name + silent+"\n") + print "STREAM ACCEPT ID="+name+silent+"\n" + return sock + +def echo( sock, lines ) : + l = 0 + while lines==-1 or l=2 : + silent = " SILENT="+sys.argv[1] +else : silent = " SILENT=false" + +if len(sys.argv)>=3 : + name = " ID="+sys.argv[2] +else : name = " ID=testOutStream" + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=STREAM"+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n") +sys.stdout.write(sess.recv(1000)) + +sock = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sock.connect(("127.0.0.1",7656)); +sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sock.recv(1000)) +sock.send("STREAM CONNECT"+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA"+silent+"\n") + +# wait for acknowledgement before sending data, if we asked for it +if (silent==" SILENT=false") : + sys.stdout.write(sock.recv(1000)) + +for i in range(1,11) : + sock.send(str(i)+'\n') + buf=sock.recv(1000) + sys.stdout.write(str(i)+' '+buf) + if not buf : break + +print + + diff --git a/apps/sam/Demos/streamTests/samOutWithNaming.py b/apps/sam/Demos/streamTests/samOutWithNaming.py new file mode 100755 index 000000000..6aa6476bc --- /dev/null +++ b/apps/sam/Demos/streamTests/samOutWithNaming.py @@ -0,0 +1,51 @@ +#!/usr/bin/python + + +# open a I2P stream destination +# then open another stream that connects to the destination created by samForward.py or samIn.py +# then send bytes through the stream +# usage : +# ./samOut.py [ silent [ sessionName ] ] +# +# silent : should the first incoming after the connection request contain the connection status message (true or false) +# sessionName : session id (default : "forward") + +import socket +import sys +import time + +if len(sys.argv)>=2 : + silent = " SILENT="+sys.argv[1] +else : silent = " SILENT=false" + +if len(sys.argv)>=3 : + name = " ID="+sys.argv[2] +else : name = " ID=testOutStream" + +sess = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sess.connect(("127.0.0.1",7656)); +sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sess.recv(1000)) +sess.send("SESSION CREATE STYLE=STREAM"+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n") +sys.stdout.write(sess.recv(1000)) + +sock = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +sock.connect(("127.0.0.1",7656)); +sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n") +sys.stdout.write(sock.recv(1000)) +sock.send("STREAM CONNECT"+name+" DESTINATION=http://amiga.i2p"+silent+"\n") + +# wait for acknowledgement before sending data, if we asked for it +if (silent==" SILENT=false") : + sys.stdout.write(sock.recv(1000)) + +while (1) : + buf=sock.recv(1000) + sys.stdout.write(buf) + if not buf : break + +print + + diff --git a/apps/sam/Demos/streamTests/server.py b/apps/sam/Demos/streamTests/server.py new file mode 100755 index 000000000..829a37e32 --- /dev/null +++ b/apps/sam/Demos/streamTests/server.py @@ -0,0 +1,41 @@ +#!/usr/bin/python + + +# echo server +# accepts a socket on specified port, writes on stdout and send back incoming data + +import socket +import sys + +if len(sys.argv)>=2 : + port = eval(sys.argv[1]) +else : port = 25000 + +#create an INET, STREAMing socket +serversocket = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) +#bind the socket to a public host, +# and a well-known port +serversocket.bind(("0.0.0.0", port)) + #become a server socket +serversocket.listen(1) + + + #accept connections from outside +(clientsocket, address) = serversocket.accept() + #now do something with the clientsocket + #in this case, we'll pretend this is a threaded server + +i = 0 +while 1 : + chunk = clientsocket.recv(1024) + i = i + 1 + sys.stdout.write(str(i)+' '+chunk) + if not chunk: break + clientsocket.send(str(i)+' '+chunk) + + +clientsocket.close() + +print + diff --git a/apps/sam/doc/protocol-v3.txt b/apps/sam/doc/protocol-v3.txt new file mode 100644 index 000000000..9aacfa124 --- /dev/null +++ b/apps/sam/doc/protocol-v3.txt @@ -0,0 +1,17 @@ +telnet localhost 7656 +HELLO VERSION MIN=3.0 MAX=3.0 +SESSION CREATE STYLE=STREAM ID=essaiSamIn DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p + +telnet localhost 7656 +HELLO VERSION MIN=3.0 MAX=3.0 +STREAM ACCEPT ID=essaiSamIn + + +telnet localhost 7656 +HELLO VERSION MIN=3.0 MAX=3.0 +SESSION CREATE STYLE=STREAM ID=essaiSamOut DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW + +telnet localhost 7656 +HELLO VERSION MIN=3.0 MAX=3.0 +STREAM CONNECT ID=essaiSamOut DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA + diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index ee7e33bb4..95ece882f 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -14,9 +14,10 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; +import java.net.InetSocketAddress; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -34,7 +35,7 @@ import net.i2p.util.Log; */ public class SAMBridge implements Runnable { private final static Log _log = new Log(SAMBridge.class); - private ServerSocket serverSocket; + private ServerSocketChannel serverSocket; private Properties i2cpProps; /** * filename in which the name to private key mapping should @@ -45,12 +46,17 @@ public class SAMBridge implements Runnable { * app designated destination name to the base64 of the I2P formatted * destination keys (Destination+PrivateKey+SigningPrivateKey) */ - private Map nameToPrivKeys; + private Map nameToPrivKeys; private boolean acceptConnections = true; private static final int SAM_LISTENPORT = 7656; public static final String DEFAULT_SAM_KEYFILE = "sam.keys"; + public static final String PROP_DATAGRAM_HOST = "sam.datagram.host"; + public static final String PROP_DATAGRAM_PORT = "sam.datagram.port"; + public static final String DEFAULT_DATAGRAM_HOST = "0.0.0.0"; + public static final String DEFAULT_DATAGRAM_PORT = "7655"; + private SAMBridge() {} @@ -64,16 +70,18 @@ public class SAMBridge implements Runnable { */ public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) { persistFilename = persistFile; - nameToPrivKeys = new HashMap(8); + nameToPrivKeys = new HashMap(8); loadKeys(); try { if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) { - serverSocket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost)); + serverSocket = ServerSocketChannel.open(); + serverSocket.socket().bind(new InetSocketAddress(listenHost, listenPort)); if (_log.shouldLog(Log.DEBUG)) _log.debug("SAM bridge listening on " + listenHost + ":" + listenPort); } else { - serverSocket = new ServerSocket(listenPort); + serverSocket = ServerSocketChannel.open(); + serverSocket.socket().bind(new InetSocketAddress(listenPort)); if (_log.shouldLog(Log.DEBUG)) _log.debug("SAM bridge listening on 0.0.0.0:" + listenPort); } @@ -193,12 +201,12 @@ public class SAMBridge implements Runnable { /** * Usage: - *
SAMBridge [[listenHost ]listenPort[ name=val]*]
+ *
SAMBridge [ keyfile [listenHost ] listenPort [ name=val ]* ]
* * name=val options are passed to the I2CP code to build a session, * allowing the bridge to specify an alternate I2CP host and port, tunnel * depth, etc. - * @param args [[listenHost ]listenPort[ name=val]*] + * @param args [ keyfile [ listenHost ] listenPort [ name=val ]* ] */ public static void main(String args[]) { String keyfile = DEFAULT_SAM_KEYFILE; @@ -266,11 +274,11 @@ public class SAMBridge implements Runnable { if (serverSocket == null) return; try { while (acceptConnections) { - Socket s = serverSocket.accept(); + SocketChannel s = serverSocket.accept(); if (_log.shouldLog(Log.DEBUG)) _log.debug("New connection from " - + s.getInetAddress().toString() + ":" - + s.getPort()); + + s.socket().getInetAddress().toString() + ":" + + s.socket().getPort()); try { SAMHandler handler = SAMHandlerFactory.createSAMHandler(s, i2cpProps); @@ -289,7 +297,7 @@ public class SAMBridge implements Runnable { _log.error("SAM error: " + e.getMessage(), e); try { String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n"; - s.getOutputStream().write(reply.getBytes("ISO-8859-1")); + s.write(ByteBuffer.wrap(reply.getBytes("ISO-8859-1"))); } catch (IOException ioe) { if (_log.shouldLog(Log.ERROR)) _log.error("SAM Error sending error reply", ioe); diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java index a3e20f7df..c8d31b489 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java @@ -30,7 +30,7 @@ public class SAMDatagramSession extends SAMMessageSession { private final static Log _log = new Log(SAMDatagramSession.class); public static int DGRAM_SIZE_MAX = 31*1024; - private SAMDatagramReceiver recv = null; + protected SAMDatagramReceiver recv = null; private I2PDatagramMaker dgramMaker; private I2PDatagramDissector dgramDissector = new I2PDatagramDissector(); @@ -84,9 +84,10 @@ public class SAMDatagramSession extends SAMMessageSession { public boolean sendBytes(String dest, byte[] data) throws DataFormatException { if (data.length > DGRAM_SIZE_MAX) throw new DataFormatException("Datagram size exceeded (" + data.length + ")"); - - byte[] dgram = dgramMaker.makeI2PDatagram(data); - + byte[] dgram ; + synchronized (dgramMaker) { + dgram = dgramMaker.makeI2PDatagram(data); + } return sendBytesThroughMessageSession(dest, dgram); } diff --git a/apps/sam/java/src/net/i2p/sam/SAMException.java b/apps/sam/java/src/net/i2p/sam/SAMException.java index e51e35ea4..ae965a4c8 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMException.java +++ b/apps/sam/java/src/net/i2p/sam/SAMException.java @@ -15,11 +15,13 @@ package net.i2p.sam; */ public class SAMException extends Exception { + static final long serialVersionUID = 1 ; + public SAMException() { - super(); + super(); } public SAMException(String s) { - super(s); + super(s); } } diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandler.java b/apps/sam/java/src/net/i2p/sam/SAMHandler.java index 64d824a57..d53a5a662 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandler.java @@ -9,9 +9,8 @@ package net.i2p.sam; */ import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; +import java.nio.channels.SocketChannel; +import java.nio.ByteBuffer; import java.util.Properties; import net.i2p.util.I2PAppThread; @@ -32,8 +31,7 @@ public abstract class SAMHandler implements Runnable { protected SAMBridge bridge = null; private Object socketWLock = new Object(); // Guards writings on socket - private Socket socket = null; - private OutputStream socketOS = null; // Stream associated to socket + protected SocketChannel socket = null; protected int verMajor = 0; protected int verMinor = 0; @@ -53,10 +51,9 @@ public abstract class SAMHandler implements Runnable { * @param i2cpProps properties to configure the I2CP connection (host, port, etc) * @throws IOException */ - protected SAMHandler(Socket s, + protected SAMHandler(SocketChannel s, int verMajor, int verMinor, Properties i2cpProps) throws IOException { socket = s; - socketOS = socket.getOutputStream(); this.verMajor = verMajor; this.verMinor = verMinor; @@ -86,8 +83,8 @@ public abstract class SAMHandler implements Runnable { * @return input stream * @throws IOException */ - protected final InputStream getClientSocketInputStream() throws IOException { - return socket.getInputStream(); + protected final SocketChannel getClientSocket() { + return socket ; } /** @@ -98,13 +95,17 @@ public abstract class SAMHandler implements Runnable { * @param data A byte array to be written * @throws IOException */ - protected final void writeBytes(byte[] data) throws IOException { + protected final void writeBytes(ByteBuffer data) throws IOException { synchronized (socketWLock) { - socketOS.write(data); - socketOS.flush(); + writeBytes(data, socket); } } + static public void writeBytes(ByteBuffer data, SocketChannel out) throws IOException { + while (data.hasRemaining()) out.write(data); + out.socket().getOutputStream().flush(); + } + /** * If you're crazy enough to write to the raw socket, grab the write lock * with getWriteLock(), synchronize against it, and write to the getOut() @@ -112,7 +113,6 @@ public abstract class SAMHandler implements Runnable { * @return socket Write lock object */ protected Object getWriteLock() { return socketWLock; } - protected OutputStream getOut() { return socketOS; } /** * Write a string to the handler's socket. This method must @@ -121,21 +121,25 @@ public abstract class SAMHandler implements Runnable { * * @param str A byte array to be written * - * @return True is the string was successfully written, false otherwise + * @return True if the string was successfully written, false otherwise */ protected final boolean writeString(String str) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Sending the client: [" + str + "]"); - try { - writeBytes(str.getBytes("ISO-8859-1")); + return writeString(str, socket); + } + + public static boolean writeString(String str, SocketChannel out) + { + try { + writeBytes(ByteBuffer.wrap(str.getBytes("ISO-8859-1")), out); } catch (IOException e) { _log.debug("Caught IOException", e); return false; } - - return true; + return true ; } - + /** * Close the socket connected to the SAM client. * @@ -178,8 +182,8 @@ public abstract class SAMHandler implements Runnable { return ("SAM handler (class: " + this.getClass().getName() + "; SAM version: " + verMajor + "." + verMinor + "; client: " - + this.socket.getInetAddress().toString() + ":" - + this.socket.getPort() + ")"); + + this.socket.socket().getInetAddress().toString() + ":" + + this.socket.socket().getPort() + ")"); } public final void run() { diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index 21a0e97d2..b5c3d198c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -9,9 +9,9 @@ package net.i2p.sam; */ import java.io.IOException; -import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.net.Socket; +import java.nio.channels.SocketChannel; +import java.nio.ByteBuffer; import java.util.Properties; import java.util.StringTokenizer; @@ -34,17 +34,17 @@ public class SAMHandlerFactory { * @throws SAMException if the connection handshake (HELLO message) was malformed * @return A SAM protocol handler, or null if the client closed before the handshake */ - public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException { + public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps) throws SAMException { String line; StringTokenizer tok; try { - line = DataHelper.readLine(s.getInputStream()); + line = DataHelper.readLine(s.socket().getInputStream()); if (line == null) { _log.debug("Connection closed by client"); return null; } - tok = new StringTokenizer(line, " "); + tok = new StringTokenizer(line.trim(), " "); } catch (IOException e) { throw new SAMException("Error reading from socket: " + e.getMessage()); @@ -89,9 +89,8 @@ public class SAMHandlerFactory { // Let's answer positively try { - OutputStream out = s.getOutputStream(); - out.write(("HELLO REPLY RESULT=OK VERSION=" - + ver + "\n").getBytes("ISO-8859-1")); + s.write(ByteBuffer.wrap(("HELLO REPLY RESULT=OK VERSION=" + + ver + "\n").getBytes("ISO-8859-1"))); } catch (UnsupportedEncodingException e) { _log.error("Caught UnsupportedEncodingException (" + e.getMessage() + ")"); @@ -115,6 +114,9 @@ public class SAMHandlerFactory { case 2: handler = new SAMv2Handler(s, verMajor, verMinor, i2cpProps); break; + case 3: + handler = new SAMv3Handler(s, verMajor, verMinor, i2cpProps); + break; default: _log.error("BUG! Trying to initialize the wrong SAM version!"); throw new SAMException("BUG! (in handler instantiation)"); @@ -128,6 +130,7 @@ public class SAMHandlerFactory { /* Return the best version we can use, or null on failure */ private static String chooseBestVersion(String minVer, String maxVer) { + int minMajor = getMajor(minVer), minMinor = getMinor(minVer); int maxMajor = getMajor(maxVer), maxMinor = getMinor(maxVer); @@ -143,6 +146,8 @@ public class SAMHandlerFactory { float fmaxVer = (float) maxMajor + (float) maxMinor / 10 ; + if ( ( fminVer <= 3.0 ) && ( fmaxVer >= 3.0 ) ) return "3.0" ; + if ( ( fminVer <= 2.0 ) && ( fmaxVer >= 2.0 ) ) return "2.0" ; if ( ( fminVer <= 1.0 ) && ( fmaxVer >= 1.0 ) ) return "1.0" ; diff --git a/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java b/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java index b52ecda65..cd1c6b1a5 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java +++ b/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java @@ -15,7 +15,8 @@ package net.i2p.sam; * @author human */ public class SAMInvalidDirectionException extends Exception { - + static final long serialVersionUID = 1 ; + public SAMInvalidDirectionException() { super(); } diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java index b29b2f84c..2c8ed2756 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java @@ -109,8 +109,7 @@ public abstract class SAMMessageSession { * @throws DataFormatException */ protected boolean sendBytesThroughMessageSession(String dest, byte[] data) throws DataFormatException { - Destination d = new Destination(); - d.fromBase64(dest); + Destination d = SAMUtils.getDest(dest); if (_log.shouldLog(Log.DEBUG)) { _log.debug("Sending " + data.length + " bytes to " + dest); diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java index 7f56066b1..92bf4960d 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java @@ -26,7 +26,7 @@ public class SAMRawSession extends SAMMessageSession { private final static Log _log = new Log(SAMRawSession.class); public static final int RAW_SIZE_MAX = 32*1024; - private SAMRawReceiver recv = null; + protected SAMRawReceiver recv = null; /** * Create a new SAM RAW session. * diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java b/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java index 6d6d824b5..326c81020 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java +++ b/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java @@ -9,6 +9,7 @@ package net.i2p.sam; */ import java.io.IOException; +import java.nio.ByteBuffer; import net.i2p.data.Destination; @@ -60,7 +61,7 @@ public interface SAMStreamReceiver { * @param len Number of bytes in data * @throws IOException */ - public void receiveStreamBytes(int id, byte data[], int len) throws IOException; + public void receiveStreamBytes(int id, ByteBuffer data) throws IOException; /** * Notify that a connection has been closed diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java index 280562e48..aef2802bd 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java @@ -13,6 +13,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; import java.net.ConnectException; import java.net.NoRouteToHostException; import java.util.ArrayList; @@ -51,15 +53,15 @@ public class SAMStreamSession { protected SAMStreamReceiver recv = null; - private SAMStreamSessionServer server = null; + protected SAMStreamSessionServer server = null; protected I2PSocketManager socketMgr = null; private Object handlersMapLock = new Object(); /** stream id (Long) to SAMStreamSessionSocketReader */ - private HashMap handlersMap = new HashMap(); + private HashMap handlersMap = new HashMap(); /** stream id (Long) to StreamSender */ - private HashMap sendersMap = new HashMap(); + private HashMap sendersMap = new HashMap(); private Object idLock = new Object(); private int lastNegativeId = 0; @@ -76,6 +78,10 @@ public class SAMStreamSession { public static String PROP_FORCE_FLUSH = "sam.forceFlush"; public static String DEFAULT_FORCE_FLUSH = "false"; + public SAMStreamSession() { + + } + /** * Create a new SAM STREAM session. * @@ -166,7 +172,7 @@ public class SAMStreamSession { } } - private class DisconnectListener implements I2PSocketManager.DisconnectListener { + protected class DisconnectListener implements I2PSocketManager.DisconnectListener { public void sessionDisconnected() { close(); } @@ -572,19 +578,20 @@ public class SAMStreamSession { _log.debug("run() called for socket reader " + id); int read = -1; - byte[] data = new byte[SOCKET_HANDLER_BUF_SIZE]; + ByteBuffer data = ByteBuffer.allocateDirect(SOCKET_HANDLER_BUF_SIZE); try { InputStream in = i2pSocket.getInputStream(); while (stillRunning) { - read = in.read(data); + data.clear(); + read = Channels.newChannel(in).read(data); if (read == -1) { _log.debug("Handler " + id + ": connection closed"); break; } - - recv.receiveStreamBytes(id, data, read); + data.flip(); + recv.receiveStreamBytes(id, data); } } catch (IOException e) { _log.debug("Caught IOException", e); @@ -650,7 +657,7 @@ public class SAMStreamSession { protected class v1StreamSender extends StreamSender { - private List _data; + private List _data; private int _id; private ByteCache _cache; private OutputStream _out = null; @@ -660,7 +667,7 @@ public class SAMStreamSession { public v1StreamSender ( I2PSocket s, int id ) throws IOException { super ( s, id ); - _data = new ArrayList(1); + _data = new ArrayList(1); _id = id; _cache = ByteCache.getInstance(4, 32*1024); _out = s.getOutputStream(); diff --git a/apps/sam/java/src/net/i2p/sam/SAMUtils.java b/apps/sam/java/src/net/i2p/sam/SAMUtils.java index 8bb3fac30..6a6d81f88 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMUtils.java +++ b/apps/sam/java/src/net/i2p/sam/SAMUtils.java @@ -101,6 +101,24 @@ public class SAMUtils { return dest; } + /** + * Resolve the destination from a key or a hostname + * + * @param s Hostname or key to be resolved + * + * @return the Destination for the specified hostname, or null if not found + */ + public static Destination getDest(String s) + { + Destination d = new Destination() ; + try { + d.fromBase64(s); + return d ; + } catch (DataFormatException e) { + return lookupHost(s, null); + } + } + /** * Parse SAM parameters, and put them into a Propetries object * diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java index 93a9a8d66..ac86d6934 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java @@ -12,12 +12,11 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; -import java.io.InputStream; import java.io.InterruptedIOException; -import java.io.OutputStream; import java.net.ConnectException; import java.net.NoRouteToHostException; -import java.net.Socket; +import java.nio.channels.SocketChannel; +import java.nio.ByteBuffer; import java.util.Properties; import java.util.StringTokenizer; @@ -40,14 +39,14 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag private final static Log _log = new Log(SAMv1Handler.class); - private final static int IN_BUFSIZE = 2048; + protected SAMRawSession rawSession = null; + protected SAMDatagramSession datagramSession = null; + protected SAMStreamSession streamSession = null; + protected SAMDatagramSession getDatagramSession() {return datagramSession ;} + protected SAMRawSession getRawSession() {return rawSession ;} - private SAMRawSession rawSession = null; - private SAMDatagramSession datagramSession = null; - protected SAMStreamSession streamSession = null; - - private long _id; - private static volatile long __id = 0; + protected long _id; + protected static volatile long __id = 0; /** * Create a new SAM version 1 handler. This constructor expects @@ -60,7 +59,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag * @throws SAMException * @throws IOException */ - public SAMv1Handler(Socket s, int verMajor, int verMinor) throws SAMException, IOException { + public SAMv1Handler(SocketChannel s, int verMajor, int verMinor) throws SAMException, IOException { this(s, verMajor, verMinor, new Properties()); } /** @@ -75,7 +74,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag * @throws SAMException * @throws IOException */ - public SAMv1Handler(Socket s, int verMajor, int verMinor, Properties i2cpProps) throws SAMException, IOException { + public SAMv1Handler(SocketChannel s, int verMajor, int verMinor, Properties i2cpProps) throws SAMException, IOException { super(s, verMajor, verMinor, i2cpProps); _id = ++__id; _log.debug("SAM version 1 handler instantiated"); @@ -101,16 +100,13 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag _log.debug("SAM handling started"); try { - InputStream in = getClientSocketInputStream(); - int b = -1; - while (true) { if (shouldStop()) { _log.debug("Stop request found"); break; } - msg = DataHelper.readLine(in); + msg = DataHelper.readLine(getClientSocket().socket().getInputStream()).trim(); if (msg == null) { _log.debug("Connection closed by client"); break; @@ -175,11 +171,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } catch (IOException e) { _log.error("Error closing socket: " + e.getMessage()); } - if (rawSession != null) { - rawSession.close(); + if (getRawSession() != null) { + getRawSession().close(); } - if (datagramSession != null) { - datagramSession.close(); + if (getDatagramSession() != null) { + getDatagramSession().close(); } if (streamSession != null) { streamSession.close(); @@ -188,13 +184,13 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } /* Parse and execute a SESSION message */ - private boolean execSessionMessage(String opcode, Properties props) { + protected boolean execSessionMessage(String opcode, Properties props) { String dest = "BUG!"; try{ if (opcode.equals("CREATE")) { - if ((rawSession != null) || (datagramSession != null) + if ((getRawSession() != null) || (getDatagramSession() != null) || (streamSession != null)) { _log.debug("Trying to create a session, but one still exists"); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n"); @@ -293,7 +289,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } /* Parse and execute a DEST message*/ - private boolean execDestMessage(String opcode, Properties props) { + protected boolean execDestMessage(String opcode, Properties props) { if (opcode.equals("GENERATE")) { if (props.size() > 0) { @@ -318,7 +314,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } /* Parse and execute a NAMING message */ - private boolean execNamingMessage(String opcode, Properties props) { + protected boolean execNamingMessage(String opcode, Properties props) { if (opcode.equals("LOOKUP")) { if (props == null) { _log.debug("No parameters specified in NAMING LOOKUP message"); @@ -333,18 +329,18 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag Destination dest; if (name.equals("ME")) { - if (rawSession != null) { - dest = rawSession.getDestination(); + if (getRawSession() != null) { + dest = getRawSession().getDestination(); } else if (streamSession != null) { dest = streamSession.getDestination(); - } else if (datagramSession != null) { - dest = datagramSession.getDestination(); + } else if (getDatagramSession() != null) { + dest = getDatagramSession().getDestination(); } else { _log.debug("Lookup for SESSION destination, but session is null"); return false; } } else { - dest = SAMUtils.lookupHost(name, null); + dest = SAMUtils.getDest(name); } if (dest == null) { @@ -364,8 +360,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag /* Parse and execute a DATAGRAM message */ - private boolean execDatagramMessage(String opcode, Properties props) { - if (datagramSession == null) { + protected boolean execDatagramMessage(String opcode, Properties props) { + if (getDatagramSession() == null) { _log.error("DATAGRAM message received, but no DATAGRAM session exists"); return false; } @@ -403,7 +399,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } try { - DataInputStream in = new DataInputStream(getClientSocketInputStream()); + DataInputStream in = new DataInputStream(getClientSocket().socket().getInputStream()); byte[] data = new byte[size]; in.readFully(data); @@ -435,8 +431,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } /* Parse and execute a RAW message */ - private boolean execRawMessage(String opcode, Properties props) { - if (rawSession == null) { + protected boolean execRawMessage(String opcode, Properties props) { + if (getRawSession() == null) { _log.error("RAW message received, but no RAW session exists"); return false; } @@ -474,12 +470,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } try { - DataInputStream in = new DataInputStream(getClientSocketInputStream()); + DataInputStream in = new DataInputStream(getClientSocket().socket().getInputStream()); byte[] data = new byte[size]; in.readFully(data); - if (!rawSession.sendBytes(dest, data)) { + if (!getRawSession().sendBytes(dest, data)) { _log.error("RAW SEND failed"); return true; } @@ -567,7 +563,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } try { - if (!streamSession.sendBytes(id, getClientSocketInputStream(), size)) { // data)) { + if (!streamSession.sendBytes(id, getClientSocket().socket().getInputStream(), size)) { // data)) { if (_log.shouldLog(Log.WARN)) _log.warn("STREAM SEND [" + size + "] failed"); boolean rv = writeString("STREAM CLOSED RESULT=CANT_REACH_PEER ID=" + id + " MESSAGE=\"Send of " + size + " bytes failed\"\n"); @@ -691,7 +687,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag // SAMRawReceiver implementation public void receiveRawBytes(byte data[]) throws IOException { - if (rawSession == null) { + if (getRawSession() == null) { _log.error("BUG! Received raw bytes, but session is null!"); throw new NullPointerException("BUG! RAW session is null!"); } @@ -701,17 +697,18 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag String msgText = "RAW RECEIVED SIZE=" + data.length + "\n"; msg.write(msgText.getBytes("ISO-8859-1")); msg.write(data); + msg.flush(); if (_log.shouldLog(Log.DEBUG)) _log.debug("sending to client: " + msgText); - writeBytes(msg.toByteArray()); + writeBytes(ByteBuffer.wrap(msg.toByteArray())); } public void stopRawReceiving() { _log.debug("stopRawReceiving() invoked"); - if (rawSession == null) { + if (getRawSession() == null) { _log.error("BUG! Got raw receiving stop, but session is null!"); throw new NullPointerException("BUG! RAW session is null!"); } @@ -726,7 +723,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag // SAMDatagramReceiver implementation public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException { - if (datagramSession == null) { + if (getDatagramSession() == null) { _log.error("BUG! Received datagram bytes, but session is null!"); throw new NullPointerException("BUG! DATAGRAM session is null!"); } @@ -740,14 +737,14 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag if (_log.shouldLog(Log.DEBUG)) _log.debug("sending to client: " + msgText); msg.write(data); - - writeBytes(msg.toByteArray()); + msg.flush(); + writeBytes(ByteBuffer.wrap(msg.toByteArray())); } public void stopDatagramReceiving() { _log.debug("stopDatagramReceiving() invoked"); - if (datagramSession == null) { + if (getDatagramSession() == null) { _log.error("BUG! Got datagram receiving stop, but session is null!"); throw new NullPointerException("BUG! DATAGRAM session is null!"); } @@ -830,29 +827,23 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag } } - public void receiveStreamBytes(int id, byte data[], int len) throws IOException { + public void receiveStreamBytes(int id, ByteBuffer data) throws IOException { if (streamSession == null) { _log.error("Received stream bytes, but session is null!"); throw new NullPointerException("BUG! STREAM session is null!"); } - String msgText = "STREAM RECEIVED ID=" + id +" SIZE=" + len + "\n"; + String msgText = "STREAM RECEIVED ID=" + id +" SIZE=" + data.remaining() + "\n"; if (_log.shouldLog(Log.DEBUG)) _log.debug("sending to client: " + msgText); - byte prefix[] = msgText.getBytes("ISO-8859-1"); + ByteBuffer prefix = ByteBuffer.wrap(msgText.getBytes("ISO-8859-1")); - // dont waste so much memory - //ByteArrayOutputStream msg = new ByteArrayOutputStream(); - //msg.write(msgText.getBytes("ISO-8859-1")); - //msg.write(data, 0, len); - // writeBytes(msg.toByteArray()); Object writeLock = getWriteLock(); - OutputStream out = getOut(); synchronized (writeLock) { - out.write(prefix); - out.write(data, 0, len); - out.flush(); + while (prefix.hasRemaining()) socket.write(prefix); + while (data.hasRemaining()) socket.write(data); + socket.socket().getOutputStream().flush(); } } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java index 75f1bd4b4..0800e17e9 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java @@ -9,7 +9,7 @@ package net.i2p.sam; */ import java.io.IOException; -import java.net.Socket; +import java.nio.channels.SocketChannel; import java.util.Properties; import net.i2p.data.DataFormatException; @@ -36,7 +36,7 @@ public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDat * @param verMajor SAM major version to manage (should be 2) * @param verMinor SAM minor version to manage */ - public SAMv2Handler ( Socket s, int verMajor, int verMinor ) throws SAMException, IOException + public SAMv2Handler ( SocketChannel s, int verMajor, int verMinor ) throws SAMException, IOException { this ( s, verMajor, verMinor, new Properties() ); } @@ -52,7 +52,7 @@ public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDat * @param i2cpProps properties to configure the I2CP connection (host, port, etc) */ - public SAMv2Handler ( Socket s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException + public SAMv2Handler ( SocketChannel s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException { super ( s, verMajor, verMinor, i2cpProps ); } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java index de5b7851b..4197597eb 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java @@ -12,6 +12,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.ByteBuffer; import java.net.ConnectException; import java.net.NoRouteToHostException; import java.util.ArrayList; @@ -140,9 +142,6 @@ public class SAMv2StreamSession extends SAMStreamSession public class StreamConnector implements Runnable { - private Object runningLock = new Object(); - private boolean stillRunning = true; - private int id; private Destination dest ; private I2PSocketOptions opts ; @@ -245,7 +244,7 @@ public class SAMv2StreamSession extends SAMStreamSession protected class v2StreamSender extends StreamSender { - private List _data; + private List _data; private int _dataSize; private int _id; private ByteCache _cache; @@ -257,7 +256,7 @@ public class SAMv2StreamSession extends SAMStreamSession public v2StreamSender ( I2PSocket s, int id ) throws IOException { super ( s, id ); - _data = new ArrayList ( 1 ); + _data = new ArrayList ( 1 ); _dataSize = 0; _id = id; _cache = ByteCache.getInstance ( 10, 32 * 1024 ); @@ -511,7 +510,7 @@ public class SAMv2StreamSession extends SAMStreamSession _log.debug ( "run() called for socket reader " + id ); int read = -1; - byte[] data = new byte[SOCKET_HANDLER_BUF_SIZE]; + ByteBuffer data = ByteBuffer.allocateDirect(SOCKET_HANDLER_BUF_SIZE); try { @@ -533,7 +532,8 @@ public class SAMv2StreamSession extends SAMStreamSession break ; } - read = in.read ( data ); + data.clear(); + read = Channels.newChannel(in).read ( data ); if ( read == -1 ) { @@ -542,8 +542,8 @@ public class SAMv2StreamSession extends SAMStreamSession } totalReceived += read ; - - recv.receiveStreamBytes ( id, data, read ); + data.flip(); + recv.receiveStreamBytes ( id, data ); } } catch ( IOException e ) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java new file mode 100644 index 000000000..dcca03c14 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java @@ -0,0 +1,90 @@ +/** + * @author MKVore + * + */ + +package net.i2p.sam; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.Properties; + +import net.i2p.client.I2PSessionException; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; +import net.i2p.util.Log; + +import java.net.InetSocketAddress; +import java.net.SocketAddress ; +import java.nio.ByteBuffer; + +public class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Session, SAMDatagramReceiver { + + private final static Log _log = new Log ( SAMv3DatagramSession.class ); + + SAMv3Handler handler = null ; + SAMv3Handler.DatagramServer server = null ; + String nick = null ; + SocketAddress clientAddress = null ; + + public String getNick() { return nick; } + + /** + * @param nick nickname of the session + * @param server DatagramServer used for communication with the client + * @throws IOException + * @throws DataFormatException + * @throws I2PSessionException + */ + public SAMv3DatagramSession(String nick) + throws IOException, DataFormatException, I2PSessionException { + + super(SAMv3Handler.sSessionsHash.get(nick).getDest(), + SAMv3Handler.sSessionsHash.get(nick).getProps(), + null + ); + this.nick = nick ; + this.recv = this ; + this.server = SAMv3Handler.DatagramServer.getInstance() ; + + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); + if ( rec==null ) throw new InterruptedIOException() ; + + this.handler = rec.getHandler(); + + Properties props = rec.getProps(); + String portStr = props.getProperty("PORT") ; + if ( portStr==null ) { + _log.debug("receiver port not specified. Current socket will be used."); + } + else { + int port = Integer.parseInt(portStr); + + String host = props.getProperty("HOST"); + if ( host==null ) { + _log.debug("no host specified. Take from the client socket"); + + host = rec.getHandler().getClientIP(); + } + + + this.clientAddress = new InetSocketAddress(host,port); + } + } + + public void receiveDatagramBytes(Destination sender, byte[] data) throws IOException { + if (this.clientAddress==null) { + this.handler.receiveDatagramBytes(sender, data); + } else { + String msg = sender.toBase64()+"\n"; + ByteBuffer msgBuf = ByteBuffer.allocate(msg.length()+data.length); + msgBuf.put(msg.getBytes("ISO-8859-1")); + msgBuf.put(data); + msgBuf.flip(); + this.server.send(this.clientAddress, msgBuf); + } + } + + public void stopDatagramReceiving() { + } +} diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java new file mode 100644 index 000000000..21ff0df68 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -0,0 +1,748 @@ +package net.i2p.sam; +/* + * free (adj.): unencumbered; not under the control of others + * Written by human in 2004 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + + +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.NoRouteToHostException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SocketChannel; +import java.nio.ByteBuffer; +import java.util.Properties; +import java.util.HashMap; +import java.util.StringTokenizer; + +import net.i2p.I2PException; +import net.i2p.client.I2PSessionException; +import net.i2p.data.Base64; +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Destination; +import net.i2p.util.Log; +import net.i2p.data.VerifiedDestination; +import net.i2p.util.I2PAppThread; + +/** + * Class able to handle a SAM version 3 client connection. + * + * @author mkvore + */ + +public class SAMv3Handler extends SAMv1Handler +{ + private final static Log _log = new Log ( SAMv3Handler.class ); + + protected SAMv3StreamSession streamSession = null ; + protected SAMv3RawSession rawSession = null ; + protected SAMv3DatagramSession datagramSession = null ; + + protected SAMDatagramSession getDatagramSession() { + return datagramSession ; + } + + protected SAMRawSession getRawSession() { + return rawSession ; + } + + protected Session session = null ; + + interface Session { + String getNick(); + void close(); + boolean sendBytes(String dest, byte[] data) throws DataFormatException; + } + + /** + * Create a new SAM version 3 handler. This constructor expects + * that the SAM HELLO message has been still answered (and + * stripped) from the socket input stream. + * + * @param s Socket attached to a SAM client + * @param verMajor SAM major version to manage (should be 3) + * @param verMinor SAM minor version to manage + */ + public SAMv3Handler ( SocketChannel s, int verMajor, int verMinor ) throws SAMException, IOException + { + this ( s, verMajor, verMinor, new Properties() ); + } + + /** + * Create a new SAM version 3 handler. This constructor expects + * that the SAM HELLO message has been still answered (and + * stripped) from the socket input stream. + * + * @param s Socket attached to a SAM client + * @param verMajor SAM major version to manage (should be 3) + * @param verMinor SAM minor version to manage + * @param i2cpProps properties to configure the I2CP connection (host, port, etc) + */ + + public SAMv3Handler ( SocketChannel s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException + { + super ( s, verMajor, verMinor, i2cpProps ); + _log.debug("SAM version 3 handler instantiated"); + } + + public boolean verifVersion() + { + return (verMajor == 3 && verMinor == 0) ; + } + + static public class DatagramServer { + + private static DatagramServer _instance = null ; + private static DatagramChannel server = null ; + + public static DatagramServer getInstance() throws IOException { + return getInstance(new Properties()); + } + + public static DatagramServer getInstance(Properties props) throws IOException { + if (_instance==null) { + _instance = new DatagramServer(props); + } + return _instance ; + } + + public DatagramServer(Properties props) throws IOException { + if (server==null) { + server = DatagramChannel.open(); + } + + String host = props.getProperty(SAMBridge.PROP_DATAGRAM_HOST, SAMBridge.DEFAULT_DATAGRAM_HOST); + String portStr = props.getProperty(SAMBridge.PROP_DATAGRAM_PORT, SAMBridge.DEFAULT_DATAGRAM_PORT); + int port ; + try { + port = Integer.parseInt(portStr); + } catch (NumberFormatException e) { + port = Integer.parseInt(SAMBridge.DEFAULT_DATAGRAM_PORT); + } + + server.socket().bind(new InetSocketAddress(host, port)); + new I2PAppThread(new Listener(server), "DatagramListener").start(); + } + + public void send(SocketAddress addr, ByteBuffer msg) throws IOException { + server.send(msg, addr); + } + + class Listener implements Runnable { + + DatagramChannel server = null; + + public Listener(DatagramChannel server) + { + this.server = server ; + } + public void run() + { + ByteBuffer inBuf = ByteBuffer.allocateDirect(SAMRawSession.RAW_SIZE_MAX+1024); + + while (!Thread.interrupted()) + { + inBuf.clear(); + try { + server.receive(inBuf); + } catch (IOException e) { + break ; + } + inBuf.flip(); + ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]); + outBuf.put(inBuf); + outBuf.flip(); + new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start(); + } + } + } + } + + public static class MessageDispatcher implements Runnable + { + ByteArrayInputStream is = null ; + + public MessageDispatcher(byte[] buf) + { + this.is = new java.io.ByteArrayInputStream(buf) ; + } + + public void run() { + String header = null ; + String nick ; + String dest ; + + try { + header = DataHelper.readLine(is).trim(); + StringTokenizer tok = new StringTokenizer(header, " "); + if (tok.countTokens() != 2) { + // This is not a correct message, for sure + _log.debug("Error in message format"); + return; + } + nick = tok.nextToken(); + dest = tok.nextToken(); + + byte[] data = new byte[is.available()]; + is.read(data); + SessionRecord rec = sSessionsHash.get(nick); + if (rec!=null) { + rec.getHandler().session.sendBytes(dest,data); + } + } catch (Exception e) {} + } + } + + public class SessionRecord + { + protected String m_dest ; + protected Properties m_props ; + protected ThreadGroup m_threadgroup ; + protected SAMv3Handler m_handler ; + + public SessionRecord( String dest, Properties props, SAMv3Handler handler ) + { + m_dest = new String(dest) ; + m_props = new Properties() ; + m_props.putAll(props); + m_threadgroup = null ; + m_handler = handler ; + } + + public SessionRecord( SessionRecord in ) + { + m_dest = in.getDest(); + m_props = in.getProps(); + m_threadgroup = in.getThreadGroup(); + m_handler = in.getHandler(); + } + + synchronized public String getDest() + { + return new String(m_dest) ; + } + synchronized public Properties getProps() + { + Properties p = new Properties(); + p.putAll(m_props); + return m_props; + } + synchronized public SAMv3Handler getHandler() + { + return m_handler ; + } + synchronized public ThreadGroup getThreadGroup() + { + return m_threadgroup ; + } + synchronized public void createThreadGroup(String name) + { + if (m_threadgroup == null) + m_threadgroup = new ThreadGroup(name); + } + } + + public static class SessionsDB + { + static final long serialVersionUID = 0x1 ; + + HashMap map ; + + public SessionsDB() { + map = new HashMap() ; + } + + synchronized public boolean put( String nick, SessionRecord session ) + { + if ( !map.containsKey(nick) ) { + session.createThreadGroup("SAM session "+nick); + map.put(nick, session) ; + return true ; + } + else + return false ; + } + synchronized public boolean del( String nick ) + { + SessionRecord rec = map.get(nick); + + if ( rec!=null ) { + map.remove(nick); + return true ; + } + else + return false ; + } + synchronized public SessionRecord get(String nick) + { + return map.get(nick); + } + synchronized public boolean containsKey( String nick ) + { + return map.containsKey(nick); + } + } + + public static SessionsDB sSessionsHash = new SessionsDB() ; + + public String getClientIP() + { + return this.socket.socket().getInetAddress().getHostAddress(); + } + + boolean stolenSocket = false ; + + public void stealSocket() + { + stolenSocket = true ; + this.stopHandling(); + } + + public void handle() { + String msg = null; + String domain = null; + String opcode = null; + boolean canContinue = false; + StringTokenizer tok; + Properties props; + + this.thread.setName("SAMv3Handler " + _id); + _log.debug("SAM handling started"); + + try { + InputStream in = getClientSocket().socket().getInputStream(); + + while (true) { + if (shouldStop()) { + _log.debug("Stop request found"); + break; + } + + msg = DataHelper.readLine(in).trim(); + if (msg == null) { + _log.debug("Connection closed by client"); + break; + } + + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("New message received: [" + msg + "]"); + } + + if(msg.equals("")) { + _log.debug("Ignoring newline"); + continue; + } + + tok = new StringTokenizer(msg, " "); + if (tok.countTokens() < 2) { + // This is not a correct message, for sure + _log.debug("Error in message format"); + break; + } + domain = tok.nextToken(); + opcode = tok.nextToken(); + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("Parsing (domain: \"" + domain + + "\"; opcode: \"" + opcode + "\")"); + } + props = SAMUtils.parseParams(tok); + + if (domain.equals("STREAM")) { + canContinue = execStreamMessage(opcode, props); + } else if (domain.equals("SESSION")) { + if (i2cpProps != null) + props.putAll(i2cpProps); // make sure we've got the i2cp settings + canContinue = execSessionMessage(opcode, props); + } else if (domain.equals("DEST")) { + canContinue = execDestMessage(opcode, props); + } else if (domain.equals("NAMING")) { + canContinue = execNamingMessage(opcode, props); + } else { + _log.debug("Unrecognized message domain: \"" + + domain + "\""); + break; + } + + if (!canContinue) { + break; + } + } + } catch (IOException e) { + _log.debug("Caught IOException (" + + e.getMessage() + ") for message [" + msg + "]", e); + } catch (Exception e) { + _log.error("Unexpected exception for message [" + msg + "]", e); + } finally { + _log.debug("Stopping handler"); + + if (!this.stolenSocket) + { + try { + closeClientSocket(); + } catch (IOException e) { + _log.error("Error closing socket: " + e.getMessage()); + } + } + + die(); + } + } + + protected void die() { + SessionRecord rec = null ; + + if (session!=null) { + session.close(); + rec = sSessionsHash.get(session.getNick()); + } + if (rec!=null) { + rec.getThreadGroup().interrupt() ; + while (rec.getThreadGroup().activeCount()>0) + try { + Thread.sleep(1000); + } catch ( InterruptedException e) {} + rec.getThreadGroup().destroy(); + sSessionsHash.del(session.getNick()); + } + } + + /* Parse and execute a SESSION message */ + @Override + protected boolean execSessionMessage(String opcode, Properties props) { + + String dest = "BUG!"; + String nick = null ; + boolean ok = false ; + + try{ + if (opcode.equals("CREATE")) { + if ((this.getRawSession()!= null) || (this.getDatagramSession() != null) + || (streamSession != null)) { + _log.debug("Trying to create a session, but one still exists"); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n"); + } + if (props == null) { + _log.debug("No parameters specified in SESSION CREATE message"); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n"); + } + + dest = props.getProperty("DESTINATION"); + if (dest == null) { + _log.debug("SESSION DESTINATION parameter not specified"); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n"); + } + props.remove("DESTINATION"); + + + if (dest.equals("TRANSIENT")) { + _log.debug("TRANSIENT destination requested"); + ByteArrayOutputStream priv = new ByteArrayOutputStream(640); + SAMUtils.genRandomKey(priv, null); + + dest = Base64.encode(priv.toByteArray()); + } else { + _log.debug("Custom destination specified [" + dest + "]"); + } + + boolean good_key = false ; + try { + good_key = (new VerifiedDestination(dest)).verifyCert(true); + } catch (DataFormatException e) { + good_key = false ; + } + if (!good_key) + { + _log.debug("Bad destination key"); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"bad destination key\"\n"); + } + + nick = props.getProperty("ID"); + if (nick == null) { + _log.debug("SESSION ID parameter not specified"); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"ID not specified\"\n"); + } + props.remove("ID"); + + + String style = props.getProperty("STYLE"); + if (style == null) { + _log.debug("SESSION STYLE parameter not specified"); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n"); + } + props.remove("STYLE"); + + + + // Record the session in the database sSessionsHash + Properties allProps = new Properties(); + allProps.putAll(i2cpProps); + allProps.putAll(props); + + if (! sSessionsHash.put( nick, new SessionRecord(dest, allProps, this) ) ) { + _log.debug("SESSION ID parameter already in use"); + String n = nick ; + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"ID "+n+" already in use\"\n"); + } + + // Create the session + + if (style.equals("RAW")) { + DatagramServer.getInstance(i2cpProps); + rawSession = newSAMRawSession(nick); + this.session = rawSession ; + } else if (style.equals("DATAGRAM")) { + DatagramServer.getInstance(i2cpProps); + datagramSession = newSAMDatagramSession(nick); + this.session = datagramSession ; + } else if (style.equals("STREAM")) { + streamSession = newSAMStreamSession(nick); + this.session = streamSession ; + } else { + _log.debug("Unrecognized SESSION STYLE: \"" + style +"\""); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n"); + } + ok = true ; + return writeString("SESSION STATUS RESULT=OK DESTINATION=" + + dest + "\n"); + } else { + _log.debug("Unrecognized SESSION message opcode: \"" + + opcode + "\""); + return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n"); + } + } catch (DataFormatException e) { + _log.debug("Invalid destination specified"); + return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); + } catch (I2PSessionException e) { + _log.debug("I2P error when instantiating session", e); + return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); + } catch (SAMException e) { + _log.error("Unexpected SAM error", e); + return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); + } catch (IOException e) { + _log.error("Unexpected IOException", e); + return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); + } finally { + // unregister the session if it has not been created + if ( !ok && nick!=null ) { + sSessionsHash.del(nick) ; + session = null ; + } + } + } + + SAMv3StreamSession newSAMStreamSession(String login ) + throws IOException, DataFormatException, SAMException + { + return new SAMv3StreamSession( login ) ; + } + + SAMv3RawSession newSAMRawSession(String login ) + throws IOException, DataFormatException, SAMException, I2PSessionException + { + return new SAMv3RawSession( login ) ; + } + + SAMv3DatagramSession newSAMDatagramSession(String login ) + throws IOException, DataFormatException, SAMException, I2PSessionException + { + return new SAMv3DatagramSession( login ) ; + } + + /* Parse and execute a STREAM message */ + protected boolean execStreamMessage ( String opcode, Properties props ) + { + String nick = null ; + SessionRecord rec = null ; + + if ( session != null ) + { + _log.error ( "STREAM message received, but this session is a master session" ); + writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"master session cannot be used for streams"); + return false; + } + + nick = props.getProperty("ID"); + if (nick == null) { + _log.debug("SESSION ID parameter not specified"); + writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"ID not specified\"\n"); + return false ; + } + props.remove("ID"); + + rec = sSessionsHash.get(nick); + + if ( rec==null ) { + _log.debug("STREAM SESSION ID does not exist"); + writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"STREAM SESSION ID does not exist\"\n"); + return false ; + } + + streamSession = rec.getHandler().streamSession ; + + if (streamSession==null) { + _log.debug("specified ID is not a stream session"); + writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"specified ID is not a STREAM session\"\n"); + return false ; + } + + if ( opcode.equals ( "CONNECT" ) ) + { + return execStreamConnect ( props ); + } + else if ( opcode.equals ( "ACCEPT" ) ) + { + return execStreamAccept ( props ); + } + else if ( opcode.equals ( "FORWARD") ) + { + return execStreamForwardIncoming( props ); + } + else + { + _log.debug ( "Unrecognized RAW message opcode: \"" + + opcode + "\"" ); + writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized RAW message opcode: \"" + + opcode + "\"" ); + return false; + } + } + + protected boolean execStreamConnect( Properties props) { + if (props == null) { + _log.debug("No parameters specified in STREAM CONNECT message"); + return false; + } + boolean verbose = props.getProperty("SILENT","false").equals("false"); + + String dest = props.getProperty("DESTINATION"); + if (dest == null) { + _log.debug("Destination not specified in RAW SEND message"); + return false; + } + props.remove("DESTINATION"); + + try { + try { + streamSession.connect( this, dest, props ); + return true ; + } catch (DataFormatException e) { + _log.debug("Invalid destination in STREAM CONNECT message"); + if (verbose) notifyStreamAccept ( "INVALID_KEY" ); + } catch (ConnectException e) { + _log.debug("STREAM CONNECT failed: " + e.getMessage()); + if (verbose) notifyStreamAccept ( "CONNECTION_REFUSED" ); + } catch (NoRouteToHostException e) { + _log.debug("STREAM CONNECT failed: " + e.getMessage()); + if (verbose) notifyStreamAccept ( "CANT_REACH_PEER" ); + } catch (InterruptedIOException e) { + _log.debug("STREAM CONNECT failed: " + e.getMessage()); + if (verbose) notifyStreamAccept ( "TIMEOUT" ); + } catch (I2PException e) { + _log.debug("STREAM CONNECT failed: " + e.getMessage()); + if (verbose) notifyStreamAccept ( "I2P_ERROR" ); + } + } catch (IOException e) { + } + return false ; + } + + protected boolean execStreamForwardIncoming( Properties props ) { + try { + try { + streamSession.startForwardingIncoming(props); + notifyStreamAccept("OK"); + return true ; + } catch (SAMException e) { + _log.debug("Forwarding STREAM connections failed: " + e.getMessage()); + notifyStreamAccept ( "FORWARDER_FAILED" ); + } + } catch (IOException e) { + } + return false ; + } + + protected boolean execStreamAccept( Properties props ) + { + boolean verbose = props.getProperty( "SILENT", "false").equals("false"); + try { + try { + streamSession.accept(this, verbose); + return true ; + } catch (InterruptedIOException e) { + _log.debug("STREAM ACCEPT failed: " + e.getMessage()); + if (verbose) notifyStreamAccept( "TIMEOUT" ); + } catch (I2PException e) { + _log.debug("STREAM ACCEPT failed: " + e.getMessage()); + if (verbose) notifyStreamAccept ( "I2P_ERROR" ); + } catch (SAMException e) { + _log.debug("STREAM ACCEPT failed: " + e.getMessage()); + if (verbose) notifyStreamAccept ( "ALREADY_ACCEPTING" ); + } + } catch (IOException e) { + } + return false ; + } + + + public void notifyStreamAccept(String status) throws IOException + { + if ( streamSession == null ) + { + _log.error ( "BUG! Received stream connection, but session is null!" ); + throw new NullPointerException ( "BUG! STREAM session is null!" ); + } + + if ( !writeString ( "STREAM STATUS RESULT=" + + status + + "\n" ) ) + { + throw new IOException ( "Error notifying connection to SAM client" ); + } + } + + public void notifyStreamOutgoingConnection(String result) throws IOException + { + if ( streamSession == null ) + { + _log.error ( "BUG! Received stream connection, but session is null!" ); + throw new NullPointerException ( "BUG! STREAM session is null!" ); + } + + if ( !writeString ( "STREAM STATUS RESULT=" + + result + + "\n" ) ) + { + throw new IOException ( "Error notifying connection to SAM client" ); + } + } + + public void notifyStreamIncomingConnection(Destination d) throws IOException { + if (streamSession == null) { + _log.error("BUG! Received stream connection, but session is null!"); + throw new NullPointerException("BUG! STREAM session is null!"); + } + + if (!writeString(d.toBase64() + "\n")) { + throw new IOException("Error notifying connection to SAM client"); + } + } + + public static void notifyStreamIncomingConnection(SocketChannel client, Destination d) throws IOException { + if (!writeString(d.toBase64() + "\n", client)) { + throw new IOException("Error notifying connection to SAM client"); + } + } + +} + diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java new file mode 100644 index 000000000..1bf7d18a0 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java @@ -0,0 +1,88 @@ +/** + * + */ +package net.i2p.sam; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.Properties; + +import net.i2p.client.I2PSessionException; +import net.i2p.data.DataFormatException; +import net.i2p.util.Log; + +/** + * @author MKVore + * + */ +public class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SAMRawReceiver { + + String nick = null ; + SAMv3Handler handler = null ; + SAMv3Handler.DatagramServer server ; + private final static Log _log = new Log ( SAMv3DatagramSession.class ); + SocketAddress clientAddress = null ; + + public String getNick() { return nick; } + + /** + * @param nick nickname of the session + * @param server DatagramServer used for communication with the client + * @throws IOException + * @throws DataFormatException + * @throws I2PSessionException + */ + public SAMv3RawSession(String nick) + throws IOException, DataFormatException, I2PSessionException { + + super(SAMv3Handler.sSessionsHash.get(nick).getDest(), + SAMv3Handler.sSessionsHash.get(nick).getProps(), + SAMv3Handler.sSessionsHash.get(nick).getHandler() + ); + this.nick = nick ; + this.recv = this ; + this.server = SAMv3Handler.DatagramServer.getInstance() ; + + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); + if ( rec==null ) throw new InterruptedIOException() ; + + this.handler = rec.getHandler(); + + Properties props = rec.getProps(); + + + String portStr = props.getProperty("PORT") ; + if ( portStr==null ) { + _log.debug("receiver port not specified. Current socket will be used."); + } + else { + int port = Integer.parseInt(portStr); + + String host = props.getProperty("HOST"); + if ( host==null ) { + _log.debug("no host specified. Take from the client socket"); + + host = rec.getHandler().getClientIP(); + } + + + this.clientAddress = new InetSocketAddress(host,port); + } + } + + public void receiveRawBytes(byte[] data) throws IOException { + if (this.clientAddress==null) { + this.handler.receiveRawBytes(data); + } else { + ByteBuffer msgBuf = ByteBuffer.allocate(data.length); + msgBuf.put(data); + msgBuf.flip(); + this.server.send(this.clientAddress, msgBuf); + } + } + + public void stopRawReceiving() {} +} diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java new file mode 100644 index 000000000..b9ea710c6 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -0,0 +1,389 @@ +package net.i2p.sam; +/* + * free (adj.): unencumbered; not under the control of others + * Written by human in 2004 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ConnectException; +import java.net.NoRouteToHostException; +import java.util.Properties; + +import net.i2p.I2PException; +import net.i2p.client.I2PClient; +import net.i2p.client.streaming.I2PServerSocket; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketManagerFactory; +import net.i2p.client.streaming.I2PSocketOptions; +import net.i2p.data.Base64; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; +import net.i2p.util.I2PAppThread; +import net.i2p.util.Log; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.ByteBuffer ; +import java.nio.channels.SocketChannel; + +/** + * SAMv3 STREAM session class. + * + * @author mkvore + */ + +public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Session +{ + + private final static Log _log = new Log ( SAMv3StreamSession.class ); + + protected final int BUFFER_SIZE = 1024 ; + + protected Object socketServerLock = new Object(); + protected I2PServerSocket socketServer = null; + + protected String nick ; + + public String getNick() { + return nick ; + } + + /** + * Create a new SAM STREAM session. + * + * @param dest Base64-encoded destination (private key) + * @param dir Session direction ("RECEIVE", "CREATE" or "BOTH") + * @param props Properties to setup the I2P session + * @param recv Object that will receive incoming data + * @throws IOException + * @throws DataFormatException + * @throws SAMException + */ + public SAMv3StreamSession(String login) + throws IOException, DataFormatException, SAMException + { + initSAMStreamSession(login); + } + + public static SAMv3Handler.SessionsDB getDB() + { + return SAMv3Handler.sSessionsHash ; + } + + private void initSAMStreamSession(String login) + throws IOException, DataFormatException, SAMException{ + + SAMv3Handler.SessionRecord rec = getDB().get(login); + String dest = rec.getDest() ; + ByteArrayInputStream ba_dest = new ByteArrayInputStream(Base64.decode(dest)); + + this.recv = rec.getHandler(); + + _log.debug("SAM STREAM session instantiated"); + + Properties allprops = new Properties(); + allprops.putAll(System.getProperties()); + allprops.putAll(rec.getProps()); + + String i2cpHost = allprops.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); + int i2cpPort ; + String port = allprops.getProperty(I2PClient.PROP_TCP_PORT, "7654"); + try { + i2cpPort = Integer.parseInt(port); + } catch (NumberFormatException nfe) { + throw new SAMException("Invalid I2CP port specified [" + port + "]"); + } + + _log.debug("Creating I2PSocketManager..."); + socketMgr = I2PSocketManagerFactory.createManager(ba_dest, + i2cpHost, + i2cpPort, + allprops); + if (socketMgr == null) { + throw new SAMException("Error creating I2PSocketManager towards "+i2cpHost+":"+i2cpPort); + } + + socketMgr.addDisconnectListener(new DisconnectListener()); + this.nick = login ; + } + + /** + * Connect the SAM STREAM session to the specified Destination + * + * @param id Unique id for the connection + * @param dest Base64-encoded Destination to connect to + * @param props Options to be used for connection + * + * @return true if successful + * @throws DataFormatException if the destination is not valid + * @throws ConnectException if the destination refuses connections + * @throws NoRouteToHostException if the destination can't be reached + * @throws InterruptedIOException if the connection timeouts + * @throws I2PException if there's another I2P-related error + * @throws IOException + */ + public void connect ( SAMv3Handler handler, String dest, Properties props ) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, IOException { + + boolean verbose = (props.getProperty("SILENT", "false").equals("false")); + Destination d = new Destination(); + d = SAMUtils.getDest(dest); + + I2PSocketOptions opts = socketMgr.buildOptions(props); + if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null) + opts.setConnectTimeout(60 * 1000); + + _log.debug("Connecting new I2PSocket..."); + + // blocking connection (SAMv3) + + I2PSocket i2ps = socketMgr.connect(d, opts); + + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); + + if ( rec==null ) throw new InterruptedIOException() ; + + if (verbose) handler.notifyStreamOutgoingConnection("OK") ; + + handler.stealSocket() ; + + ReadableByteChannel fromClient = handler.getClientSocket(); + ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream()); + WritableByteChannel toClient = handler.getClientSocket(); + WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream()); + + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P), "SAMPipeClientToI2P"))).start(); + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient), "SAMPipeClientToI2P"))).start(); + + } + + /** + * Accept an incoming STREAM + * + * @param id Unique id for the connection + * @param dest Base64-encoded Destination to connect to + * @param props Options to be used for connection + * + * @return true if successful + * @throws DataFormatException if the destination is not valid + * @throws ConnectException if the destination refuses connections + * @throws NoRouteToHostException if the destination can't be reached + * @throws InterruptedIOException if the connection timeouts + * @throws I2PException if there's another I2P-related error + * @throws IOException + */ + public void accept(SAMv3Handler handler, boolean verbose) + throws I2PException, InterruptedIOException, IOException, SAMException { + + synchronized( this.socketServerLock ) + { + if (this.socketServer!=null) { + _log.debug("a socket server is already defined for this destination"); + throw new SAMException("a socket server is already defined for this destination"); + } + this.socketServer = this.socketMgr.getServerSocket(); + } + + I2PSocket i2ps; + i2ps = this.socketServer.accept(); + + synchronized( this.socketServerLock ) + { + this.socketServer = null ; + } + + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); + + if ( rec==null ) throw new InterruptedIOException() ; + + if (verbose) + handler.notifyStreamIncomingConnection(i2ps.getPeerDestination()) ; + + handler.stealSocket() ; + ReadableByteChannel fromClient = handler.getClientSocket(); + ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream()); + WritableByteChannel toClient = handler.getClientSocket(); + WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream()); + + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P), "SAMPipeClientToI2P"))).start(); + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient), "SAMPipeClientToI2P"))).start(); + } + + + public void startForwardingIncoming( Properties props ) throws SAMException, InterruptedIOException + { + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); + boolean verbose = props.getProperty("SILENT", "false").equals("false"); + + if ( rec==null ) throw new InterruptedIOException() ; + + String portStr = props.getProperty("PORT") ; + if ( portStr==null ) { + _log.debug("receiver port not specified"); + throw new SAMException("receiver port not specified"); + } + int port = Integer.parseInt(portStr); + + String host = props.getProperty("HOST"); + if ( host==null ) { + _log.debug("no host specified. Take from the client socket"); + + host = rec.getHandler().getClientIP(); + } + + + synchronized( this.socketServerLock ) + { + if (this.socketServer!=null) { + _log.debug("a socket server is already defined for this destination"); + throw new SAMException("a socket server is already defined for this destination"); + } + this.socketServer = this.socketMgr.getServerSocket(); + } + + SocketForwarder forwarder = new SocketForwarder(host, port, this, verbose); + (new Thread(rec.getThreadGroup(), new I2PAppThread(forwarder, "SAMStreamForwarder"))).start(); + + } + + public class SocketForwarder extends Thread + { + String host = null ; + int port = 0 ; + SAMv3StreamSession session; + boolean verbose; + + SocketForwarder(String host, int port, SAMv3StreamSession session, boolean verbose) { + this.host = host ; + this.port = port ; + this.session = session ; + this.verbose = verbose ; + } + + public void run() + { + while (session.socketServer!=null) { + + boolean available = false ; + I2PSocket i2ps = null ; + try { + available = session.socketServer.waitIncoming(-1); + } catch (ConnectException e) { + _log.debug("ConnectException"); + break ; + } catch (I2PException e) { + _log.debug("I2PServerSocket has been closed"); + break ; + } catch (InterruptedException e) { + _log.debug("InterruptedException"); + break ; + } + if ( !available ) continue ; + + java.net.InetSocketAddress addr = new java.net.InetSocketAddress(host,port); + + SocketChannel clientServerSock = null ; + try { + clientServerSock = SocketChannel.open(addr) ; + } + catch ( IOException e ) { + continue ; + } + + try { + i2ps = session.socketServer.accept(false); + } catch (Exception e) {} + + if (i2ps==null) { + try { + clientServerSock.close(); + } catch (IOException ee) {} + continue ; + } + try { + if (this.verbose) + SAMv3Handler.notifyStreamIncomingConnection( + clientServerSock, i2ps.getPeerDestination()); + ReadableByteChannel fromClient = clientServerSock ; + ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream()); + WritableByteChannel toClient = clientServerSock ; + WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream()); + new I2PAppThread(new Pipe(fromClient,toI2P), "SAMPipeClientToI2P").start(); + new I2PAppThread(new Pipe(fromI2P,toClient), "SAMPipeClientToI2P").start(); + + } catch (IOException e) { + try { + clientServerSock.close(); + } catch (IOException ee) {} + try { + i2ps.close(); + } catch (IOException ee) {} + continue ; + } + } + } + } + public class Pipe extends Thread + { + ReadableByteChannel in ; + WritableByteChannel out ; + ByteBuffer buf ; + + public Pipe(ReadableByteChannel in, WritableByteChannel out) + { + this.in = in ; + this.out = out ; + this.buf = ByteBuffer.allocate(BUFFER_SIZE) ; + } + + public void run() + { + try { + while (!Thread.interrupted() && (in.read(buf)>=0 || buf.position() != 0)) { + buf.flip(); + out.write(buf); + buf.compact(); + } + } + catch (IOException e) + { + this.interrupt(); + } + try { + in.close(); + } + catch (IOException e) {} + try { + buf.flip(); + while (buf.hasRemaining()) + out.write(buf); + } + catch (IOException e) {} + try { + out.close(); + } + catch (IOException e) {} + } + } + + + + /** + * Close the stream session + */ + @Override + public void close() { + socketMgr.destroySocketManager(); + } + + public boolean sendBytes(String s, byte[] b) throws DataFormatException + { + throw new DataFormatException(null); + } + +} diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java b/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java index 7df1a2324..9df867aa5 100644 --- a/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java +++ b/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java @@ -12,17 +12,17 @@ import net.i2p.util.Log; * */ public class SAMEventHandler extends SAMClientEventListenerImpl { - private I2PAppContext _context; + //private I2PAppContext _context; private Log _log; private Boolean _helloOk; private Object _helloLock = new Object(); private Boolean _sessionCreateOk; private Object _sessionCreateLock = new Object(); private Object _namingReplyLock = new Object(); - private Map _namingReplies = new HashMap(); + private Map _namingReplies = new HashMap(); public SAMEventHandler(I2PAppContext ctx) { - _context = ctx; + //_context = ctx; _log = ctx.logManager().getLog(getClass()); } diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java index 4e9d1133b..80db744a3 100644 --- a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java +++ b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java @@ -31,10 +31,10 @@ public class SAMStreamSend { private OutputStream _samOut; private InputStream _samIn; private SAMReader _reader; - private boolean _dead; + //private boolean _dead; private SAMEventHandler _eventHandler; /** Connection id (Integer) to peer (Flooder) */ - private Map _remotePeers; + private Map _remotePeers; public static void main(String args[]) { if (args.length < 4) { @@ -42,7 +42,7 @@ public class SAMStreamSend { return; } I2PAppContext ctx = new I2PAppContext(); - String files[] = new String[args.length - 3]; + //String files[] = new String[args.length - 3]; SAMStreamSend sender = new SAMStreamSend(ctx, args[0], args[1], args[2], args[3]); sender.startup(); } @@ -50,14 +50,14 @@ public class SAMStreamSend { public SAMStreamSend(I2PAppContext ctx, String samHost, String samPort, String destFile, String dataFile) { _context = ctx; _log = ctx.logManager().getLog(SAMStreamSend.class); - _dead = false; + //_dead = false; _samHost = samHost; _samPort = samPort; _destFile = destFile; _dataFile = dataFile; _conOptions = ""; _eventHandler = new SendEventHandler(_context); - _remotePeers = new HashMap(); + _remotePeers = new HashMap(); } public void startup() { @@ -207,7 +207,6 @@ public class SAMStreamSend { _started = _context.clock().now(); _context.statManager().addRateData("send." + _connectionId + ".started", 1, 0); byte data[] = new byte[1024]; - long value = 0; long lastSend = _context.clock().now(); while (!_closed) { try { diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java index 8d29e3799..406150b36 100644 --- a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java +++ b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java @@ -31,10 +31,10 @@ public class SAMStreamSink { private OutputStream _samOut; private InputStream _samIn; private SAMReader _reader; - private boolean _dead; + //private boolean _dead; private SAMEventHandler _eventHandler; /** Connection id (Integer) to peer (Flooder) */ - private Map _remotePeers; + private Map _remotePeers; public static void main(String args[]) { if (args.length < 4) { @@ -49,14 +49,14 @@ public class SAMStreamSink { public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) { _context = ctx; _log = ctx.logManager().getLog(SAMStreamSink.class); - _dead = false; + //_dead = false; _samHost = samHost; _samPort = samPort; _destFile = destFile; _sinkDir = sinkDir; _conOptions = ""; _eventHandler = new SinkEventHandler(_context); - _remotePeers = new HashMap(); + _remotePeers = new HashMap(); } public void startup() { @@ -70,7 +70,8 @@ public class SAMStreamSink { String ourDest = handshake(); _log.debug("Handshake complete. we are " + ourDest); if (ourDest != null) { - boolean written = writeDest(ourDest); + //boolean written = + writeDest(ourDest); _log.debug("Dest written"); } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index 7d1d4827f..35eca57e5 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -1,9 +1,11 @@ package net.i2p.client.streaming; +import java.net.ConnectException; import java.util.ArrayList; import java.util.List; import net.i2p.I2PAppContext; +import net.i2p.util.Clock; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; @@ -14,7 +16,7 @@ class ConnectionHandler { private I2PAppContext _context; private Log _log; private ConnectionManager _manager; - private List _synQueue; + private List _synQueue; private boolean _active; private int _acceptTimeout; @@ -61,13 +63,45 @@ class ConnectionHandler { } } + public boolean waitSyn( long ms ) throws InterruptedException { + boolean incoming = false ; + boolean isTimed = (ms>=0); + + Clock clock = I2PAppContext.getGlobalContext().clock(); + long now = clock.now(); + long end = now + ms; + while (!incoming && (!isTimed || now<=end) ) { + synchronized (_synQueue) { + + for (Packet p : _synQueue) + { + if (p.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { + incoming = true ; + break; + } + } + if (!incoming) { + if (!isTimed) { + _synQueue.wait(); + } else { + now = clock.now(); + if (now < end) { + _synQueue.wait(end-now); + } + } + } + } + } + return incoming ; + } + /** * Receive an incoming connection (built from a received SYN) * Non-SYN packets with a zero SendStreamID may also be queued here so * that they don't get thrown away while the SYN packet before it is queued. * - * @param timeoutMs max amount of time to wait for a connection (if less - * than 1ms, wait indefinitely) + * @param timeoutMs max amount of time to wait for a connection (if negative, + * wait indefinitely) * @return connection received, or null if there was a timeout or the * handler was shut down */ @@ -77,8 +111,6 @@ class ConnectionHandler { long expiration = timeoutMs + _context.clock().now(); while (true) { - if ( (timeoutMs > 0) && (expiration < _context.clock().now()) ) - return null; if (!_active) { // fail all the ones we had queued up synchronized (_synQueue) { @@ -97,7 +129,7 @@ class ConnectionHandler { if (_log.shouldLog(Log.DEBUG)) _log.debug("Accept("+ timeoutMs+"): active=" + _active + " queue: " + _synQueue.size()); - if (timeoutMs <= 0) { + if (timeoutMs < 0) { try { _synQueue.wait(); } catch (InterruptedException ie) {} } else { long remaining = expiration - _context.clock().now(); @@ -129,6 +161,8 @@ class ConnectionHandler { } } // keep looping... + if ( (timeoutMs >= 0) && (expiration < _context.clock().now()) ) + return null; } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java index 83f7c8376..ab9cb1c9d 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java @@ -1,7 +1,12 @@ package net.i2p.client.streaming; +import java.net.ConnectException; import java.net.SocketTimeoutException; + +import net.i2p.I2PAppContext; import net.i2p.I2PException; +import net.i2p.util.Clock; +import net.i2p.util.Log; /** * Bridge to allow accepting new connections @@ -45,4 +50,43 @@ public class I2PServerSocketFull implements I2PServerSocket { public I2PSocketManager getManager() { return _socketManager; } + + /** + * accept(true) has the same behaviour as accept(). + * accept(false) does not wait for a socket connecting. If a socket is + * available in the queue, it is accepted. Else, null is returned. + * + * @param true if the call should block until a socket is available + * + * @return a connected I2PSocket, or null + * + * @throws I2PException if there is a problem with reading a new socket + * from the data available (aka the I2PSession closed, etc) + * @throws SocketTimeoutException if the timeout has been reached + */ + + public I2PSocket accept(boolean blocking) throws I2PException, SocketTimeoutException { + long timeout = this.getSoTimeout(); + + try { + if (blocking) + { + this.setSoTimeout(-1); + } else { + this.setSoTimeout(0); + } + try { + return this.accept(); + } catch (SocketTimeoutException e) { + if (blocking) throw e; + else return null ; + } + } finally { + this.setSoTimeout(timeout); + } + } + + public boolean waitIncoming(long timeoutMs) throws InterruptedException { + return this._socketManager.getConnectionManager().getConnectionHandler().waitSyn(timeoutMs); + } } From e0dccb59705afa45cff809c791f5b4a013a109f4 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Thu, 2 Apr 2009 08:54:28 +0000 Subject: [PATCH 02/14] --- .../client/streaming/ConnectionHandler.java | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index 35eca57e5..a189d9a30 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -64,35 +64,7 @@ class ConnectionHandler { } public boolean waitSyn( long ms ) throws InterruptedException { - boolean incoming = false ; - boolean isTimed = (ms>=0); - - Clock clock = I2PAppContext.getGlobalContext().clock(); - long now = clock.now(); - long end = now + ms; - while (!incoming && (!isTimed || now<=end) ) { - synchronized (_synQueue) { - - for (Packet p : _synQueue) - { - if (p.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { - incoming = true ; - break; - } - } - if (!incoming) { - if (!isTimed) { - _synQueue.wait(); - } else { - now = clock.now(); - if (now < end) { - _synQueue.wait(end-now); - } - } - } - } - } - return incoming ; + throw new InterruptedException(); } /** From de6edc6a99d1c2b41f8a0cdb58703e39523eb183 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Sat, 4 Apr 2009 10:28:31 +0000 Subject: [PATCH 03/14] --- .../i2p/client/streaming/I2PServerSocket.java | 4 +- .../client/streaming/I2PServerSocketImpl.java | 37 ++++++++++--------- apps/sam/Demos/datagramTests/samForward.py | 2 +- .../src/net/i2p/sam/SAMv3StreamSession.java | 6 +-- .../client/streaming/ConnectionHandler.java | 27 +++++++++++--- .../client/streaming/I2PServerSocketFull.java | 26 +++++-------- 6 files changed, 55 insertions(+), 47 deletions(-) diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java index e7db251fa..6d6cfdc15 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java @@ -44,7 +44,7 @@ public interface I2PServerSocket { * @throws ConnectException if the I2PServerSocket is closed * @throws SocketTimeoutException */ - public I2PSocket accept(boolean blocking) throws I2PException, ConnectException, SocketTimeoutException; + public I2PSocket accept(long timeout) throws I2PException, ConnectException, SocketTimeoutException, InterruptedException; /** * Waits until there is a socket waiting for acception or the timeout is @@ -58,7 +58,7 @@ public interface I2PServerSocket { * from the data available (aka the I2PSession closed, etc) * @throws ConnectException if the I2PServerSocket is closed */ - public boolean waitIncoming(long timeoutMs) throws I2PException, ConnectException, InterruptedException; + public void waitIncoming(long timeoutMs) throws I2PException, ConnectException, InterruptedException; /** * Set Sock Option accept timeout diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java index a2c075e3e..59459286e 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java @@ -60,28 +60,24 @@ class I2PServerSocketImpl implements I2PServerSocket { * * @param timeoutMs timeout in ms. A negative value waits forever. * - * @return true if a socket is available, false if not - * * @throws I2PException if there is a problem with reading a new socket * from the data available (aka the I2PSession closed, etc) * @throws ConnectException if the I2PServerSocket is closed */ - public boolean waitIncoming(long timeoutMs) throws I2PException, ConnectException { + public void waitIncoming(long timeoutMs) throws I2PException, ConnectException, InterruptedException { if (_log.shouldLog(Log.DEBUG)) _log.debug("waitIncoming() called, pending: " + pendingSockets.size()); - boolean isTimed = (timeoutMs>=0); + boolean isTimed = (timeoutMs>0); if (isTimed) { Clock clock = I2PAppContext.getGlobalContext().clock(); long now = clock.now(); long end = now + timeoutMs; while (pendingSockets.size() <= 0 && now0); } @@ -112,16 +107,20 @@ class I2PServerSocketImpl implements I2PServerSocket { * @throws ConnectException if the I2PServerSocket is closed */ - public I2PSocket accept(boolean blocking) throws I2PException, ConnectException { + public I2PSocket accept(long timeout) throws I2PException, ConnectException, InterruptedException { I2PSocket ret = null; - if (blocking) { + if (timeout<=0) { ret = accept(); } else { + long now = I2PAppContext.getGlobalContext().clock().now(); + long expiration = timeout + now ; synchronized (pendingSockets) { - if (pendingSockets.size() > 0) { - ret = (I2PSocket)pendingSockets.remove(0); + while (pendingSockets.size() == 0 && expiration>now) { + pendingSockets.wait(expiration-now); + now = I2PAppContext.getGlobalContext().clock().now(); } + ret = (I2PSocket)pendingSockets.remove(0); } if (ret != null) { synchronized (socketAcceptedLock) { @@ -151,10 +150,12 @@ class I2PServerSocketImpl implements I2PServerSocket { I2PSocket ret = null; while ( (ret == null) && (!closing) ){ - - this.waitIncoming(-1); - - ret = accept(false); + try { + this.waitIncoming(0); + ret = accept(1); + } catch (InterruptedException e) { + throw new I2PException("Thread interrupted") ; + } } if (_log.shouldLog(Log.DEBUG)) diff --git a/apps/sam/Demos/datagramTests/samForward.py b/apps/sam/Demos/datagramTests/samForward.py index 56590e7ef..1706b3fa5 100755 --- a/apps/sam/Demos/datagramTests/samForward.py +++ b/apps/sam/Demos/datagramTests/samForward.py @@ -16,7 +16,7 @@ else : if len(sys.argv)==3 : name = sys.argv[2] else : - name = "essaiSamForward" + name = "datagramSamForward" sess = socket.socket( socket.AF_INET, socket.SOCK_STREAM) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index b9ea710c6..ca83e8a1f 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -269,10 +269,9 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle { while (session.socketServer!=null) { - boolean available = false ; I2PSocket i2ps = null ; try { - available = session.socketServer.waitIncoming(-1); + session.socketServer.waitIncoming(0); } catch (ConnectException e) { _log.debug("ConnectException"); break ; @@ -283,7 +282,6 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle _log.debug("InterruptedException"); break ; } - if ( !available ) continue ; java.net.InetSocketAddress addr = new java.net.InetSocketAddress(host,port); @@ -296,7 +294,7 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle } try { - i2ps = session.socketServer.accept(false); + i2ps = session.socketServer.accept(1); } catch (Exception e) {} if (i2ps==null) { diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index 37a1f0d7c..9dd6aee41 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -23,6 +23,7 @@ class ConnectionHandler { private Log _log; private ConnectionManager _manager; private LinkedBlockingQueue _synQueue; + private Object _synSignal; private boolean _active; private int _acceptTimeout; @@ -81,7 +82,13 @@ class ConnectionHandler { boolean success = _synQueue.offer(packet); // fail immediately if full if (success) { SimpleScheduler.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout); - } else { + if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) + synchronized (this._synSignal) + { + this._synSignal.notifyAll(); + } + + } else { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping new SYN request, as the queue is full"); if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) @@ -89,8 +96,17 @@ class ConnectionHandler { } } - public boolean waitSyn( long ms ) throws InterruptedException { - throw new InterruptedException(); + /** + * Wait until some SYN packet is available + * @param ms max amount of time to wait for a connection (if negative or null, + * wait indefinitely) + * @throws InterruptedException + */ + public void waitSyn( long ms ) throws InterruptedException { + synchronized (this._synSignal) + { + this._synSignal.wait(ms); + } } /** @@ -120,6 +136,9 @@ class ConnectionHandler { return null; } + if ( (timeoutMs > 0) && (expiration < _context.clock().now()) ) + return null; + Packet syn = null; while ( _active && syn == null) { if (_log.shouldLog(Log.DEBUG)) @@ -162,8 +181,6 @@ class ConnectionHandler { } } // keep looping... - if ( (timeoutMs >= 0) && (expiration < _context.clock().now()) ) - return null; } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java index ab9cb1c9d..8fe7fa9ce 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java @@ -65,28 +65,20 @@ public class I2PServerSocketFull implements I2PServerSocket { * @throws SocketTimeoutException if the timeout has been reached */ - public I2PSocket accept(boolean blocking) throws I2PException, SocketTimeoutException { - long timeout = this.getSoTimeout(); + public I2PSocket accept(long timeout) throws I2PException { + long reset_timeout = this.getSoTimeout(); try { - if (blocking) - { - this.setSoTimeout(-1); - } else { - this.setSoTimeout(0); - } - try { - return this.accept(); - } catch (SocketTimeoutException e) { - if (blocking) throw e; - else return null ; - } - } finally { this.setSoTimeout(timeout); + return this.accept(); + } catch (SocketTimeoutException e) { + return null ; + } finally { + this.setSoTimeout(reset_timeout); } } - public boolean waitIncoming(long timeoutMs) throws InterruptedException { - return this._socketManager.getConnectionManager().getConnectionHandler().waitSyn(timeoutMs); + public void waitIncoming(long timeoutMs) throws InterruptedException { + this._socketManager.getConnectionManager().getConnectionHandler().waitSyn(timeoutMs); } } From 2cf52216208284945fb62e432acf69d01f32598b Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Sat, 4 Apr 2009 12:37:19 +0000 Subject: [PATCH 04/14] minimize differences with mainstream apps/streaming --- .../i2p/client/streaming/I2PServerSocket.java | 17 ++++---- .../client/streaming/I2PServerSocketImpl.java | 17 +++----- .../client/streaming/ConnectionHandler.java | 41 +++++++++++-------- .../client/streaming/I2PServerSocketFull.java | 23 +++++++---- 4 files changed, 54 insertions(+), 44 deletions(-) diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java index 6d6cfdc15..733958736 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java @@ -31,32 +31,33 @@ public interface I2PServerSocket { public I2PSocket accept() throws I2PException, ConnectException, SocketTimeoutException; /** - * accept(true) has the same behaviour as accept(). - * accept(false) does not wait for a socket connecting. If a socket is - * available in the queue, it is accepted. Else, null is returned. + * accept(timeout) waits timeout ms for a socket connecting. If a socket is + * not available during the timeout, return null. accept(0) behaves like accept() * - * @param true if the call should block until a socket is available + * @param timeout in ms * * @return a connected I2PSocket, or null * * @throws I2PException if there is a problem with reading a new socket * from the data available (aka the I2PSession closed, etc) * @throws ConnectException if the I2PServerSocket is closed - * @throws SocketTimeoutException + * @throws InterruptedException if thread is interrupted while waiting */ - public I2PSocket accept(long timeout) throws I2PException, ConnectException, SocketTimeoutException, InterruptedException; + public I2PSocket accept(long timeout) throws I2PException, ConnectException, InterruptedException; /** - * Waits until there is a socket waiting for acception or the timeout is + * Wait until there is a socket waiting for acception or the timeout is * reached. * - * @param timeoutMs timeout in ms. A negative value waits forever. + * @param timeoutMs timeout in ms. If ms is 0, wait forever. * * @return true if a socket is available, false if not * * @throws I2PException if there is a problem with reading a new socket * from the data available (aka the I2PSession closed, etc) * @throws ConnectException if the I2PServerSocket is closed + * @throws InterruptedException if the thread is interrupted before + * completion */ public void waitIncoming(long timeoutMs) throws I2PException, ConnectException, InterruptedException; diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java index 59459286e..2da8320fc 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java @@ -1,6 +1,7 @@ package net.i2p.client.streaming; import java.net.ConnectException; +import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,11 +50,6 @@ class I2PServerSocketImpl implements I2PServerSocket { this.mgr = mgr; } - - - - - /** * Waits until there is a socket waiting for acception or the timeout is * reached. @@ -63,6 +59,7 @@ class I2PServerSocketImpl implements I2PServerSocket { * @throws I2PException if there is a problem with reading a new socket * from the data available (aka the I2PSession closed, etc) * @throws ConnectException if the I2PServerSocket is closed + * @throws InterruptedException if thread is interrupted while waiting */ public void waitIncoming(long timeoutMs) throws I2PException, ConnectException, InterruptedException { if (_log.shouldLog(Log.DEBUG)) @@ -92,21 +89,19 @@ class I2PServerSocketImpl implements I2PServerSocket { } } - /** - * accept(true) has the same behaviour as accept(). - * accept(false) does not wait for a socket connecting. If a socket is - * available in the queue, it is accepted. Else, null is returned. + * accept(timeout) waits timeout ms for a socket connecting. If a socket is + * not available during the timeout, return null. accept(0) behaves like accept() * - * @param true if the call should block until a socket is available + * @param timeout in ms * * @return a connected I2PSocket, or null * * @throws I2PException if there is a problem with reading a new socket * from the data available (aka the I2PSession closed, etc) * @throws ConnectException if the I2PServerSocket is closed + * @throws InterruptedException if thread is interrupted while waiting */ - public I2PSocket accept(long timeout) throws I2PException, ConnectException, InterruptedException { I2PSocket ret = null; diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index 9dd6aee41..e53e701a1 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -2,8 +2,6 @@ package net.i2p.client.streaming; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import java.util.ArrayList; -import java.util.List; import net.i2p.I2PAppContext; import net.i2p.util.Log; @@ -42,7 +40,8 @@ class ConnectionHandler { _context = context; _log = context.logManager().getLog(ConnectionHandler.class); _manager = mgr; - _synQueue = new LinkedBlockingQueue(MAX_QUEUE_SIZE); + _synQueue = new LinkedBlockingQueue(MAX_QUEUE_SIZE); + _synSignal= new Object(); _active = false; _acceptTimeout = DEFAULT_ACCEPT_TIMEOUT; } @@ -83,12 +82,8 @@ class ConnectionHandler { if (success) { SimpleScheduler.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout); if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) - synchronized (this._synSignal) - { - this._synSignal.notifyAll(); - } - - } else { + synchronized (this._synSignal) {this._synSignal.notifyAll();} + } else { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping new SYN request, as the queue is full"); if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) @@ -103,9 +98,24 @@ class ConnectionHandler { * @throws InterruptedException */ public void waitSyn( long ms ) throws InterruptedException { - synchronized (this._synSignal) + synchronized (this._synSignal) { - this._synSignal.wait(ms); + long now = this._context.clock().now() ; + long expiration = now + ms ; + while ( expiration > now || ms<=0 ) { + // check we have not missed a SYN packet before entering + // the lock + for ( Packet p : this._synQueue ) { + if ( p.isFlagSet(Packet.FLAG_SYNCHRONIZE) ) return ; + } + // wait until a SYN is signaled + if ( ms == 0) { + this._synSignal.wait(); + } else { + this._synSignal.wait(expiration-now); + now = this._context.clock().now(); + } + } } } @@ -114,8 +124,8 @@ class ConnectionHandler { * Non-SYN packets with a zero SendStreamID may also be queued here so * that they don't get thrown away while the SYN packet before it is queued. * - * @param timeoutMs max amount of time to wait for a connection (if negative, - * wait indefinitely) + * @param timeoutMs max amount of time to wait for a connection (if less + * than 1ms, wait indefinitely) * @return connection received, or null if there was a timeout or the * handler was shut down */ @@ -125,6 +135,8 @@ class ConnectionHandler { long expiration = timeoutMs + _context.clock().now(); while (true) { + if ( (timeoutMs > 0) && (expiration < _context.clock().now()) ) + return null; if (!_active) { // fail all the ones we had queued up while(true) { @@ -136,9 +148,6 @@ class ConnectionHandler { return null; } - if ( (timeoutMs > 0) && (expiration < _context.clock().now()) ) - return null; - Packet syn = null; while ( _active && syn == null) { if (_log.shouldLog(Log.DEBUG)) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java index 8fe7fa9ce..0a183afef 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PServerSocketFull.java @@ -1,12 +1,9 @@ package net.i2p.client.streaming; -import java.net.ConnectException; import java.net.SocketTimeoutException; import net.i2p.I2PAppContext; import net.i2p.I2PException; -import net.i2p.util.Clock; -import net.i2p.util.Log; /** * Bridge to allow accepting new connections @@ -52,17 +49,15 @@ public class I2PServerSocketFull implements I2PServerSocket { } /** - * accept(true) has the same behaviour as accept(). - * accept(false) does not wait for a socket connecting. If a socket is - * available in the queue, it is accepted. Else, null is returned. + * accept(timeout) waits timeout ms for a socket connecting. If a socket is + * not available during the timeout, return null. accept(0) behaves like accept() * - * @param true if the call should block until a socket is available + * @param timeout in ms * * @return a connected I2PSocket, or null * * @throws I2PException if there is a problem with reading a new socket * from the data available (aka the I2PSession closed, etc) - * @throws SocketTimeoutException if the timeout has been reached */ public I2PSocket accept(long timeout) throws I2PException { @@ -78,7 +73,17 @@ public class I2PServerSocketFull implements I2PServerSocket { } } - public void waitIncoming(long timeoutMs) throws InterruptedException { + /** + * block until a SYN packet is detected or the timeout is reached. If timeout is 0, + * block until a SYN packet is detected. + * + * @param timeoutMs + * @throws InterruptedException + * @throws I2PException + */ + public void waitIncoming(long timeoutMs) throws I2PException, InterruptedException { + if (this._socketManager.getConnectionManager().getSession().isClosed()) + throw new I2PException("Session is closed"); this._socketManager.getConnectionManager().getConnectionHandler().waitSyn(timeoutMs); } } From 6b825fbe2536e12938a41c171de894fcf702989b Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Sun, 5 Apr 2009 21:32:43 +0000 Subject: [PATCH 05/14] SAMv3 : protocol better specified, and small changes in the code reflecting the new protocol --- apps/sam/Demos/datagramTests/samForward.py | 2 +- apps/sam/Demos/datagramTests/samOut.py | 4 +- apps/sam/Demos/rawTests/samOut.py | 4 +- apps/sam/Demos/streamTests/samForward.py | 6 + apps/sam/Demos/streamTests/samIn.py | 6 +- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 62 ++++--- .../src/net/i2p/sam/SAMHandlerFactory.java | 8 +- apps/sam/java/src/net/i2p/sam/SAMUtils.java | 39 ++++- .../java/src/net/i2p/sam/SAMv1Handler.java | 7 +- .../java/src/net/i2p/sam/SAMv3Handler.java | 156 ++++++++++-------- .../src/net/i2p/sam/SAMv3StreamSession.java | 7 +- 11 files changed, 180 insertions(+), 121 deletions(-) diff --git a/apps/sam/Demos/datagramTests/samForward.py b/apps/sam/Demos/datagramTests/samForward.py index 1706b3fa5..a1fd5b3e3 100755 --- a/apps/sam/Demos/datagramTests/samForward.py +++ b/apps/sam/Demos/datagramTests/samForward.py @@ -23,7 +23,7 @@ sess = socket.socket( sess.connect(("127.0.0.1",7656)); sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n") sys.stdout.write(sess.recv(1000)) -sess.send("SESSION CREATE STYLE=DATAGRAM PORT="+str(port)+" ID="+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n") +sess.send("SESSION CREATE STYLE=DATAGRAM PORT="+str(port)+" ID="+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW outbound.nickname="+name+" inbound.nickname="+name+" outbound.length=0\n") sys.stdout.write(sess.recv(10000)) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) diff --git a/apps/sam/Demos/datagramTests/samOut.py b/apps/sam/Demos/datagramTests/samOut.py index bb9be4db2..1a521334f 100755 --- a/apps/sam/Demos/datagramTests/samOut.py +++ b/apps/sam/Demos/datagramTests/samOut.py @@ -26,6 +26,6 @@ port = 7655 host = "localhost" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(("", 0)) -s.sendto(name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port)) -s.sendto(name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port)) +s.sendto("3.0 "+name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port)) +s.sendto("3.0 "+name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port)) diff --git a/apps/sam/Demos/rawTests/samOut.py b/apps/sam/Demos/rawTests/samOut.py index bb9be4db2..1a521334f 100755 --- a/apps/sam/Demos/rawTests/samOut.py +++ b/apps/sam/Demos/rawTests/samOut.py @@ -26,6 +26,6 @@ port = 7655 host = "localhost" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(("", 0)) -s.sendto(name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port)) -s.sendto(name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port)) +s.sendto("3.0 "+name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port)) +s.sendto("3.0 "+name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port)) diff --git a/apps/sam/Demos/streamTests/samForward.py b/apps/sam/Demos/streamTests/samForward.py index c12753405..687b265f6 100755 --- a/apps/sam/Demos/streamTests/samForward.py +++ b/apps/sam/Demos/streamTests/samForward.py @@ -55,4 +55,10 @@ while 1 : chunk = sock.recv(100) sys.stdout.write(chunk) if not chunk : break +print "Forward socket closed" +l=0 +while 1 : + chunk = sess.recv(100) + sys.stdout.write(chunk) + if not chunk : break diff --git a/apps/sam/Demos/streamTests/samIn.py b/apps/sam/Demos/streamTests/samIn.py index 2edfd65c6..ab2e3fa52 100755 --- a/apps/sam/Demos/streamTests/samIn.py +++ b/apps/sam/Demos/streamTests/samIn.py @@ -44,16 +44,18 @@ def accept() : sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n") sys.stdout.write(sock.recv(1000)) sock.send("STREAM ACCEPT ID=" + name + silent+"\n") - print "STREAM ACCEPT ID="+name+silent+"\n" + print "STREAM ACCEPT ID="+name+silent + if (silent==" SILENT=false") : + sys.stdout.write( sock.recv(100) ) return sock def echo( sock, lines ) : l = 0 while lines==-1 or l map ; public SessionsDB() { map = new HashMap() ; } - synchronized public boolean put( String nick, SessionRecord session ) + synchronized public boolean put( String nick, SessionRecord session ) throws ExistingId, ExistingDest { + if ( map.containsKey(nick) ) { + throw new ExistingId(); + } + for ( SessionRecord r : map.values() ) { + if (r.getDest().equals(session.getDest())) { + throw new ExistingDest(); + } + } + if ( !map.containsKey(nick) ) { session.createThreadGroup("SAM session "+nick); map.put(nick, session) ; @@ -455,16 +473,10 @@ public class SAMv3Handler extends SAMv1Handler _log.debug("Custom destination specified [" + dest + "]"); } - boolean good_key = false ; try { - good_key = (new VerifiedDestination(dest)).verifyCert(true); - } catch (DataFormatException e) { - good_key = false ; - } - if (!good_key) - { - _log.debug("Bad destination key"); - return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"bad destination key\"\n"); + SAMUtils.checkPrivateDestination(dest); + } catch ( SAMUtils.InvalidDestination e ) { + return writeString("SESSION STATUS RESULT=INVALID_KEY\n"); } nick = props.getProperty("ID"); @@ -489,12 +501,17 @@ public class SAMv3Handler extends SAMv1Handler allProps.putAll(i2cpProps); allProps.putAll(props); - if (! sSessionsHash.put( nick, new SessionRecord(dest, allProps, this) ) ) { + + try { + sSessionsHash.put( nick, new SessionRecord(dest, allProps, this) ) ; + } catch (SessionsDB.ExistingId e) { _log.debug("SESSION ID parameter already in use"); - String n = nick ; - return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"ID "+n+" already in use\"\n"); + return writeString("SESSION STATUS RESULT=DUPLICATED_ID\n"); + } catch (SessionsDB.ExistingDest e) { + return writeString("SESSION STATUS RESULT=DUPLICATED_DEST\n"); } + // Create the session if (style.equals("RAW")) { @@ -568,14 +585,19 @@ public class SAMv3Handler extends SAMv1Handler if ( session != null ) { _log.error ( "STREAM message received, but this session is a master session" ); - writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"master session cannot be used for streams"); + + try { + notifyStreamResult(true, "I2P_ERROR", "master session cannot be used for streams"); + } catch (IOException e) {} return false; } nick = props.getProperty("ID"); if (nick == null) { _log.debug("SESSION ID parameter not specified"); - writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"ID not specified\"\n"); + try { + notifyStreamResult(true, "I2P_ERROR", "ID not specified"); + } catch (IOException e) {} return false ; } props.remove("ID"); @@ -584,7 +606,9 @@ public class SAMv3Handler extends SAMv1Handler if ( rec==null ) { _log.debug("STREAM SESSION ID does not exist"); - writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"STREAM SESSION ID does not exist\"\n"); + try { + notifyStreamResult(true, "INVALID_ID", "STREAM SESSION ID does not exist"); + } catch (IOException e) {} return false ; } @@ -592,7 +616,9 @@ public class SAMv3Handler extends SAMv1Handler if (streamSession==null) { _log.debug("specified ID is not a stream session"); - writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"specified ID is not a STREAM session\"\n"); + try { + notifyStreamResult(true, "I2P_ERROR", "specified ID is not a STREAM session"); + } catch (IOException e) {} return false ; } @@ -612,45 +638,49 @@ public class SAMv3Handler extends SAMv1Handler { _log.debug ( "Unrecognized RAW message opcode: \"" + opcode + "\"" ); - writeString("STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized RAW message opcode: \"" - + opcode + "\"" ); + try { + notifyStreamResult(true, "I2P_ERROR", "Unrecognized RAW message opcode: "+opcode ); + } catch (IOException e) {} return false; } } + protected boolean execStreamConnect( Properties props) { - if (props == null) { - _log.debug("No parameters specified in STREAM CONNECT message"); - return false; - } - boolean verbose = props.getProperty("SILENT","false").equals("false"); - - String dest = props.getProperty("DESTINATION"); - if (dest == null) { - _log.debug("Destination not specified in RAW SEND message"); - return false; - } - props.remove("DESTINATION"); - try { + if (props == null) { + notifyStreamResult(true,"I2P_ERROR","No parameters specified in STREAM CONNECT message"); + _log.debug("No parameters specified in STREAM CONNECT message"); + return false; + } + boolean verbose = props.getProperty("SILENT","false").equals("false"); + + String dest = props.getProperty("DESTINATION"); + if (dest == null) { + notifyStreamResult(verbose, "I2P_ERROR", "Destination not specified in RAW SEND message"); + _log.debug("Destination not specified in RAW SEND message"); + return false; + } + props.remove("DESTINATION"); + try { streamSession.connect( this, dest, props ); return true ; } catch (DataFormatException e) { _log.debug("Invalid destination in STREAM CONNECT message"); - if (verbose) notifyStreamAccept ( "INVALID_KEY" ); + notifyStreamResult ( verbose, "INVALID_KEY", null ); } catch (ConnectException e) { _log.debug("STREAM CONNECT failed: " + e.getMessage()); - if (verbose) notifyStreamAccept ( "CONNECTION_REFUSED" ); + notifyStreamResult ( verbose, "CONNECTION_REFUSED", null ); } catch (NoRouteToHostException e) { _log.debug("STREAM CONNECT failed: " + e.getMessage()); - if (verbose) notifyStreamAccept ( "CANT_REACH_PEER" ); + notifyStreamResult ( verbose, "CANT_REACH_PEER", null ); } catch (InterruptedIOException e) { _log.debug("STREAM CONNECT failed: " + e.getMessage()); - if (verbose) notifyStreamAccept ( "TIMEOUT" ); + notifyStreamResult ( verbose, "TIMEOUT", null ); } catch (I2PException e) { _log.debug("STREAM CONNECT failed: " + e.getMessage()); - if (verbose) notifyStreamAccept ( "I2P_ERROR" ); + notifyStreamResult ( verbose, "I2P_ERROR", e.getMessage() ); } } catch (IOException e) { } @@ -661,11 +691,11 @@ public class SAMv3Handler extends SAMv1Handler try { try { streamSession.startForwardingIncoming(props); - notifyStreamAccept("OK"); - return true ; + notifyStreamResult( true, "OK", null ); + return false ; } catch (SAMException e) { _log.debug("Forwarding STREAM connections failed: " + e.getMessage()); - notifyStreamAccept ( "FORWARDER_FAILED" ); + notifyStreamResult ( true, "I2P_ERROR", "Forwarding failed : " + e.getMessage() ); } } catch (IOException e) { } @@ -677,17 +707,18 @@ public class SAMv3Handler extends SAMv1Handler boolean verbose = props.getProperty( "SILENT", "false").equals("false"); try { try { + notifyStreamResult(verbose, "OK", null); streamSession.accept(this, verbose); return true ; } catch (InterruptedIOException e) { _log.debug("STREAM ACCEPT failed: " + e.getMessage()); - if (verbose) notifyStreamAccept( "TIMEOUT" ); + notifyStreamResult( verbose, "TIMEOUT", e.getMessage() ); } catch (I2PException e) { _log.debug("STREAM ACCEPT failed: " + e.getMessage()); - if (verbose) notifyStreamAccept ( "I2P_ERROR" ); + notifyStreamResult ( verbose, "I2P_ERROR", e.getMessage() ); } catch (SAMException e) { _log.debug("STREAM ACCEPT failed: " + e.getMessage()); - if (verbose) notifyStreamAccept ( "ALREADY_ACCEPTING" ); + notifyStreamResult ( verbose, "ALREADY_ACCEPTING", null ); } } catch (IOException e) { } @@ -695,33 +726,16 @@ public class SAMv3Handler extends SAMv1Handler } - public void notifyStreamAccept(String status) throws IOException + public void notifyStreamResult(boolean verbose, String result, String message) throws IOException { - if ( streamSession == null ) - { - _log.error ( "BUG! Received stream connection, but session is null!" ); - throw new NullPointerException ( "BUG! STREAM session is null!" ); - } - - if ( !writeString ( "STREAM STATUS RESULT=" - + status - + "\n" ) ) - { - throw new IOException ( "Error notifying connection to SAM client" ); - } - } - - public void notifyStreamOutgoingConnection(String result) throws IOException - { - if ( streamSession == null ) - { - _log.error ( "BUG! Received stream connection, but session is null!" ); - throw new NullPointerException ( "BUG! STREAM session is null!" ); - } - - if ( !writeString ( "STREAM STATUS RESULT=" - + result - + "\n" ) ) + if (!verbose) return ; + + String out = "STREAM STATUS RESULT="+result; + if (message!=null) + out = out + " MESSAGE=\"" + message + "\""; + out = out + '\n'; + + if ( !writeString ( out ) ) { throw new IOException ( "Error notifying connection to SAM client" ); } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index ca83e8a1f..f02591bb6 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -77,7 +77,7 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle } private void initSAMStreamSession(String login) - throws IOException, DataFormatException, SAMException{ + throws IOException, DataFormatException, SAMException { SAMv3Handler.SessionRecord rec = getDB().get(login); String dest = rec.getDest() ; @@ -131,8 +131,7 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle public void connect ( SAMv3Handler handler, String dest, Properties props ) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, IOException { boolean verbose = (props.getProperty("SILENT", "false").equals("false")); - Destination d = new Destination(); - d = SAMUtils.getDest(dest); + Destination d = SAMUtils.getDest(dest); I2PSocketOptions opts = socketMgr.buildOptions(props); if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null) @@ -148,7 +147,7 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle if ( rec==null ) throw new InterruptedIOException() ; - if (verbose) handler.notifyStreamOutgoingConnection("OK") ; + handler.notifyStreamResult(verbose, "OK", null) ; handler.stealSocket() ; From 1bc4cb382ed49a79f506e1fd534177f1108ef4f5 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Sun, 5 Apr 2009 21:35:56 +0000 Subject: [PATCH 06/14] SAMv3 : protocol better specified, and small changes in the code reflecting the new protocol --- apps/sam/doc/protocol-v3.txt | 17 -- apps/sam/doc/sam.3.0-protocol.txt | 477 ++++++++++++++++++++++++++++++ 2 files changed, 477 insertions(+), 17 deletions(-) delete mode 100644 apps/sam/doc/protocol-v3.txt create mode 100644 apps/sam/doc/sam.3.0-protocol.txt diff --git a/apps/sam/doc/protocol-v3.txt b/apps/sam/doc/protocol-v3.txt deleted file mode 100644 index 9aacfa124..000000000 --- a/apps/sam/doc/protocol-v3.txt +++ /dev/null @@ -1,17 +0,0 @@ -telnet localhost 7656 -HELLO VERSION MIN=3.0 MAX=3.0 -SESSION CREATE STYLE=STREAM ID=essaiSamIn DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p - -telnet localhost 7656 -HELLO VERSION MIN=3.0 MAX=3.0 -STREAM ACCEPT ID=essaiSamIn - - -telnet localhost 7656 -HELLO VERSION MIN=3.0 MAX=3.0 -SESSION CREATE STYLE=STREAM ID=essaiSamOut DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW - -telnet localhost 7656 -HELLO VERSION MIN=3.0 MAX=3.0 -STREAM CONNECT ID=essaiSamOut DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA - diff --git a/apps/sam/doc/sam.3.0-protocol.txt b/apps/sam/doc/sam.3.0-protocol.txt new file mode 100644 index 000000000..c544068db --- /dev/null +++ b/apps/sam/doc/sam.3.0-protocol.txt @@ -0,0 +1,477 @@ +---------------------------------------------------------------------- +Simple Anonymous Messaging (SAM version 3.0) +---------------------------------------------------------------------- +Client application talks to SAM bridge, which deals with +all of the I2P functionality (using the ministreaming +lib for virtual streams, or I2CP directly for async messages). + +All client<-->SAM bridge communication is unencrypted and +unauthenticated. Access to the SAM +bridge should be protected through firewalls or other means +(perhaps the bridge may have ACLs on what IPs it accepts +connections from). + +All of these SAM messages are sent on a single line in plain ASCII, +terminated by the newline character (\n). The formatting shown +below is merely for readability, and while the first two words in +each message must stay in their specific order, the ordering of +the key=value pairs can change (e.g. "ONE TWO A=B C=D" or +"ONE TWO C=D A=B" are both perfectly valid constructions). In +addition, the protocol is case-sensitive. +In the following, message examples are preceded by "-> " for +messages sent by the client to the SAM bridge, and by "<- " for +messages sent by the SAM bridge to the client. + +I2P communications can take three distinct forms: +* Virtual streams +* Repliable datagrams (messages with a FROM field) +* Anonymous datagrams (raw anonymous messages) + +I2P communications are supported by I2P sessions, and each I2P +session is bound to an address (called destination). An I2P session +is associated with one of the three types above, and cannot carry +communications of another type. + + +---------------------------------------------------------------------- +SAM connection handshake +---------------------------------------------------------------------- +No SAM communication can occur until after the client and bridge have +agreed on a protocol version, which is done by the client sending +a HELLO and the bridge sending a HELLO REPLY: + +-> HELLO VERSION MIN=$min MAX=$max + +and + +<- HELLO REPLY RESULT=OK VERSION=3.0 + +*** In order to force protocol version 3.0, the values of $min and $max +*** must be "3.0". + +If the SAM bridge cannot find a suitable version, it replies with : + +<- HELLO REPLY RESULT=NOVERSION + +If some error occurred, such as a bad request format, it replies with : + +<- HELLO REPLY RESULT=I2P_ERROR MESSAGE={$message} + + +---------------------------------------------------------------------- +SAM sessions +---------------------------------------------------------------------- +A SAM session is created by a client opening a socket to the SAM +bridge, operating a handshake, and sending a SESSION CREATE message, +and the session terminates when the socket is disconnected. + +Each registered I2P Destination is uniquely associated with a session ID +(or nickname). + +Each session is uniquely associated with : + * the socket from which the client creates the session + * its ID (or nickname) + +The session creation message can only use one of these forms (messages +received through other forms are answered with an error message) : + +-> SESSION CREATE + STYLE={STREAM,DATAGRAM,RAW} + ID={$nickname} + DESTINATION={$private_destination_key,TRANSIENT} + [option=value]* + +DESTINATION specifies what destination should be used for +sending and receiving messages/streams. It has to be a suitable +private base64 destination key. If the destination is +specified as TRANSIENT, the SAM bridge creates a new destination. + +{$nickname} is the choice of the client. No whitespace is allowed. + +Additional options given are passed to the I2P session +configuration if not interpreted by the SAM bridge (e.g. +outbound.length=0). These options are documented below. + +The SAM bridge itself should already be configured with what router +it should communicate over I2P through (though if need be there may +be a way to provide an override, e.g. i2cp.tcp.host=localhost and +i2cp.tcp.port=7654). + +After receiving the session create message, the SAM bridge will reply +with a session status message, as follows: + +If the creation was successful : +<- SESSION STATUS RESULT=OK DESTINATION={$private_destination_key} + +If the nickname is already associated with a session : +<- SESSION STATUS RESULT=DUPLICATED_ID + +If the destination is already in use : +<- SESSION STATUS RESULT=DUPLICATED_DEST + +If the destination is not a valid private destination key : +<- SESSION STATUS RESULT=INVALID_KEY + +If some other error has occurred : +<- SESSION STATUS RESULT=I2P_ERROR MESSAGE={$message} + +If it's not OK, the MESSAGE should contain human-readable information +as to why the session could not be created. + + +SAM sessions live and die with the socket they are associated with. +When the socket is closed, the session dies, and all communications +using the session die at the same time. And the other way round, when +the session dies for any reason, the SAM bridge closes the socket. + + +---------------------------------------------------------------------- +SAM virtual streams +---------------------------------------------------------------------- +Virtual streams are guaranteed to be sent reliably and in order, with +failure and success notification as soon as it is available. + +Streams are bidirectional communication sockets between two I2P +destinations, but their opening has to be requested by one of them. +Hereafter, CONNECT commands are used by the SAM client for such a +request. FORWARD / ACCEPT commands are used by the SAM client when +it wants to listen to requests coming from other I2P destinations. + + +----------------------------- +SAM virtual streams : CONNECT +----------------------------- +A client asks for a connection by : + * opening a new socket with the SAM bridge + * passing the same HELLO handshake as above + * sending the connection command : + +-> STREAM CONNECT + ID={$nickname} + DESTINATION=$peer_public_base64_key + [SILENCE={true,false}] + +This establishes a new virtual connection from the local session +whose ID is {$nickname} to the specified peer. + +If SILENCE=true is passed, the SAM bridge won't issue any other message +on the socket : if the connection fails, the socket will be closed. +If the connection succeeds, all remaining data passing through the +current socket is forwarded from and to the connected I2P destination +peer. + +If SILENCE=false, which is the default value, the SAM bridge sends a +last message to its client before forwarding or shutting down the +socket : + +<- STREAM STATUS + RESULT=$result + [MESSAGE=...] + +The RESULT value may be one of: + + OK + CANT_REACH_PEER + I2P_ERROR + INVALID_KEY + INVALID_ID + TIMEOUT + +If the RESULT is OK, all remaining data passing through the +current socket is forwarded from and to the connected I2P destination +peer. If the connection was not possible (timeout, etc), +RESULT will contain the appropriate error value (accompanied by an +optional human-readable MESSAGE), and the SAM bridge closes the +socket. + +---------------------------- +SAM virtual streams : ACCEPT +---------------------------- + +A client waits for an incoming connection request by : + * opening a new socket with the SAM bridge + * passing the same HELLO handshake as above + * sending the accept command : + +-> STREAM ACCEPT + ID={$nickname} + [SILENCE={true,false}] + +This makes the session ${nickname} listen for one incoming +connection request from the I2P network. + +The SAM bridge answers with : + +<- STREAM STATUS + RESULT=$result + [MESSAGE=...] + +The RESULT value may be one of: + + OK + I2P_ERROR + INVALID_ID + +If the result is not OK, the socket is closed immediately by the SAM +bridge. If the result is OK, the SAM bridge starts waiting for an +incoming connection request from another I2P peer. When a request +arrives, the SAM bridge accepts it and : + + * If SILENCE=true was passed, the SAM bridge won't issue any other message +on the client socket : all remaining data passing through the +current socket is forwarded from and to the connected I2P destination +peer. + * If SILENCE=false was passed, which is the default value, the SAM bridge +sends the client a ASCII line containing the base64 public destination key +of the requesting peer. After this '\n' terminated line, all remaining data +passing through the current socket is forwarded from and to the connected +I2P destination peer, until one of the peer closes the socket. + +----------------------------- +SAM virtual streams : FORWARD +----------------------------- + +A client waits for an incoming connection request by : + * opening a new socket with the SAM bridge + * passing the same HELLO handshake as above + * sending the forward command : + +-> STREAM FORWARD + ID={$nickname} + PORT={$port} + [HOST={$host}] + [SILENCE={true,false}] + +This makes the session ${nickname} listen forever for incoming +connection requests from the I2P network. + +The SAM bridge answers with : + +<- STREAM STATUS + RESULT=$result + [MESSAGE=...] + +The RESULT value may be one of: + + OK + I2P_ERROR + INVALID_ID + +The socket is closed immediately after the message by the SAM +bridge. If the result is OK, the SAM bridge starts waiting for +incoming connection requests from other I2P peers. + + * {$host} is the hostname or IP address of the socket server to which +SAM will forward connection requests. If not given, SAM takes the IP +of the socket that issued the forward command. + + * {$port} is the port number of the socket server to which SAM will +forward connection requests. Is is mandatory. + +When a connexion request arrives from I2P, the SAM bridge requests a +socket connexion from {$host}:{$port}. If it is accepted after no more +than 3 seconds, SAM will accept the connexion from I2P, and then : + + * If SILENCE=true was passed, all data passing through the obtained +current socket is forwarded from and to the connected I2P destination +peer. + * If SILENCE=false was passed, which is the default value, the SAM bridge +sends on the obtained socket an ASCII line containing the base64 public +destination key of the requesting peer. After this '\n' terminated line, +all remaining data passing through the socket is forwarded from and to +the connected I2P destination peer, until one of the sides closes the +socket. + + + + +---------------------------------------------------------------------- +SAM repliable datagrams : sending a datagram +---------------------------------------------------------------------- +While I2P doesn't inherently contain a FROM address, for ease of use +an additional layer is provided as repliable datagrams - unordered +and unreliable messages of up to 31KB in size that include a FROM +address (leaving up to 1KB for header material). This FROM address +is authenticated internally by SAM (making use of the destination's +signing key to verify the source) and includes replay prevention. + +After establishing a SAM session with STYLE=DATAGRAM, the client can +send datagrams through SAM's UDP port (7655). + +The first line of a datagram sent through this port has to be in the +following format : + +3.0 {$nickname} {$base64_public_destination_key} + + * 3.0 is the version of SAM + * {$nickname} is the id of the DGRAM session that will be used + * {$base64_public_destination_key} is the destination of the + datagram + * this line is '\n' terminated. + +The first line will be discarded by SAM before sending the remaining +of the message to the specified destination. + +---------------------------------------------------------------------- +SAM repliable datagrams : receiving a datagram +---------------------------------------------------------------------- +Received datagrams are written by SAM on the socket from which the +datagram session was opened, unless specified otherwise by the CREATE +command. + +When a datagram arrives, the bridge delivers it to the client via the +message : + +<- DATAGRAM RECEIVED + DESTINATION=$base64key + SIZE=$numBytes\n[$numBytes of data] + +The SAM bridge never exposes to the client the authentication headers +or other fields, merely the data that the sender provided. This +continues until the session is closed (by the client dropping the +connection). + +---------------------------------------------------------------------- +SAM repliable datagrams : forwarding datagrams +---------------------------------------------------------------------- +When creating a datagram session, the client can ask SAM to forward +incoming messages to a specified ip:port. It does so by issuing the +CREATE command with PORT and HOST options : + +-> SESSION CREATE + STYLE=DATAGRAM + ID={$nickname} + DESTINATION={$private_destination_key,TRANSIENT} + PORT={$port} + [HOST={$host}] + [option=value]* + + * {$host} is the hostname or IP address of the datagram server to + which SAM will forward datagrams. If not given, SAM takes the + IP of the socket that issued the forward command. + + * {$port} is the port number of the datagram server to which SAM + will forward datagrams. + +When a datagram arrives, the bridge sends to the specified host:port +a message containing the following data : + +${sender_base64_destination_key}\n{$datagram_payload} + + +---------------------------------------------------------------------- +SAM anonymous datagrams +---------------------------------------------------------------------- +Squeezing the most out of I2P's bandwidth, SAM allows clients to send +and receive anonymous datagrams, leaving authentication and reply +information up to the client themselves. These datagrams are +unreliable and unordered, and may be up to 32KB in size. + +After establishing a SAM session with STYLE=RAW, the client can +send anonymous datagrams throug the SAM bridge exactly the same way +he sends non anonymous datagrams. + +Both ways of receiving datagrams are also available for anonymous +datagrams. + +When anonymous datagrams are to be written to the socket that created +the session,the bridge delivers it to the client via: + +<- RAW RECEIVED + SIZE=$numBytes\n[$numBytes of data] + +When anonymous datagrams are to be forwarded to some host:port, +the bridge sends to the specified host:port a message containing +the following data : + +{$datagram_payload} + + +---------------------------------------------------------------------- +SAM utility functionality +---------------------------------------------------------------------- +The following message can be used by the client to query the SAM +bridge for name resolution: + + NAMING LOOKUP + NAME=$name + +which is answered by + + NAMING REPLY + RESULT=$result + NAME=$name + [VALUE=$base64key] + [MESSAGE=$message] + + +The RESULT value may be one of: + + OK + INVALID_KEY + KEY_NOT_FOUND + +If NAME=ME, then the reply will contain the base64key used by the +current session (useful if you're using a TRANSIENT one). If $result +is not OK, MESSAGE may convey a descriptive message, such as "bad +format", etc. + +Public and private base64 keys can be generated using the following +message: + + DEST GENERATE + +which is answered by + + DEST REPLY + PUB=$pubkey + PRIV=$privkey + +---------------------------------------------------------------------- +RESULT values +---------------------------------------------------------------------- +These are the values that can be carried by the RESULT field, with +their meaning: + + OK Operation completed succesfully + CANT_REACH_PEER The peer exists, but cannot be reached + DUPLICATED_DEST The specified Destination is already in use + I2P_ERROR A generic I2P error (e.g. I2CP disconnection, etc.) + INVALID_KEY The specified key is not valid (bad format, etc.) + KEY_NOT_FOUND The naming system can't resolve the given name + PEER_NOT_FOUND The peer cannot be found on the network + TIMEOUT Timeout while waiting for an event (e.g. peer answer) + +---------------------------------------------------------------------- +Tunnel Pool Options +---------------------------------------------------------------------- + +These options can be passed in as name=value pairs at the end of a +SAM SESSION CREATE line. + + inbound.nickname - Name shows up in I2P router console. + inbound.quantity - Number of tunnels, default 2. + inbound.backupQuantity - Number of backup tunnels, default 0. + inbound.rebuildPeriod - Obsolete - ignored - the router controls rebuilding + inbound.duration - Tunnels last X ms, default 10*60*1000. + (change not recommended, will break anonymmity + if it works at all) + inbound.length - Depth of tunnels, default 2. + inbound.lengthVariance - If negative, randomly skews from + (length - variance) to + (length + variance). If positive, from + length to (length + var), inclusive. + Default -1. + inbound.allowZeroHop - Zero hop allowed? Default "true". + outbound.* - Same properties as inbound. + i2p.streaming.connectDelay - If 0, connect ASAP. If positive, wait + until X ms have passed or output stream + is flushed or buffer fills. Default 0. + i2p.streaming.maxWindowSize - Max window size, default 64. + +---------------------------------------------------------------------- +Client library implementations: +---------------------------------------------------------------------- + C/C++: libSAM: http://www.innographx.com/mpc/libsam/ or i2p/sam/c/ + Python: Python/I2P: http://dev.i2p.net/contrib/apps/sam/python/index.html + Others: See apps/sam/ in I2P CVS. From 4d27f18710ef9fd48d5a3b35716abf225830388b Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Mon, 6 Apr 2009 05:29:03 +0000 Subject: [PATCH 07/14] SAMv3: rawTests Demos scripts updated --- apps/sam/Demos/rawTests/samForward.py | 2 +- apps/sam/Demos/rawTests/samIn.py | 2 +- apps/sam/Demos/rawTests/samOut.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/sam/Demos/rawTests/samForward.py b/apps/sam/Demos/rawTests/samForward.py index 6d55da5f7..5a65b6614 100755 --- a/apps/sam/Demos/rawTests/samForward.py +++ b/apps/sam/Demos/rawTests/samForward.py @@ -16,7 +16,7 @@ else : if len(sys.argv)==3 : name = sys.argv[2] else : - name = "essaiSamForward" + name = "rawSamForward" sess = socket.socket( socket.AF_INET, socket.SOCK_STREAM) diff --git a/apps/sam/Demos/rawTests/samIn.py b/apps/sam/Demos/rawTests/samIn.py index 0f89deb17..ee2c3cbc8 100755 --- a/apps/sam/Demos/rawTests/samIn.py +++ b/apps/sam/Demos/rawTests/samIn.py @@ -11,7 +11,7 @@ import sys if len(sys.argv)==2 : name = sys.argv[1] else : - name = "datagramSamIn" + name = "rawSamIn" sess = socket.socket( diff --git a/apps/sam/Demos/rawTests/samOut.py b/apps/sam/Demos/rawTests/samOut.py index 1a521334f..b035d8997 100755 --- a/apps/sam/Demos/rawTests/samOut.py +++ b/apps/sam/Demos/rawTests/samOut.py @@ -13,7 +13,7 @@ import time if len(sys.argv)>=2 : name = sys.argv[1] else : - name = "datagramSamForward" + name = "rawSamForward" if len(sys.argv)>2 : message = ''.join([s+' ' for s in sys.argv[2:]]).strip() From fcbfd7554fd7be592b5c3448b7b7f47042021f5c Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Mon, 6 Apr 2009 06:39:30 +0000 Subject: [PATCH 08/14] SAMv3: alternate form of calling SAMBridge.main. Usage message updated. --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 80 ++++++++++++++------ 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 34dc4c59b..54a721ce1 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -51,11 +51,17 @@ public class SAMBridge implements Runnable { private boolean acceptConnections = true; private static final int SAM_LISTENPORT = 7656; + public static final String DEFAULT_SAM_KEYFILE = "sam.keys"; - public static final String PROP_DATAGRAM_HOST = "sam.datagram.host"; - public static final String PROP_DATAGRAM_PORT = "sam.datagram.port"; - public static final String DEFAULT_DATAGRAM_HOST = "0.0.0.0"; - public static final String DEFAULT_DATAGRAM_PORT = "7655"; + public static final String PROP_TCP_HOST = "sam.tcp.host"; + public static final String PROP_TCP_PORT = "sam.tcp.port"; + protected static final String DEFAULT_TCP_HOST = "0.0.0.0"; + protected static final String DEFAULT_TCP_PORT = "7656"; + + public static final String PROP_DATAGRAM_HOST = "sam.udp.host"; + public static final String PROP_DATAGRAM_PORT = "sam.udp.port"; + protected static final String DEFAULT_DATAGRAM_HOST = "0.0.0.0"; + protected static final String DEFAULT_DATAGRAM_PORT = "7655"; private SAMBridge() {} @@ -199,10 +205,14 @@ public class SAMBridge implements Runnable { } } + static class HelpRequested extends Exception {static final long serialVersionUID=0x1;} + /** * Usage: *
SAMBridge [ keyfile [listenHost ] listenPort [ name=val ]* ]
- * + * or: + *
SAMBridge [ name=val ]* 
+ * * name=val options are passed to the I2CP code to build a session, * allowing the bridge to specify an alternate I2CP host and port, tunnel * depth, etc. @@ -211,24 +221,34 @@ public class SAMBridge implements Runnable { public static void main(String args[]) { String keyfile = DEFAULT_SAM_KEYFILE; int port = SAM_LISTENPORT; - String host = "0.0.0.0"; + String host = DEFAULT_TCP_HOST; Properties opts = null; if (args.length > 0) { - keyfile = args[0]; - int portIndex = 1; - try { - port = Integer.parseInt(args[portIndex]); - } catch (NumberFormatException nfe) { - host = args[1]; - portIndex++; - try { - port = Integer.parseInt(args[portIndex]); - } catch (NumberFormatException nfe1) { - usage(); - return; - } - } - opts = parseOptions(args, portIndex+1); + try { + opts = parseOptions(args, 0); + keyfile = args[0]; + int portIndex = 1; + try { + if (args.length>portIndex) port = Integer.parseInt(args[portIndex]); + } catch (NumberFormatException nfe) { + host = args[portIndex]; + portIndex++; + try { + if (args.length>portIndex) port = Integer.parseInt(args[portIndex]); + } catch (NumberFormatException nfe1) { + try { + port = Integer.parseInt(opts.getProperty(SAMBridge.PROP_TCP_PORT, SAMBridge.DEFAULT_TCP_PORT)); + host = opts.getProperty(SAMBridge.PROP_TCP_HOST, SAMBridge.DEFAULT_TCP_HOST); + } catch (NumberFormatException e) { + usage(); + return; + } + } + } + } catch (HelpRequested e) { + usage(); + return; + } } SAMBridge bridge = new SAMBridge(host, port, opts, keyfile); I2PAppThread t = new I2PAppThread(bridge, "SAMListener"); @@ -244,10 +264,11 @@ public class SAMBridge implements Runnable { t.start(); } - private static Properties parseOptions(String args[], int startArgs) { + private static Properties parseOptions(String args[], int startArgs) throws HelpRequested { Properties props = new Properties(); // skip over first few options for (int i = startArgs; i < args.length; i++) { + if (args[i].equals("-h")) throw new HelpRequested(); int eq = args[i].indexOf('='); if (eq <= 0) continue; if (eq >= args[i].length()-1) continue; @@ -263,11 +284,26 @@ public class SAMBridge implements Runnable { private static void usage() { System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]"); + System.err.println("or:"); + System.err.println(" SAMBridge [ name=val ]*"); System.err.println(" keyfile: location to persist private keys (default sam.keys)"); System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)"); System.err.println(" listenPort: port to listen for SAM connections on (default 7656)"); System.err.println(" name=val: options to pass when connecting via I2CP, such as "); System.err.println(" i2cp.host=localhost and i2cp.port=7654"); + System.err.println(""); + System.err.println("Host and ports of the SAM bridge can be specified with the alternate"); + System.err.println("form by specifying options "+SAMBridge.PROP_TCP_HOST+" and/or "+ + SAMBridge.PROP_TCP_PORT); + System.err.println(""); + System.err.println("Options "+SAMBridge.PROP_DATAGRAM_HOST+" and "+SAMBridge.PROP_DATAGRAM_PORT+ + " specify the listening ip"); + System.err.println("range and the port of SAM datagram server. This server is"); + System.err.println("only launched after a client creates the first SAM datagram"); + System.err.println("or raw session, after a handshake with SAM version >= 3.0."); + System.err.println(""); + System.err.println("The option loglevel=[DEBUG|WARN|ERROR|CRIT] can be used"); + System.err.println("for tuning the log verbosity.\n"); } public void run() { From 12625a46c2b4d25f307c8c4d4a1ec53cc9014f69 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Mon, 6 Apr 2009 07:54:19 +0000 Subject: [PATCH 09/14] comment added in net.i2p.client.streaming.ConnectionHanler --- .../java/src/net/i2p/client/streaming/ConnectionHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index e53e701a1..c42491379 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -81,6 +81,8 @@ class ConnectionHandler { boolean success = _synQueue.offer(packet); // fail immediately if full if (success) { SimpleScheduler.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout); + // advertise the new syn packet to threads that could be waiting + // (by calling waitSyn(long) if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) synchronized (this._synSignal) {this._synSignal.notifyAll();} } else { @@ -103,8 +105,7 @@ class ConnectionHandler { long now = this._context.clock().now() ; long expiration = now + ms ; while ( expiration > now || ms<=0 ) { - // check we have not missed a SYN packet before entering - // the lock + // check if there is a SYN packet in the queue for ( Packet p : this._synQueue ) { if ( p.isFlagSet(Packet.FLAG_SYNCHRONIZE) ) return ; } From d54695e5420a0e2ab73a11a1c875c5081f599774 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Mon, 6 Apr 2009 08:16:19 +0000 Subject: [PATCH 10/14] add dependency to streaming lib to apps/sam/java/build.xml --- apps/sam/java/build.xml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/sam/java/build.xml b/apps/sam/java/build.xml index bb692f2db..73ddab1c0 100644 --- a/apps/sam/java/build.xml +++ b/apps/sam/java/build.xml @@ -4,6 +4,7 @@ + @@ -18,6 +19,7 @@ + @@ -28,20 +30,20 @@ srcdir="./src" debug="true" deprecation="on" source="1.5" target="1.5" destdir="./build/obj" - classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" /> + classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../streaming/java/build/streaming.jar" /> + classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../streaming/java/build/streaming.jar" /> - + @@ -52,7 +54,7 @@ + + From bc831d3c3517b453b6b5485c398b7637bb7e0433 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Mon, 6 Apr 2009 09:53:01 +0000 Subject: [PATCH 11/14] Changelog: SAM : big bug in SAMv1Handler corrected --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 54a721ce1..d6ed45ffe 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -320,7 +320,8 @@ public class SAMBridge implements Runnable { SocketChannel s ; SAMBridge parent ; HelloHandler(SocketChannel s, SAMBridge parent) { - this.s = s ; + this.s = s ; + this.parent = parent ; } public void run() { try { From eae4362181b8b182f4e19fdf34e3085456c348bd Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Tue, 21 Apr 2009 04:14:53 +0000 Subject: [PATCH 12/14] SAMv3: NAMING ME request now work on stream sessions --- apps/sam/java/src/net/i2p/sam/SAMv1Handler.java | 7 ++++--- apps/sam/java/src/net/i2p/sam/SAMv3Handler.java | 12 +++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java index e7d192aad..7734ea574 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java @@ -42,8 +42,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag protected SAMRawSession rawSession = null; protected SAMDatagramSession datagramSession = null; protected SAMStreamSession streamSession = null; - protected SAMDatagramSession getDatagramSession() {return datagramSession ;} protected SAMRawSession getRawSession() {return rawSession ;} + protected SAMDatagramSession getDatagramSession() {return datagramSession ;} + protected SAMStreamSession getStreamSession() {return streamSession ;} protected long _id; protected static volatile long __id = 0; @@ -331,8 +332,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag if (name.equals("ME")) { if (getRawSession() != null) { dest = getRawSession().getDestination(); - } else if (streamSession != null) { - dest = streamSession.getDestination(); + } else if (getStreamSession() != null) { + dest = getStreamSession().getDestination(); } else if (getDatagramSession() != null) { dest = getDatagramSession().getDestination(); } else { diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index f439b1253..b629cc09a 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -44,17 +44,19 @@ public class SAMv3Handler extends SAMv1Handler { private final static Log _log = new Log ( SAMv3Handler.class ); - protected SAMv3StreamSession streamSession = null ; protected SAMv3RawSession rawSession = null ; protected SAMv3DatagramSession datagramSession = null ; - - protected SAMDatagramSession getDatagramSession() { - return datagramSession ; - } + protected SAMv3StreamSession streamSession = null ; protected SAMRawSession getRawSession() { return rawSession ; } + protected SAMDatagramSession getDatagramSession() { + return datagramSession ; + } + protected SAMStreamSession getStreamSession() { + return streamSession ; + } protected Session session = null ; From 0c738e2c6f25dad858a729b376a84b6788e681c8 Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Thu, 23 Apr 2009 00:55:12 +0000 Subject: [PATCH 13/14] SAMv3: stream session stop listening when the forwarding session closes. Doc has to be changed to reflect this. --- .../src/net/i2p/sam/SAMv3DatagramSession.java | 5 ++-- .../java/src/net/i2p/sam/SAMv3Handler.java | 19 +++++++++++- .../java/src/net/i2p/sam/SAMv3RawSession.java | 4 +-- .../src/net/i2p/sam/SAMv3StreamSession.java | 30 +++++++++++++++++-- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java index dcca03c14..c933e77cc 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java @@ -61,10 +61,9 @@ public class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Han int port = Integer.parseInt(portStr); String host = props.getProperty("HOST"); - if ( host==null ) { - _log.debug("no host specified. Take from the client socket"); - + if ( host==null ) { host = rec.getHandler().getClientIP(); + _log.debug("no host specified. Taken from the client socket : " + host+':'+port); } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index b629cc09a..b6a03a9eb 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -323,6 +323,8 @@ public class SAMv3Handler extends SAMv1Handler boolean stolenSocket = false ; + boolean streamForwardingSocket = false ; + public void stealSocket() { stolenSocket = true ; @@ -414,6 +416,20 @@ public class SAMv3Handler extends SAMv1Handler _log.error("Error closing socket: " + e.getMessage()); } } + if (streamForwardingSocket) + { + if (this.streamSession!=null) { + try { + this.streamSession.stopForwardingIncoming(); + } catch (SAMException e) { + _log.error("Error while stopping forwarding connections: " + e.getMessage()); + } catch (InterruptedIOException e) { + _log.error("Interrupted while stopping forwarding connections: " + e.getMessage()); + } + } + } + + die(); } @@ -692,9 +708,10 @@ public class SAMv3Handler extends SAMv1Handler protected boolean execStreamForwardIncoming( Properties props ) { try { try { + streamForwardingSocket = true ; streamSession.startForwardingIncoming(props); notifyStreamResult( true, "OK", null ); - return false ; + return true ; } catch (SAMException e) { _log.debug("Forwarding STREAM connections failed: " + e.getMessage()); notifyStreamResult ( true, "I2P_ERROR", "Forwarding failed : " + e.getMessage() ); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java index 1bf7d18a0..085293565 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java @@ -63,9 +63,9 @@ public class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Sess String host = props.getProperty("HOST"); if ( host==null ) { - _log.debug("no host specified. Take from the client socket"); - host = rec.getHandler().getClientIP(); + + _log.debug("no host specified. Taken from the client socket : " + host +':'+port); } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index f02591bb6..0c2922fb1 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -230,9 +230,8 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle String host = props.getProperty("HOST"); if ( host==null ) { - _log.debug("no host specified. Take from the client socket"); - host = rec.getHandler().getClientIP(); + _log.debug("no host specified. Taken from the client socket : " + host +':'+port); } @@ -368,7 +367,32 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle } } - + /** + * + * @param props + * @throws SAMException + * @throws InterruptedIOException + */ + public void stopForwardingIncoming() throws SAMException, InterruptedIOException + { + SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick); + + if ( rec==null ) throw new InterruptedIOException() ; + + I2PServerSocket server = null ; + synchronized( this.socketServerLock ) + { + if (this.socketServer==null) { + _log.debug("no socket server is defined for this destination"); + throw new SAMException("no socket server is defined for this destination"); + } + server = this.socketServer ; + this.socketServer = null ; + } + try { + server.close(); + } catch ( I2PException e) {} + } /** * Close the stream session From 4ffa2450c205c0f153d9763e0d2c60b15692e76b Mon Sep 17 00:00:00 2001 From: mkvore-commit Date: Sun, 26 Apr 2009 10:14:23 +0000 Subject: [PATCH 14/14] * SAMv3 : doc/sam.3.0-protocol.txt updated * SAMv3 : SAMv3StreamSession.java: thread naming for debugging purpose --- apps/sam/doc/sam.3.0-protocol.txt | 24 ++++++++++--------- .../src/net/i2p/sam/SAMv3StreamSession.java | 22 +++++++++-------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/sam/doc/sam.3.0-protocol.txt b/apps/sam/doc/sam.3.0-protocol.txt index c544068db..7d8871dca 100644 --- a/apps/sam/doc/sam.3.0-protocol.txt +++ b/apps/sam/doc/sam.3.0-protocol.txt @@ -135,7 +135,7 @@ Streams are bidirectional communication sockets between two I2P destinations, but their opening has to be requested by one of them. Hereafter, CONNECT commands are used by the SAM client for such a request. FORWARD / ACCEPT commands are used by the SAM client when -it wants to listen to requests coming from other I2P destinations. +he wants to listen to requests coming from other I2P destinations. ----------------------------- @@ -231,10 +231,11 @@ I2P destination peer, until one of the peer closes the socket. SAM virtual streams : FORWARD ----------------------------- -A client waits for an incoming connection request by : - * opening a new socket with the SAM bridge - * passing the same HELLO handshake as above - * sending the forward command : +A client can use a regular socket server and wait for connection requests +coming from I2P. For that, the client has to : + * open a new socket with the SAM bridge + * pass the same HELLO handshake as above + * send the forward command : -> STREAM FORWARD ID={$nickname} @@ -242,7 +243,7 @@ A client waits for an incoming connection request by : [HOST={$host}] [SILENCE={true,false}] -This makes the session ${nickname} listen forever for incoming +This makes the session ${nickname} listen for incoming connection requests from the I2P network. The SAM bridge answers with : @@ -257,16 +258,12 @@ The RESULT value may be one of: I2P_ERROR INVALID_ID -The socket is closed immediately after the message by the SAM -bridge. If the result is OK, the SAM bridge starts waiting for -incoming connection requests from other I2P peers. - * {$host} is the hostname or IP address of the socket server to which SAM will forward connection requests. If not given, SAM takes the IP of the socket that issued the forward command. * {$port} is the port number of the socket server to which SAM will -forward connection requests. Is is mandatory. +forward connection requests. It is mandatory. When a connexion request arrives from I2P, the SAM bridge requests a socket connexion from {$host}:{$port}. If it is accepted after no more @@ -284,6 +281,11 @@ socket. +The I2P router will stop listening to incoming connection requests as +soon as the "forwarding" socket is closed. + + + ---------------------------------------------------------------------- SAM repliable datagrams : sending a datagram diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java index 0c2922fb1..0d5615abe 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java @@ -156,8 +156,8 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle WritableByteChannel toClient = handler.getClientSocket(); WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream()); - (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P), "SAMPipeClientToI2P"))).start(); - (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient), "SAMPipeClientToI2P"))).start(); + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P, "SAMPipeClientToI2P"), "SAMPipeClientToI2P"), "SAMPipeClientToI2P")).start(); + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient, "SAMPipeI2PToClient"), "SAMPipeI2PToClient"), "SAMPipeI2PToClient")).start(); } @@ -209,8 +209,8 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle WritableByteChannel toClient = handler.getClientSocket(); WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream()); - (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P), "SAMPipeClientToI2P"))).start(); - (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient), "SAMPipeClientToI2P"))).start(); + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P, "SAMPipeClientToI2P"), "SAMPipeClientToI2P"), "SAMPipeClientToI2P")).start(); + (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient, "SAMPipeI2PToClient"), "SAMPipeI2PToClient"), "SAMPipeI2PToClient")).start(); } @@ -245,8 +245,7 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle } SocketForwarder forwarder = new SocketForwarder(host, port, this, verbose); - (new Thread(rec.getThreadGroup(), new I2PAppThread(forwarder, "SAMStreamForwarder"))).start(); - + (new Thread(rec.getThreadGroup(), new I2PAppThread(forwarder, "SAMStreamForwarder"), "SAMStreamForwarder")).start(); } public class SocketForwarder extends Thread @@ -309,9 +308,11 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream()); WritableByteChannel toClient = clientServerSock ; WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream()); - new I2PAppThread(new Pipe(fromClient,toI2P), "SAMPipeClientToI2P").start(); - new I2PAppThread(new Pipe(fromI2P,toClient), "SAMPipeClientToI2P").start(); - + I2PAppThread send = new I2PAppThread(new Pipe(fromClient,toI2P, "SAMPipeClientToI2P"), "SAMPipeClientToI2P"); + I2PAppThread recv = new I2PAppThread(new Pipe(fromI2P,toClient, "SAMPipeI2PToClient"), "SAMPipeI2PToClient"); + send.start(); + recv.start(); + } catch (IOException e) { try { clientServerSock.close(); @@ -330,8 +331,9 @@ public class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handle WritableByteChannel out ; ByteBuffer buf ; - public Pipe(ReadableByteChannel in, WritableByteChannel out) + public Pipe(ReadableByteChannel in, WritableByteChannel out, String name) { + super(name); this.in = in ; this.out = out ; this.buf = ByteBuffer.allocate(BUFFER_SIZE) ;