package ru habrahabr import java io IOException import java net InetAd

  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
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
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;
/**
* Класс реализующий простой неблокирующий Socks 4 Proxy Сервер Реализуюший
* только команду connect
*
* @author dgreen
* @date 19.09.2009
*
*/
public class Socks4Proxy implements Runnable {
int bufferSize = 8192;
/**
* Порт
*/
int port;
/**
* Хост
*/
String host;
/**
* Дополнительная информация цепляемая к каждому ключу {@link SelectionKey}
*
* @author dgreen
* @date 19.09.2009
*
*/
static class Attachment {
/**
* Буфер для чтения, в момент проксирования становится буфером для
* записи для ключа хранимого в peer
*
* ВАЖНО: При парсинге Socks4 заголовком мы предполагаем что размер
* буфера, больше чем размер нормального заголовка, у браузера Mozilla
* Firefox, размер заголовка равен 12 байт 1 версия + 1 команда + 2 порт +
* 4 ip + 3 id (MOZ) + 1 \0
*/
ByteBuffer in;
/**
* Буфер для записи, в момент проксирования равен буферу для чтения для
* ключа хранимого в peer
*/
ByteBuffer out;
/**
* Куда проксируем
*/
SelectionKey peer;
}
/**
* так выглядит ответ ОК или Сервис предоставлен
*/
static final byte[] OK = new byte[] { 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
/**
* Сердце неблокирующего сервера, практически не меняется от приложения к
* приложению, разве что при использование неблокирующего сервера в
* многопоточном приложение, и работе с ключами из других потоков, надо
* будет добавить некий KeyChangeRequest, но нам в этом приложение это без
* надобности
*/
@Override
public void run() {
try {
// Создаём Selector
Selector selector = SelectorProvider.provider().openSelector();
// Открываем серверный канал
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// Убираем блокировку
serverChannel.configureBlocking(false);
// Вешаемся на порт
serverChannel.socket().bind(new InetSocketAddress(host, port));
// Регистрация в селекторе
serverChannel.register(selector, serverChannel.validOps());
// Основной цикл работу неблокирующего сервер
// Этот цикл будет одинаковым для практически любого неблокирующего
// сервера
while (selector.select() > -1) {
// Получаем ключи на которых произошли события в момент
// последней выборки
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isValid()) {
// Обработка всех возможнных событий ключа
try {
if (key.isAcceptable()) {
// Принимаем соединение
accept(key);
} else if (key.isConnectable()) {
// Устанавливаем соединение
connect(key);
} else if (key.isReadable()) {
// Читаем данные
read(key);
} else if (key.isWritable()) {
// Пишем данные
write(key);
}
} catch (Exception e) {
e.printStackTrace();
close(key);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
}
/**
* Функция принимает соединение, регистрирует ключ с интересуемым действием
* чтение данных (OP_READ)
*
* @param key
* ключ на котором произошло событие
* @throws IOException
* @throws ClosedChannelException
*/
private void accept(SelectionKey key) throws IOException, ClosedChannelException {
// Приняли
SocketChannel newChannel = ((ServerSocketChannel) key.channel()).accept();
// Неблокирующий
newChannel.configureBlocking(false);
// Регистрируем в селекторе
newChannel.register(key.selector(), SelectionKey.OP_READ);
}
/**
* Читаем данные доступные в данный момент. Функция бывает в двух состояних -
* чтение заголовка запроса и непосредственного проксирование
*
* @param key
* ключ на котором произошло событие
* @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) {
// Лениво инициализируем буферы
key.attach(attachment = new Attachment());
attachment.in = ByteBuffer.allocate(bufferSize);
}
if (channel.read(attachment.in) < 1) {
// -1 - разрыв 0 - нету места в буфере, такое может быть только если
// заголовок превысил размер буфера
close(key);
} else if (attachment.peer == null) {
// если нету второго конца :) стало быть мы читаем заголовок
readHeader(key, attachment);
} else {
// ну а если мы проксируем, то добавляем ко второму концу интерес
// записать
attachment.peer.interestOps(attachment.peer.interestOps() | SelectionKey.OP_WRITE);
// а у первого убираем интерес прочитать, т.к пока не записали
// текущие данные, читать ничего не будем
key.interestOps(key.interestOps() ^ SelectionKey.OP_READ);
// готовим буфер для записи
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) {
// Если последний байт \0 это конец ID пользователя.
if (ar[0] != 4 && ar[1] != 1 || attachment.in.position() < 8) {
// Простенькая проверка на версию протокола и на валидность
// команды,
// Мы поддерживаем только conect
throw new IllegalStateException("Bad Request");
} else {
// Создаём соединение
SocketChannel peer = SocketChannel.open();
peer.configureBlocking(false);
// Получаем из пакета адрес и порт
byte[] addr = new byte[] { ar[4], ar[5], ar[6], ar[7] };
int p = (((0xFF & ar[2]) << 8) + (0xFF & ar[3]));
// Начинаем устанавливать соединение
peer.connect(new InetSocketAddress(InetAddress.getByAddress(addr), p));
// Регистрация в селекторе
SelectionKey peerKey = peer.register(key.selector(), SelectionKey.OP_CONNECT);
// Глушим запрашивающее соединение
key.interestOps(0);
// Обмен ключами :)
attachment.peer = peerKey;
Attachment peerAttachemtn = new Attachment();
peerAttachemtn.peer = key;
peerKey.attach(peerAttachemtn);
// Очищаем буфер с заголовками
attachment.in.clear();
}
}
}
/**
* Запись данных из буфера
*
* @param key
* @throws IOException
*/
private void write(SelectionKey key) throws IOException {
// Закрывать сокет надо только записав все данные
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) {
// Дописали что было в буфере и закрываемся
close(key);
} else {
// если всё записано, чистим буфер
attachment.out.clear();
// Добавялем ко второму концу интерес на чтение
attachment.peer.interestOps(attachment.peer.interestOps() | SelectionKey.OP_READ);
// А у своего убираем интерес на запись
key.interestOps(key.interestOps() ^ SelectionKey.OP_WRITE);
}
}
}
/**
* Завершаем соединение
*
* @param key
* ключ на котором произошло событие
* @throws IOException
*/
private void connect(SelectionKey key) throws IOException {
SocketChannel channel = ((SocketChannel) key.channel());
Attachment attachment = ((Attachment) key.attachment());
// Завершаем соединение
channel.finishConnect();
// Создаём буфер и отвечаем 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;
// Ставим второму концу флаги на на запись и на чтение
// как только она запишет OK, переключит второй конец на чтение и все
// будут счастливы
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;
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();
}
}