FileDocCategorySizeDatePackage
NatPMPDeviceImpl.javaAPI DocAzureus 3.0.3.416733Fri Jun 15 11:53:02 BST 2007com.aelitis.net.natpmp.impl

NatPMPDeviceImpl

public class NatPMPDeviceImpl extends Object implements com.aelitis.net.natpmp.NatPMPDevice
Main class

Fields Summary
static final int
NATMAP_VER
static final int
NATMAP_PORT
static final int
NATMAP_RESPONSE_MASK
static final int
NATMAP_INIT_RETRY
static final int
NATMAP_MAX_RETRY
static final int
NATMAP_DEFAULT_LEASE
static final String
NATMAP_LLM
static final byte
NATOp_AddrRequest
static final byte
NATOp_MapUDP
static final byte
NATOp_MapTCP
static final int
NATAddrRequest
static final int
NATPortMapRequestLen
static final int
NATAddrReplyLen
static final int
NATPortMapReplyLen
static final int
NATResultSuccess
static final int
NATResultUnsupportedVer
static final int
NATResultNotAuth
Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off)
static final int
NATResultNetFailure
Network Failure (e.g. NAT box itself has not obtained a DHCP lease)
static final int
NATResultNoResc
static final int
NATResultUnsupportedOp
String
current_router_address
InetAddress
hostInet
InetAddress
natPriInet
InetAddress
natPubInet
NetworkInterface
networkInterface
InetAddress
llmInet
boolean
nat_pmp_found
int
nat_epoch
private com.aelitis.net.natpmp.NATPMPDeviceAdapter
adapter
private static NatPMPDeviceImpl
NatPMPDeviceSingletonRef
Singleton creation
Constructors Summary
private NatPMPDeviceImpl(com.aelitis.net.natpmp.NATPMPDeviceAdapter _adapter)

    	adapter		= _adapter;
        hostInet 	= NetUtils.getLocalHost();
        
        checkRouterAddress();
    
Methods Summary
public intaddPortMapping(boolean tcp, int publicPort, int privatePort)
Asks for a public port to be mapped to a private port from this host. NAP-PMP allows the device to assign another public port if the requested one is taken. So, you should check the returned port.

param
tcp true TCP, false UDP
return
the returned publicPort. -1 if error occured
todo
either take a class (like UPnPMapping) or return a class

        // check for actual connection!
        return portMappingProtocol( tcp, publicPort, privatePort, 
                                    NATMAP_DEFAULT_LEASE );                          
    
public java.lang.StringbyteArrayString(byte[] buf)

        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < buf.length; i++) {
            sb.append(buf[i]).append(" ");
        }
        return sb.toString();   
    
protected voidcheckRouterAddress()

    	String	natAddr = adapter.getRouterAddress().trim();
        
        if ( natAddr.length() == 0 ){
        
        	natAddr = convertHost2RouterAddress(hostInet);
        }
        
        if ( natAddr.equals( current_router_address )){
        	
        	return;
        }
        
        current_router_address = natAddr;
        
        log("Using Router IP: " + natAddr);
        
        natPriInet = InetAddress.getByName(natAddr);
        
        networkInterface = NetworkInterface.getByInetAddress( natPriInet );
    
public booleanconnect()
Try to connect with a NAT-PMP device. This could take sometime.

return
true if it found one


   		checkRouterAddress();
    	
    	try{
	        // Send NAT request to find out if it is PMP happy
	        byte reqBuf[] = {NATMAP_VER, NATOp_AddrRequest};
	        DatagramPacket dstPkt = new DatagramPacket(reqBuf, reqBuf.length);     
	        byte recBuf[] = new byte[NATAddrReplyLen];
	        /* DatagramPacket recPkt = */ sendNATMsg(natPriInet, dstPkt, recBuf);
	        
	        //int recVer = unsigned8ByteArrayToInt( recBuf, 0 ); 
	        //int recOp  = unsigned8ByteArrayToInt( recBuf, 1 ); 
	        int recErr = unsigned16ByteArrayToInt( recBuf, 2 ); 
	        int recEpoch  = unsigned32ByteArrayToInt( recBuf, 4 );
	        String recPubAddr = unsigned8ByteArrayToInt( recBuf, 8 ) + "." +
	                            unsigned8ByteArrayToInt( recBuf, 9 ) + "." +
	                            unsigned8ByteArrayToInt( recBuf, 10 ) + "." +
	                            unsigned8ByteArrayToInt( recBuf, 11 );
	        
	        /* set the global NAT public address */
	        natPubInet = InetAddress.getByName(recPubAddr);
	        
	        /* set the global NAT Epoch time (in seconds) */
	        nat_epoch = recEpoch;
	        
	        if (recErr != 0) 
	            throw( new Exception("NAT-PMP connection error: " + recErr) );
	            
	        log("Err: " +recErr);
	        log("Uptime: " + recEpoch);
	        log("Public Address: " + recPubAddr);
	           
	        /**
	         * TO DO:
	         *  Set up listner for announcements from the device for
	         *  address changes (public address changes)   
	         **/
	         
	        nat_pmp_found = true;
	        
	        return true;
	        
    	}catch( PortUnreachableException e ){
    		
    		return( false );
    	}
    
private java.lang.StringconvertHost2RouterAddress(java.net.InetAddress inet)

param
init takes the host address
return
String the address as (xxx.xxx.xxx.1)

    
        byte rawIP[] = inet.getAddress();
        
        // assume router is at xxx.xxx.xxx.1
        rawIP[3] = 1;
        // is there no printf in java?
        String newIP = (rawIP[0]&0xff) +"."+(rawIP[1]&0xff)+"."+(rawIP[2]&0xff)+"."+(rawIP[3]&0xff);
        return newIP;
    
public voiddeletePortMapping(boolean tcp, int publicPort, int privatePort)
Delete a mapped public port

param
tcp true TCP, false UDP port
param
publicPort the public port to close
param
privatePort the private port that it is mapped to
warn
untested

        /**
         * if the request was successful, a zero lifetime will 
         * delete the mapping and return a public port of 0
         **/
         // check for actual connection
        /*int result = */ portMappingProtocol(tcp, publicPort, privatePort, 0);
        
    
public intgetEpoch()

		return( nat_epoch );
	
public java.lang.StringgetExternalIPAddress()

		return( natPubInet.getHostAddress());
	
public java.net.InetAddressgetLocalAddress()

    	return( hostInet );
    
public java.net.NetworkInterfacegetNetworkInterface()

		return( networkInterface );
	
public static synchronized com.aelitis.net.natpmp.impl.NatPMPDeviceImplgetSingletonObject(com.aelitis.net.natpmp.NATPMPDeviceAdapter adapter)

    
       
                        
        if (NatPMPDeviceSingletonRef == null)
            NatPMPDeviceSingletonRef = new NatPMPDeviceImpl(adapter);
        return NatPMPDeviceSingletonRef;
    
public java.lang.StringintArrayString(int[] buf)

        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < buf.length; i++) {
            sb.append(buf[i]).append(" ");
        }
        return sb.toString();   
    
public byte[]intToByteArray(int v)
Convert a 32-bit int into a 4 byte array

return
unsigned byte array

        byte b[] = new byte[4];
        int i, shift;
          
        for(i = 0, shift = 24; i < 4; i++, shift -= 8)
            b[i] = (byte)(0xFF & (v >> shift));
        
        return b;
    
protected voidlog(java.lang.String str)

		adapter.log( str );
	
public intportMappingProtocol(boolean tcp, int publicPort, int privatePort, int lifetime)
General port mapping protocol


        byte NATOp = (tcp?NATOp_MapTCP:NATOp_MapUDP);
        // Should check for errors - only using lower 2 bytes
        byte pubPort[] = intToByteArray(publicPort);
        byte priPort[] = intToByteArray(privatePort);
        byte portLifeTime[] = intToByteArray(lifetime);
        
        // Generate Port Map request packet
        byte dstBuf[] = new byte[NATPortMapRequestLen];
        dstBuf[0] = NATMAP_VER;  // Ver
        dstBuf[1] = NATOp;       // OP
        dstBuf[2] = 0;           // Reserved - 2 bytes
        dstBuf[3] = 0;
        dstBuf[4] = priPort[2];  // Private Port - 2 bytes
        dstBuf[5] = priPort[3];
        dstBuf[6] = pubPort[2];  // Requested Public Port - 2 bytes
        dstBuf[7] = pubPort[3];
        for (int i = 0; i < 4; i++) {
            dstBuf[8 + i] = portLifeTime[i];
        }
        
        DatagramPacket dstPkt = new DatagramPacket(dstBuf, dstBuf.length);       
        byte recBuf[] = new byte[NATPortMapReplyLen];
        /* DatagramPacket recPkt = */ sendNATMsg(natPriInet, dstPkt, recBuf);

        // Unpack this and check codes
        //int recVers = unsigned8ByteArrayToInt( recBuf, 0 );
        int recOP =  unsigned8ByteArrayToInt( recBuf, 1 ); 
        int recCode = unsigned16ByteArrayToInt( recBuf, 2 );
        int recEpoch = unsigned32ByteArrayToInt( recBuf, 4);
        //int recPriPort = unsigned16ByteArrayToInt( recBuf, 8 );
        int recPubPort = unsigned16ByteArrayToInt( recBuf, 10 );
        int recLifetime = unsigned32ByteArrayToInt( recBuf, 12);
        
        /**
         * Should save the epoch. This can be used to determine the
         * time the mapping will be deleted. 
         **/
        log("Seconds since Start of Epoch: " + recEpoch);
        log("Returned Mapped Port Lifetime: " + recLifetime);
        
        if ( recCode != 0 )
            throw( new Exception( "An error occured while getting a port mapping: " + recCode ) );
        if ( recOP != ( NATOp + 128) )
            throw( new Exception( "Received the incorrect port type: " + recOP) );
        if ( lifetime != recLifetime )
            log("Received different port life time!");

        return recPubPort;
    
public java.net.DatagramPacketsendNATMsg(java.net.InetAddress dstInet, java.net.DatagramPacket dstPkt, byte[] recBuf)
Send a request and wait for reply This class should be threaded!!! This sends to the default NATPMP_PORT.

param
dstInet destination address (should be the private NAT address)
param
dstPkt packet to send
param
recBuf byte buffer big enough to hold received

        int retryInterval = NATMAP_INIT_RETRY;
        boolean recRep = false;

        DatagramSocket skt = new DatagramSocket();
        skt.connect( dstInet, NATMAP_PORT );
        skt.setSoTimeout( NATMAP_INIT_RETRY );
        skt.send(dstPkt);       // how do we know we hit something?
              
        DatagramPacket recPkt = new DatagramPacket(recBuf, recBuf.length);
        
        // We have several tries at this (like 3) 
        while ( !recRep && (retryInterval < NATMAP_MAX_RETRY) ) {
            try {
                skt.receive(recPkt);
                recRep = true;        
            } catch (SocketTimeoutException ste) {
                //log("Timed Out!");
                //log( ste.getMessage() );
                // sleep before trying again
                // this.sleep(retryInterval);
                Thread.sleep(retryInterval);        // not sleeping?!?
                // increase retry interval
                retryInterval += (retryInterval * 2);
            }
        } 
        
        if ( !recRep ){
        	
        	throw( new PortUnreachableException());
        }
        
        // check recRep for true!!!
        return recPkt;
    
public byte[]shortToByteArray(short v)
Convert a 16-bit short into a 2 byte array

return
unsigned byte array

        byte b[] = new byte[2];
        b[0] = (byte) ( 0xFF & (v >> 8) );
        b[1] = (byte) ( 0xFF & (v >> 0) );
        
        return b;
    
public static intunsigned16ByteArrayToInt(byte[] b, int offset)
Convert the byte array containing 16-bits to an int starting from the given offset.

param
b The byte array
param
offset The array offset
return
The integer

        int value = 0;
        for (int i = 0; i < 2; i++) {
            int shift = (2 - 1 - i) * 8;
            value += ( (int) b[i + offset] & 0xFF) << shift;
        }
        return value;
    
public static intunsigned32ByteArrayToInt(byte[] b, int offset)
Convert the byte array containing 32-bit to an int starting from the given offset.

param
b The byte array
param
offset The array offset
return
The integer

        int value = 0;
        for (int i = 0; i < 4; i++) {
            int shift = (4 - 1 - i) * 8;
            value += ( (int) b[i + offset] & 0xFF) << shift;
        }
        return value;
    
public static intunsigned8ByteArrayToInt(byte[] b, int offset)
Convert the byte array containing 8-bits to an int starting from the given offset.

param
b The byte array
param
offset The array offset
return
The integer

        return (int) b[offset] & 0xFF;
    
public shortunsignedByteArrayToShort(byte[] buf)

        if (buf.length == 2) {
            int i;
            i = ( ( ( (int) buf[0] & 0xFF) << 8) | ( (int) buf[1] & 0xFF) ); 
            return (short) i;
        }
        return -1;