UPnP ServerSocketFactory for JXTA JSE

A few days ago for unexplained reasons, I think it was because I had been looking at the JXTA JSE TCP Message Transport accept() loop, I decided it might be cool to see if I could take advantage of the UPnP dynamic port forwarding feature.

I started by doing some lunchtime reading but beyond hundreds of message board posts "How do I get UPnP forwarding working for <insert file sharing application name here> with my <insert consumer router name and model here> so I can steal more movies?" and the inscrutable (and obviously machine generated) UPnP device profile documents I hadn't found much good information. I knew of several other open source projects which included UPnP port forwarding as a feature and I resolved to investigate when I got time.

Fast forward to Friday evening. It was raining and the Chinese New Year Parade (Solano Avenue's first) and dinner I had planned on was canceled. So I started digging into source. Somehow I got distracted several times but settled on trying to implement based upon the "Legacy NAT Traversal" code I found in mDNSResponder (otherwise known as Bonjour). I got the code up to the point where I needed an actual UPnP device in order to test what I had written but my wife was doing email and wasn't thrilled by the prospect of me hauling off the wireless router to the garage for "harmless tinkering".

Saturday morning as I watched cartoons, I had found what looked to be some better resources for UPnP including the very spiffy looking Super Bonbon Industries UPNPLib and a heck of a good license too.

Saturday evening, it's still raining. My wife is intent on sitting by the fire and building a giant K'nex rollercoaster that if full size nobody would be able to keep their lunch on (or possibly even survive). Now's my chance to steal off to the laboratory (garage) with the wireless router and perform some experiments! Within minutes I have port forwarding running within NetBeans using the sample code provided on the Super Bonbon web site.

Following warnings I had read about the general bugginess of UPnP support by routers I spent some time experimenting with mapping and unmapping, looking at what happens when I try to remap the same address that's already mapped, halt the program with active mappings, play around with timeouts, etc. It turns out that my NetGear router is better than most.

I decided to abandon my Friday night code in favour of something new based upon UPNPLib. Pretty quickly I was able to put together a ServerSocketFactory that I could get JXTA to use by specifying it as the factory to net.jxta.impl.endpoint.IPUtils. I have not tested the factory with JXTA, but I have done some testing with a standalone program to ensure that the forwarding works properly. The hard part, in theory, is done.

So now that I have written the code some doubts remain. I am concerned with how best to integrate and configure this feature into JXTA. I also still have a lot of questions and uncertainty about the viability of the feature as discussed on P2PHackers UPnP Realworld Stats and the previously mentioned thousands of message threads seeking assistance. Adding UPnP port forwarding as a feature could be a support nightmare. I'm also wondering about Stuart Cheshire's Internet Draft for NAT Port Mapping Protocol (NAT-PMP). Maybe it will catch on, maybe it won't. I'm also concerned that adding UPNPLib might be seen as a hardship by some because it requires several additional support jars. I don't want to require yet more jars with JXTA. UPnPLib also doesn't appear to handle IP multi-homing. While it is true that most people who would need to use UPnP port forwarding have only one IP connection I'm not willing to force JXTA to use only one connection.

Lastly, I wonder if UPnP port forwarding is a feature that interests JXTA users and developers. Is anyone developing JXTA applications that will be deployed primarily on home networks where UPnP support is most commonly found. Would adding inbound connectivity for an additional 20% of end users (wild guess number) be useful? As I mentioned I suspect that enabling UPnP port forwarding might have end user support cost implications but I also think that it is likely to require a lot of testing and fine tuning to be realistically deployable. Some of this work has undoubtedly been done by the Super BonBon Industries developers.

In spite of my doubts, I sure did have fun researching and hacking out the code so it was ultimately worth doing.

UPnPServerSocketFactory.java
package upnptest;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.logging.Logger;
import javax.net.ServerSocketFactory;
import net.jxta.impl.endpoint.IPUtils;
import net.jxta.impl.util.TimeUtils;
import net.sbbi.upnp.impls.InternetGatewayDevice;
import net.sbbi.upnp.messages.UPNPResponseException;

/\*\*
 \*
 \* @author mike
 \*/
public class UPnPServerSocketFactory extends ServerSocketFactory {
    /\*\*
     \* Logger
     \*/
    private static final Logger LOG = Logger.getLogger(UPnPServerSocketFactory.class.getName());
    
    private final InetAddress ANYADDRESS;
    
    private InternetGatewayDevice[] IGDs = null;
    
    private InternetGatewayDevice currentIGD = null;
    
    private long lastIGDQueryAt = 0;
    
    /\*\* Creates a new instance of UPnPServerSocketFactory \*/
    public UPnPServerSocketFactory( ) throws IOException {
        // XXX 20070210 bondolo IPv4 specific.
        ANYADDRESS = InetAddress.getByName("0.0.0.0");
        
        if( null == getCurrentIGD() ) {
            throw new IOException( "No suitable internet gateway devices found." );
        }
    }
    
    /\*\*
     \* 
     \*/
    public InetAddress getExternalAddress() throws IOException {
        InternetGatewayDevice useIGD = getCurrentIGD();
        
        if( null == useIGD ) {
            throw new IOException( "No suitable internet gateway devices found." );
        }
        try {            
            String response = useIGD.getExternalIPAddress();
            
            InetAddress result = InetAddress.getByName( response );
            
            return result;
        }  catch (UPNPResponseException ex) {
            IOException rethrow = new IOException( "UPnP Operation Failed : " + ex.getMessage()  );
            rethrow.initCause(ex);
            
            throw rethrow;
        }
    }
    
    /\*\*
     \*  {@inheritDoc}
     \*/
    public ServerSocket createServerSocket(int port ) throws IOException {
        return createServerSocket( port, 0, ANYADDRESS );
    }
    
    /\*\*
     \*  {@inheritDoc}
     \*/
    public ServerSocket createServerSocket(int port, int backlog ) throws IOException {
        return createServerSocket( port, backlog, ANYADDRESS );
    }
    
    /\*\*
     \*  {@inheritDoc}
     \*/
    public synchronized ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress)
        throws IOException {
        
        InternetGatewayDevice useIGD = getCurrentIGD();
        
        if( null == useIGD ) {
            throw new IOException( "No suitable internet gateway devices found." );
        }
        
        // The local socket. This is what we will pass back and what the router will forward to.
        ServerSocket localServerSocket = new ServerSocket( port, backlog, ifAddress );
        
        if( ifAddress.equals(ANYADDRESS) ) {
            // FIXME 20070210 bondolo This is totally wrong and entirely speculative. 
            // But it does work if you're not mulithomed.
            ifAddress = InetAddress.getLocalHost();
        }
        
        
        try {
            boolean mapped = useIGD.addPortMapping( "JXTA TCP ServerSocket",
                    null, localServerSocket.getLocalPort(),
                    localServerSocket.getLocalPort(), ifAddress.getHostAddress(),
                    0, "TCP" );
        } catch (UPNPResponseException ex) {
            IOException rethrow = new IOException( "UPnP Operation Failed : " + ex.getMessage()  );
            rethrow.initCause(ex);
            
            throw rethrow;
        }  catch( IOException failed ) {
            try {
                localServerSocket.close();
            } catch( IOException ignored ) {
                ;
            }
            
            throw failed;
        }
        
        return localServerSocket;
    }
    
    private synchronized InternetGatewayDevice getCurrentIGD() {
        if( TimeUtils.toRelativeTimeMillis( TimeUtils.timeNow(), lastIGDQueryAt) > (5 \* TimeUtils.AMINUTE) ) {
            IGDs = null;
            currentIGD = null;
            
            try {
                IGDs = InternetGatewayDevice.getDevices( (int) (5 \* TimeUtils.ASECOND) );
                
                if( null != IGDs ) {
                    // FIXME 20070210 bondolo This may cause us to change IGD even though 
                    // our current one has not failed.
                    currentIGD = IGDs[0];
                }
            } catch( IOException failed ) {
                
            }
            lastIGDQueryAt = TimeUtils.timeNow();
        }
        
        return currentIGD;
    }
}
Comments:

Post a Comment:
Comments are closed for this entry.
About

mduigou

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today