package ru.habrahabr ;
import java.io.IOException ;
import java.net.InetAddress ;
import java.net.InetSocketAddress ;
import java.net.UnknownHostException ;
import java.nio.ByteBuffer ;
import java.nio.channels.ClosedChannelException ;
import java.nio.channels.SelectionKey ;
import java.nio.channels.Selector ;
import java.nio.channels.ServerSocketChannel ;
import java.nio.channels.SocketChannel ;
import java.nio.channels.spi.SelectorProvider ;
import java.util.Iterator ;
/ **
* A class that implements a simple non-blocking Socks 4 Proxy Server Implementing
* connect command only
*
* @author dgreen
* @date 09/19/2009
*
* /
public class Socks4Proxy implements Runnable {
int bufferSize = 8192 ;
/ **
* Port
* /
int port ;
/ **
* Host
* /
String host ;
/ **
* Additional information clinging to each key {@link SelectionKey}
*
* @author dgreen
* @date 09/19/2009
*
* /
static class Attachment {
/ **
* Buffer for reading, at the time of proxying becomes a buffer for
* entries for key stored in peer
*
* IMPORTANT: When parsing Socks4 header, we assume that the size
* Buffer, larger than normal header size, Mozilla browser
* Firefox, header size is 12 bytes 1 version + 1 command + 2 port +
* 4 ip + 3 id (MOZ) + 1 \ 0
* /
ByteBuffer in ;
/ **
* Buffer for writing, at the time of proxying, is equal to read buffer for
* key stored in peer
* /
ByteBuffer out ;
/ **
* Where are we proxying
* /
SelectionKey peer ;
}
/ **
* the answer is OK or Service is provided
* /
static final byte [ ] OK = new byte [ ] { 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } ;
/ **
* The heart of a non-blocking server, practically does not change from application to
* application, except when using a non-blocking server in
* multithreaded application, and working with keys from other threads, it is necessary
* will add some KeyChangeRequest, but we are in this application without
* needs
* /
@ Override
public void run ( ) {
try {
// Create Selector
Selector selector = SelectorProvider . provider ( ) . openSelector ( ) ;
// Open the server channel
ServerSocketChannel serverChannel = ServerSocketChannel . open ( ) ;
// Remove the lock
serverChannel. configureBlocking ( false ) ;
// Hang on the port
serverChannel. socket ( ) . bind ( new InetSocketAddress ( host, port ) ) ;
// Register in the selector
serverChannel. register ( selector, serverChannel. validOps ( ) ) ;
// The main cycle of the non-blocking server
// This cycle will be the same for almost any non-blocking
// server
while ( selector. select ( ) > - 1 ) {
// Get the keys on which the events occurred at the moment
// last sample
Iterator < SelectionKey > iterator = selector. selectedKeys ( ) . iterator ( ) ;
while ( iterator. hasNext ( ) ) {
SelectionKey key = iterator. next ( ) ;
iterator. remove ( ) ;
if ( key. isValid ( ) ) {
// Handle all possible key events
try {
if ( key. isAcceptable ( ) ) {
// accept connection
accept ( key ) ;
} else if ( key. isConnectable ( ) ) {
// Establish connection
connect ( key ) ;
} else if ( key. isReadable ( ) ) {
// Read the data
read ( key ) ;
} else if ( key. isWritable ( ) ) {
// Write data
write ( key ) ;
}
} catch ( Exception e ) {
e. printStackTrace ( ) ;
close ( key ) ;
}
}
}
}
} catch ( Exception e ) {
e. printStackTrace ( ) ;
throw new IllegalStateException ( e ) ;
}
}
/ **
* The function accepts the connection, registers the key with the action of interest.
* read data (OP_READ)
*
* @param key
* key on which the event occurred
* @throws IOException
* @throws ClosedChannelException
* /
private void accept ( Selection key Key ) throws IOException , ClosedChannelException {
// Accepted
SocketChannel newChannel = ( ( ServerSocketChannel ) key. Channel ( ) ) . accept ( ) ;
// Non-blocking
newChannel. configureBlocking ( false ) ;
// Register in the selector
newChannel. register ( key. selector ( ) , SelectionKey . OP_READ ) ;
}
/ **
* We read data available at the moment. The function is in two states -
* read request header and direct proxying
*
* @param key
* key on which the event occurred
* @throws IOException
* @throws UnknownHostException
* @throws ClosedChannelException
* /
private void read ( SelectionKey key ) throws IOException , UnknownHostException , ClosedChannelException {
SocketChannel channel = ( ( SocketChannel ) key. Channel ( ) ) ;
Attachment attachment = ( ( Attachment ) key. Attachment ( ) ) ;
if ( attachment == null ) {
// Lazily initialize the buffers
key. attach ( attachment = new Attachment ( ) ) ;
attachment. in = ByteBuffer . allocate ( bufferSize ) ;
}
if ( channel. read ( attachment. in ) < 1 ) {
// -1 - break 0 - there is no space in the buffer, this can only be if
// header exceeded buffer size
close ( key ) ;
} else if ( attachment. peer == null ) {
// if there is no second end :) therefore we read the title
readHeader ( key, attachment ) ;
} else {
// well, if we proxify, then we add interest to the second end
// write
attachment. peer . interestOps ( attachment. peer . interestOps ( ) | SelectionKey . OP_WRITE ) ;
// and remove the interest of the first to read, because it has not yet been recorded
// current data, we will not read anything
key. interestOps ( key. interestOps ( ) ^ SelectionKey . OP_READ ) ;
// prepare buffer for writing
attachment. in . flip ( ) ;
}
}
private void readHeader ( SelectionKey key, Attachment attachment ) throws IllegalStateException , IOException ,
UnknownHostException , ClosedChannelException {
byte [ ] ar = attachment. in . array ( ) ;
if ( ar [ attachment. in . position ( ) - 1 ] == 0 ) {
// If the last byte \ 0 is the end of the user ID.
if ( ar [ 0 ] ! = 4 && ar [ 1 ] ! = 1 || attachment. in . position ( ) < 8 ) {
// A simple check on the protocol version and on the validity
// commands
// We support only conect
throw new IllegalStateException ( "Bad Request" ) ;
} else {
// Create a connection
SocketChannel peer = SocketChannel . open ( ) ;
peer. configureBlocking ( false ) ;
// Get the address and port from the packet
byte [ ] addr = new byte [ ] { ar [ 4 ] , ar [ 5 ] , ar [ 6 ] , ar [ 7 ] } ;
int p = ( ( ( 0xFF & ar [ 2 ] ) << 8 ) + ( 0xFF & ar [ 3 ] ) ) ;
// Start to connect
peer. connect ( new InetSocketAddress ( InetAddress . getByAddress ( addr ) , p ) ) ;
// Register in the selector
SelectionKey peerKey = peer. register ( key. selector ( ) , SelectionKey . OP_CONNECT ) ;
// Hear the requesting connection
key. interestOps ( 0 ) ;
// Key exchange :)
attachment. peer = peerKey ;
Attachment peerAttachemtn = new Attachment ( ) ;
peerAttachemtn. peer = key ;
peerKey attach ( peerAttachemtn ) ;
// Clear the buffer with headers
attachment. in . clear ( ) ;
}
}
}
/ **
* Write data from the buffer
*
* @param key
* @throws IOException
* /
private void write ( Selection key key ) throws IOException {
// Close the socket only by writing all the data
SocketChannel channel = ( ( SocketChannel ) key. Channel ( ) ) ;
Attachment attachment = ( ( Attachment ) key. Attachment ( ) ) ;
if ( channel. write ( attachment. out ) == - 1 ) {
close ( key ) ;
} else if ( attachment. out . remaining ( ) == 0 ) {
if ( attachment. peer == null ) {
// Write what was in the buffer and close
close ( key ) ;
} else {
// if everything is written, clear the buffer
attachment. out . clear ( ) ;
// Add a read interest to the second end
attachment. peer . interestOps ( attachment. peer . interestOps ( ) | SelectionKey . OP_READ ) ;
// And we remove the interest on the record
key. interestOps ( key. interestOps ( ) ^ SelectionKey . OP_WRITE ) ;
}
}
}
/ **
* Complete the connection
*
* @param key
* key on which the event occurred
* @throws IOException
* /
private void connect ( SelectionKey key ) throws IOException {
SocketChannel channel = ( ( SocketChannel ) key. Channel ( ) ) ;
Attachment attachment = ( ( Attachment ) key. Attachment ( ) ) ;
// Finish the connection
channel. finishConnect ( ) ;
// Create a buffer and respond OK
attachment. in = ByteBuffer . allocate ( bufferSize ) ;
attachment. in . put ( ok ) . flip ( ) ;
attachment. out = ( ( Attachment ) attachment. peer . attachment ( ) ) . in ;
( ( Attachment ) attachment. Peer . Attachment ( ) ) . out = attachment. in ;
// Put the second end of the flags on the write and read
// as soon as it writes OK, it will switch the second end to read and everything
// will be happy
attachment. peer . interestOps ( SelectionKey . OP_WRITE | SelectionKey . OP_READ ) ;
key. interestOps ( 0 ) ;
}
/ **
* No Comments
*
* @param key
* @throws IOException
* /
private void close ( SelectionKey key ) throws IOException {
key. cancel ( ) ;
key. channel ( ) . close ( ) ;
SelectionKey peerKey = ( ( Attachment ) key. Attachment ( ) ) . peer ;
if ( peerKey ! = null ) {
( ( Attachment ) peerKey. Attachment ( ) ) . peer = null ;
if ( ( peerKey. interestOps ( ) & SelectionKey. OP_WRITE ) == 0 ) {
( ( Attachment ) peerKey. Attachment ( ) ) . out . flip ( ) ;
}
peerKey interestOps ( SelectionKey . OP_WRITE ) ;
}
}
public static void main ( String [ ] args ) {
Socks4Proxy server = new Socks4Proxy ( ) ;
server. host = "127.0.0.1" ;
server. port = 1080 ;
server. run ( ) ;
}
}
Source: https://habr.com/ru/post/70690/
All Articles