You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by tr...@apache.org on 2005/03/09 05:12:05 UTC

svn commit: r156607 - in incubator/directory/network/mina/trunk/src/java/org/apache/mina/io: datagram/DatagramAcceptor.java filter/SSLByteBufferPool.java filter/SSLFilter.java filter/SSLHandler.java socket/SocketAcceptor.java

Author: trustin
Date: Tue Mar  8 20:12:03 2005
New Revision: 156607

URL: http://svn.apache.org/viewcvs?view=rev&rev=156607
Log:
* Merged Jan's SSL fixes
* Fixed: SSLHandler.unwrap() cannot unwrap if the buffer contains multiple encrypted chunks.
* Separated unwrap() into unwrap() and unwrapHandshake()

Modified:
    incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/datagram/DatagramAcceptor.java
    incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLByteBufferPool.java
    incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLFilter.java
    incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLHandler.java
    incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/socket/SocketAcceptor.java

Modified: incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/datagram/DatagramAcceptor.java
URL: http://svn.apache.org/viewcvs/incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/datagram/DatagramAcceptor.java?view=diff&r1=156606&r2=156607
==============================================================================
--- incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/datagram/DatagramAcceptor.java (original)
+++ incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/datagram/DatagramAcceptor.java Tue Mar  8 20:12:03 2005
@@ -21,7 +21,6 @@
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
-import java.nio.channels.ClosedChannelException;
 import java.nio.channels.DatagramChannel;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
@@ -32,9 +31,9 @@
 import java.util.Set;
 
 import org.apache.mina.common.ByteBuffer;
-import org.apache.mina.io.IoAcceptor;
 import org.apache.mina.io.DefaultExceptionMonitor;
 import org.apache.mina.io.ExceptionMonitor;
+import org.apache.mina.io.IoAcceptor;
 import org.apache.mina.io.IoHandler;
 import org.apache.mina.io.IoHandlerFilter;
 import org.apache.mina.util.IoHandlerFilterManager;
@@ -92,30 +91,14 @@
         if( ( ( InetSocketAddress ) address ).getPort() == 0 )
             throw new IllegalArgumentException( "Unsupported port number: 0" );
 
-        DatagramChannel ch = DatagramChannel.open();
-        boolean bound = false;
-        try
+        RegistrationRequest request = new RegistrationRequest( address, handler );
+        synchronized( registerQueue )
         {
-            ch.configureBlocking( false );
-            ch.socket().bind( address );
-            bound = true;
-        }
-        finally
-        {
-            if( !bound )
-            {
-                ch.close();
-            }
+            registerQueue.push( request );
         }
 
         synchronized( this )
         {
-            synchronized( registerQueue )
-            {
-                registerQueue.push( new RegistrationRequest( ch, handler ) );
-            }
-            channels.put( address, ch );
-
             if( worker == null )
             {
                 worker = new Worker();
@@ -124,6 +107,25 @@
         }
 
         selector.wakeup();
+        
+        synchronized( request )
+        {
+            while( !request.done )
+            {
+                try
+                {
+                    request.wait();
+                }
+                catch( InterruptedException e )
+                {
+                }
+            }
+        }
+        
+        if( request.exception != null )
+        {
+            throw request.exception;
+        }
     }
 
     public void unbind( SocketAddress address )
@@ -131,25 +133,27 @@
         if( address == null )
             throw new NullPointerException( "address" );
 
-        DatagramChannel ch;
-
-        synchronized( this )
+        CancellationRequest request = new CancellationRequest( address );
+        synchronized( cancelQueue )
         {
-            ch = ( DatagramChannel ) channels.get( address );
-
-            if( ch == null )
-                return;
+            cancelQueue.push( request );
+        }
 
-            SelectionKey key = ch.keyFor( selector );
-            channels.remove( address );
-            synchronized( cancelQueue )
+        selector.wakeup();
+        
+        synchronized( request )
+        {
+            while( !request.done )
             {
-                cancelQueue.push( key );
+                try
+                {
+                    request.wait();
+                }
+                catch( InterruptedException e )
+                {
+                }
             }
         }
-
-        selector.wakeup();
-        ch.socket().close();
     }
 
     public void flushSession( DatagramSession session )
@@ -380,7 +384,7 @@
         }
     }
 
-    private void registerNew() throws ClosedChannelException
+    private void registerNew()
     {
         if( registerQueue.isEmpty() )
             return;
@@ -396,8 +400,39 @@
             if( req == null )
                 break;
 
-            req.channel
-                    .register( selector, SelectionKey.OP_READ, req.handler );
+            DatagramChannel ch = null;
+            try
+            {
+                ch = DatagramChannel.open();
+                ch.configureBlocking( false );
+                ch.socket().bind( req.address );
+                ch.register( selector, SelectionKey.OP_READ, req.handler );
+                channels.put( req.address, ch );
+            }
+            catch( IOException e )
+            {
+                req.exception = e;
+            }
+            finally
+            {
+                synchronized( req )
+                {
+                    req.done = true;
+                    req.notify();
+                }
+
+                if( ch != null && req.exception != null )
+                {
+                    try
+                    {
+                        ch.close();
+                    }
+                    catch( IOException e )
+                    {
+                        exceptionMonitor.exceptionCaught( this, e );
+                    }
+                }
+            }
         }
     }
 
@@ -408,18 +443,41 @@
 
         for( ;; )
         {
-            SelectionKey key;
+            CancellationRequest request;
             synchronized( cancelQueue )
             {
-                key = ( SelectionKey ) cancelQueue.pop();
+                request = ( CancellationRequest ) cancelQueue.pop();
             }
-
-            if( key == null )
+            
+            if( request == null )
+            {
                 break;
-            else
+            }
+
+            DatagramChannel ch = ( DatagramChannel ) channels.get( request.address );
+            if( ch == null )
+                continue;
+            
+            SelectionKey key = ch.keyFor( selector );
+            key.cancel();
+            selector.wakeup(); // wake up again to trigger thread death
+            
+            // close the channel
+            try
+            {
+                ch.close();
+            }
+            catch( IOException e )
+            {
+                exceptionMonitor.exceptionCaught( this, e );
+            }
+            finally
             {
-                key.cancel();
-                selector.wakeup(); // wake up again to trigger thread death
+                synchronized( request )
+                {
+                    request.done = true;
+                    request.notify();
+                }
             }
         }
     }
@@ -446,15 +504,30 @@
 
     private static class RegistrationRequest
     {
-        private final DatagramChannel channel;
-
+        private final SocketAddress address;
+        
         private final IoHandler handler;
-
-        private RegistrationRequest( DatagramChannel channel,
-                                    IoHandler handler )
+        
+        private IOException exception; 
+        
+        private boolean done;
+        
+        private RegistrationRequest( SocketAddress address, IoHandler handler )
         {
-            this.channel = channel;
+            this.address = address;
             this.handler = handler;
+        }
+    }
+
+    private static class CancellationRequest
+    {
+        private final SocketAddress address;
+        
+        private boolean done;
+        
+        private CancellationRequest( SocketAddress address )
+        {
+            this.address = address;
         }
     }
 

Modified: incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLByteBufferPool.java
URL: http://svn.apache.org/viewcvs/incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLByteBufferPool.java?view=diff&r1=156606&r2=156607
==============================================================================
--- incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLByteBufferPool.java (original)
+++ incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLByteBufferPool.java Tue Mar  8 20:12:03 2005
@@ -58,7 +58,7 @@
      *
      * @param sslEngine SSLEngine
      */
-    static void initiate( SSLEngine sslEngine )
+    static synchronized void initiate( SSLEngine sslEngine )
     {
         if( !initiated )
         {
@@ -71,7 +71,11 @@
             }
             // init buffer sizes from SSLEngine
             packetBufferSize = sslEngine.getSession().getPacketBufferSize();
-            appBufferSize = sslEngine.getSession().getApplicationBufferSize();
+            
+            // application buffer size has been doubled because SSLEngine
+            // returns BUFFER_OVERFLOW even if there is enough room for the buffer.
+            // So I doubled the size as a workaround.
+            appBufferSize = sslEngine.getSession().getApplicationBufferSize() * 2;
             initiateBufferStacks();
             initiated = true;
         }

Modified: incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLFilter.java
URL: http://svn.apache.org/viewcvs/incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLFilter.java?view=diff&r1=156606&r2=156607
==============================================================================
--- incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLFilter.java (original)
+++ incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLFilter.java Tue Mar  8 20:12:03 2005
@@ -156,6 +156,11 @@
         SSLHandler sslHandler = getSSLSessionHandler( session );
         if( sslHandler != null )
         {
+            if( debug != null )
+            {
+                debug.print( "Data Read: " + sslHandler + " ("
+                             + buf.getHexDump() + ')' );
+            }
             synchronized( sslHandler )
             {
                 try
@@ -165,6 +170,16 @@
 
                     // Handle data to be forwarded to application or written to net
                     handleSSLData( nextHandler, session, sslHandler );
+
+                    if( sslHandler.isClosed() )
+                    {
+                        if( debug != null )
+                        {
+                            debug
+                                    .print( "SSL Session closed. Closing connection.." );
+                        }
+                        session.close();
+                    }
                 }
                 catch( SSLException ssle )
                 {
@@ -196,6 +211,10 @@
     {
 
         SSLHandler sslHandler = getSSLSessionHandler( session );
+        if( debug != null )
+        {
+            debug.print( "Filtered Write: " + sslHandler );
+        }
         if( sslHandler != null )
         {
             synchronized( sslHandler )
@@ -221,7 +240,12 @@
                         sslHandler.encrypt( buf.buf() );
                         ByteBuffer encryptedBuffer = copy( sslHandler
                                 .getOutNetBuffer() );
-                        //debug("encrypted data: {0}", encryptedBuffer.getHexDump());
+
+                        if( debug != null )
+                        {
+                            debug.print( "encrypted data: "
+                                    + encryptedBuffer.getHexDump() );
+                        }
                         return encryptedBuffer;
                     }
                     catch( SSLException ssle )
@@ -236,6 +260,7 @@
     }
 
     // Utiliities
+
     private void handleSSLData( IoHandler nextHandler, IoSession session,
                                SSLHandler sslHandler ) throws SSLException
     {
@@ -258,9 +283,8 @@
             ByteBuffer readBuffer = copy( sslHandler.getAppBuffer() );
             if( debug != null )
             {
-                debug.print( "app data read: " + readBuffer );
+                debug.print( "app data read: " + readBuffer + " (" + readBuffer.getHexDump() + ')' );
             }
-            //debug("app data: {0}", readBuffer.getHexDump());
             nextHandler.dataRead( session, readBuffer );
         }
     }
@@ -387,7 +411,7 @@
          * This will print out the messages to Commons-Logging or stdout.
          */
         static final Debug ON = new DebugOn();
-        
+
         /**
          * This will suppress debug messages.
          */

Modified: incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLHandler.java
URL: http://svn.apache.org/viewcvs/incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLHandler.java?view=diff&r1=156606&r2=156607
==============================================================================
--- incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLHandler.java (original)
+++ incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/filter/SSLHandler.java Tue Mar  8 20:12:03 2005
@@ -131,6 +131,14 @@
     }
 
     /**
+     * Check if SSL sesssion closed
+     */
+    public boolean isClosed()
+    {
+        return closed;
+    }
+
+    /**
      * Check if there is any need to complete initial handshake.
      */
     public boolean needToCompleteInitialHandshake()
@@ -212,7 +220,10 @@
      */
     public void shutdown() throws SSLException
     {
-        doShutdown();
+        if( !shutdown )
+        {
+            doShutdown();
+        }
     }
 
     /**
@@ -244,12 +255,24 @@
             throw new IllegalStateException();
         }
 
-        SSLEngineResult.Status status = unwrap();
-        if( status != SSLEngineResult.Status.OK
-                && status != SSLEngineResult.Status.CLOSED )
+        unwrap();
+    }
+
+    /**
+     * @param status
+     * @throws SSLException
+     */
+    private SSLEngineResult.Status checkStatus( SSLEngineResult.Status status ) throws SSLException
+    {
+        if( status != SSLEngineResult.Status.OK &&
+            status != SSLEngineResult.Status.CLOSED &&
+            status != SSLEngineResult.Status.BUFFER_UNDERFLOW )
         {
-            throw new SSLException( "Unexpected SSLEngineResult: " + status );
+            throw new SSLException( "SSLEngine error during decrypt: " +
+                                    status );
         }
+        
+        return status;
     }
 
     private void doEncrypt( ByteBuffer src ) throws SSLException
@@ -266,7 +289,7 @@
 
         outNetBuffer.flip();
 
-        if ( result.getStatus() == SSLEngineResult.Status.OK )
+        if( result.getStatus() == SSLEngineResult.Status.OK )
         {
             if( result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK )
             {
@@ -275,7 +298,7 @@
         }
         else
         {
-            throw new SSLException( "SSLEngine error during data write: "
+            throw new SSLException( "SSLEngine error during encrypt: "
                     + result.getStatus() );
         }
     }
@@ -292,7 +315,7 @@
         }
         while( true )
         {
-            if ( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED )
+            if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED )
             {
                 if( parent.debug != null )
                 {
@@ -301,7 +324,7 @@
                 initialHandshakeComplete = true;
                 return;
             }
-            else if ( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK )
+            else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK )
             {
                 if( parent.debug != null )
                 {
@@ -309,7 +332,7 @@
                 }
                 initialHandshakeStatus = doTasks();
             }
-            else if ( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP )
+            else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP )
             {
                 // we need more data read
                 if( parent.debug != null )
@@ -317,14 +340,15 @@
                     parent.debug
                             .print( " initialHandshakeStatus=NEED_UNWRAP" );
                 }
-                SSLEngineResult.Status status = unwrap();
-                if( status == SSLEngineResult.Status.BUFFER_UNDERFLOW || closed )
+                SSLEngineResult.Status status = unwrapHandshake();
+                if( status == SSLEngineResult.Status.BUFFER_UNDERFLOW
+                        || closed )
                 {
                     // We need more data or the session is closed
                     return;
                 }
             }
-            else if ( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP )
+            else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP )
             {
                 if( parent.debug != null )
                 {
@@ -342,6 +366,11 @@
                 }
                 outNetBuffer.clear();
                 SSLEngineResult result = sslEngine.wrap( hsBB, outNetBuffer );
+                if( parent.debug != null )
+                {
+                    parent.debug.print( "Wrap res:" + result );
+                }
+
                 outNetBuffer.flip();
                 initialHandshakeStatus = result.getHandshakeStatus();
                 // return to allow data on out buffer being sent
@@ -364,12 +393,12 @@
         // Prepare the application buffer to receive decrypted data
         appBuffer.clear();
 
+        // Prepare the net data for reading.
+        inNetBuffer.flip();
+
         SSLEngineResult res;
         do
         {
-            // Prepare the net data for reading.
-            inNetBuffer.flip();
-
             if( parent.debug != null )
             {
                 parent.debug.print( "  inNetBuffer: " + inNetBuffer );
@@ -380,48 +409,71 @@
             {
                 parent.debug.print( "Unwrap res:" + res );
             }
-            // prepare to be written again
-            inNetBuffer.compact();
+        }
+        while( res.getStatus() == SSLEngineResult.Status.OK );
 
-            /*
-             * Could check here for a renegotation, but we're only
-             * doing a simple read/write, and won't have enough state
-             * transitions to do a complete handshake, so ignore that
-             * possibility.
-             */
-            SSLEngineResult.Status status = res.getStatus();
-            if( status == SSLEngineResult.Status.BUFFER_UNDERFLOW ||
-                status == SSLEngineResult.Status.OK )
-            {
-                if( res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK )
-                {
-                    doTasks();
-                }
-            }
-            else if( status == SSLEngineResult.Status.CLOSED )
+        // If we are CLOSED, set flag
+        if( res.getStatus() == SSLEngineResult.Status.CLOSED )
+        {
+            closed = true;
+        }
+
+        // prepare to be written again
+        inNetBuffer.compact();
+        // prepare app data to be read
+        appBuffer.flip();
+
+        /*
+         * The status may be:
+         * OK - Normal operation
+         * OVERFLOW - Should never happen since the application buffer is
+         *      sized to hold the maximum packet size.
+         * UNDERFLOW - Need to read more data from the socket. It's normal.
+         * CLOSED - The other peer closed the socket. Also normal.
+         */
+        return checkStatus( res.getStatus() );
+    }
+
+    SSLEngineResult.Status unwrapHandshake() throws SSLException
+    {
+        if( parent.debug != null )
+        {
+            parent.debug.print( "unwrapHandshake()" );
+        }
+        // Prepare the application buffer to receive decrypted data
+        appBuffer.clear();
+
+        // Prepare the net data for reading.
+        inNetBuffer.flip();
+
+        SSLEngineResult res;
+        do
+        {
+            if( parent.debug != null )
             {
-                if( parent.debug != null )
-                {
-                    parent.debug.print( "Closed while unwrapping" );
-                }
-                break;
+                parent.debug.print( "  inNetBuffer: " + inNetBuffer );
+                parent.debug.print( "  appBuffer: " + appBuffer );
             }
-            else
+            res = sslEngine.unwrap( inNetBuffer, appBuffer );
+            if( parent.debug != null )
             {
-                throw new SSLException( "SSLEngine error during data read: "
-                        + res.getStatus() );
+                parent.debug.print( "Unwrap res:" + res );
             }
+
         }
-        while( ( inNetBuffer.position() != 0 )
-                && res.getStatus() != SSLEngineResult.Status.BUFFER_UNDERFLOW );
+        while( res.getStatus() == SSLEngineResult.Status.OK &&
+               res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP );
 
         // If we are CLOSED, set flag
         if( res.getStatus() == SSLEngineResult.Status.CLOSED )
         {
             closed = true;
         }
+        
+        // prepare to be written again
+        inNetBuffer.compact();
 
-        // prepare app datat to be read
+        // prepare app data to be read
         appBuffer.flip();
 
         /*
@@ -433,7 +485,7 @@
          * CLOSED - The other peer closed the socket. Also normal.
          */
         initialHandshakeStatus = res.getHandshakeStatus();
-        return res.getStatus();
+        return checkStatus( res.getStatus() );
     }
 
     /**

Modified: incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/socket/SocketAcceptor.java
URL: http://svn.apache.org/viewcvs/incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/socket/SocketAcceptor.java?view=diff&r1=156606&r2=156607
==============================================================================
--- incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/socket/SocketAcceptor.java (original)
+++ incubator/directory/network/mina/trunk/src/java/org/apache/mina/io/socket/SocketAcceptor.java Tue Mar  8 20:12:03 2005
@@ -21,7 +21,6 @@
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
-import java.nio.channels.ClosedChannelException;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
 import java.nio.channels.ServerSocketChannel;
@@ -31,9 +30,9 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.mina.io.IoAcceptor;
 import org.apache.mina.io.DefaultExceptionMonitor;
 import org.apache.mina.io.ExceptionMonitor;
+import org.apache.mina.io.IoAcceptor;
 import org.apache.mina.io.IoHandler;
 import org.apache.mina.io.IoHandlerFilter;
 import org.apache.mina.util.IoHandlerFilterManager;
@@ -102,30 +101,14 @@
         if( ( ( InetSocketAddress ) address ).getPort() == 0 )
             throw new IllegalArgumentException( "Unsupported port number: 0" );
 
-        ServerSocketChannel ssc = ServerSocketChannel.open();
-        boolean bound = false;
-        try
+        RegistrationRequest request = new RegistrationRequest( address, backlog, handler );
+        synchronized( registerQueue )
         {
-            ssc.configureBlocking( false );
-            ssc.socket().bind( address, backlog );
-            bound = true;
-        }
-        finally
-        {
-            if( !bound )
-            {
-                ssc.close();
-            }
+            registerQueue.push( request );
         }
 
         synchronized( this )
         {
-            synchronized( registerQueue )
-            {
-                registerQueue.push( new RegistrationRequest( ssc, handler ) );
-            }
-            channels.put( address, ssc );
-
             if( worker == null )
             {
                 worker = new Worker();
@@ -134,39 +117,52 @@
         }
 
         selector.wakeup();
+        
+        synchronized( request )
+        {
+            while( !request.done )
+            {
+                try
+                {
+                    request.wait();
+                }
+                catch( InterruptedException e )
+                {
+                }
+            }
+        }
+        
+        if( request.exception != null )
+        {
+            throw request.exception;
+        }
     }
 
     public void unbind( SocketAddress address )
     {
         if( address == null )
             throw new NullPointerException( "address" );
-
-        ServerSocketChannel ssc;
-
-        synchronized( this )
+        
+        CancellationRequest request = new CancellationRequest( address );
+        synchronized( cancelQueue )
         {
-            ssc = ( ServerSocketChannel ) channels.get( address );
-
-            if( ssc == null )
-                return;
-
-            SelectionKey key = ssc.keyFor( selector );
-            channels.remove( address );
-            synchronized( cancelQueue )
-            {
-                cancelQueue.push( key );
-            }
+            cancelQueue.push( request );
         }
 
         selector.wakeup();
 
-        try
-        {
-            ssc.close();
-        }
-        catch( IOException e )
+        synchronized( request )
         {
-            exceptionMonitor.exceptionCaught( this, e );
+            while( !request.done )
+            {
+                try
+                {
+                    request.wait();
+                }
+                catch( InterruptedException e )
+                {
+                }
+            }
         }
     }
 
@@ -243,7 +239,7 @@
         }
     }
 
-    private void registerNew() throws ClosedChannelException
+    private void registerNew()
     {
         if( registerQueue.isEmpty() )
             return;
@@ -259,8 +255,40 @@
             if( req == null )
                 break;
 
-            req.channel.register( selector, SelectionKey.OP_ACCEPT,
-                    req.handler );
+            ServerSocketChannel ssc = null;
+            try
+            {
+                ssc = ServerSocketChannel.open();
+                ssc.configureBlocking( false );
+                ssc.socket().bind( req.address, req.backlog );
+                ssc.register( selector, SelectionKey.OP_ACCEPT,
+                              req.handler );
+                channels.put( req.address, ssc );
+            }
+            catch( IOException e )
+            {
+                req.exception = e;
+            }
+            finally
+            {
+                synchronized( req )
+                {
+                    req.done = true;
+                    req.notify();
+                }
+
+                if( ssc != null && req.exception != null )
+                {
+                    try
+                    {
+                        ssc.close();
+                    }
+                    catch( IOException e )
+                    {
+                        exceptionMonitor.exceptionCaught( this, e );
+                    }
+                }
+            }
         }
     }
 
@@ -271,18 +299,41 @@
 
         for( ;; )
         {
-            SelectionKey key;
+            CancellationRequest request;
             synchronized( cancelQueue )
             {
-                key = ( SelectionKey ) cancelQueue.pop();
+                request = ( CancellationRequest ) cancelQueue.pop();
             }
-
-            if( key == null )
+            
+            if( request == null )
+            {
                 break;
-            else
+            }
+
+            ServerSocketChannel ssc = ( ServerSocketChannel ) channels.get( request.address );
+            if( ssc == null )
+                continue;
+            
+            SelectionKey key = ssc.keyFor( selector );
+            key.cancel();
+            selector.wakeup(); // wake up again to trigger thread death
+            
+            // close the channel
+            try
             {
-                key.cancel();
-                selector.wakeup(); // wake up again to trigger thread death
+                ssc.close();
+            }
+            catch( IOException e )
+            {
+                exceptionMonitor.exceptionCaught( this, e );
+            }
+            finally
+            {
+                synchronized( request )
+                {
+                    request.done = true;
+                    request.notify();
+                }
             }
         }
     }
@@ -309,15 +360,34 @@
 
     private static class RegistrationRequest
     {
-        private final ServerSocketChannel channel;
+        private final SocketAddress address;
+        
+        private final int backlog;
 
         private final IoHandler handler;
-
-        private RegistrationRequest( ServerSocketChannel channel,
-                                    IoHandler handler )
+        
+        private IOException exception; 
+        
+        private boolean done;
+        
+        private RegistrationRequest( SocketAddress address, int backlog,
+                                     IoHandler handler )
         {
-            this.channel = channel;
+            this.address = address;
+            this.backlog = backlog;
             this.handler = handler;
+        }
+    }
+
+    private static class CancellationRequest
+    {
+        private final SocketAddress address;
+        
+        private boolean done;
+        
+        private CancellationRequest( SocketAddress address )
+        {
+            this.address = address;
         }
     }