Java / Network - Инкапсуляция любого протокола в http
Я пытаюсь инкапсулировать любой сетевой протокол в HTTP (я знаю, что он может работать не для всех протоколов, но это своего рода POC, который, по моему мнению, должен, наконец, работать для HTTP и HTTPS, я хотел бы заставить его работать для SSH также). Код написан на Java, основан на Apache HTTPComponents, и я назвал его JProxy. JProxy можно запустить в режиме клиента или в режиме сервера. Его цель - быть полностью прозрачным с точки зрения клиента и сервера.
Вот концепция:
- клиент (независимо от клиента: веб-браузер, клиент ssh и т. д.) отправляет сетевые пакеты в JProxy, работающий в режиме клиента
- JProxy инкапсулирует сетевые пакеты, полученные в запрос HTTP POST (и, конечно же, тело - это сетевые пакеты, полученные от клиента), и запрос POST отправляется JProxy, работающему в режиме сервера
- JProxy в режиме sever получает запрос HTTP POST и извлекает тело. Затем тело отправляется конечной цели в режиме сокета.
- Конечная цель - сервер, отвечающий отправкой сетевых пакетов обратно на JProxy сервер. Эти пакеты инкапсулируются в ответ HTTP и отправляются обратно клиенту JProxy.
- JProxy-клиент отправляет HTTP-тело ответа исходному клиенту в режиме сокета.
Клиент JPRoxy может быть настроен для доступа к серверу JProxy через HTTP-прокси, если это необходимо.
Итак, мы имеем:
Client <-> JProxy client <-> JProxy server <-> Target
или же
Client <-> JProxy client <-> HTTP Proxy <-> JProxy server <-> Target
И протоколы:
Client <- protocol X -> JProxy client <- http -> JProxy Server <- protocol X -> Target
Все работает нормально, когда оригинальный клиент - это веб-браузер, работающий на сайтах HTTP. Но это больше не работает, когда это HTTPS (кажется, что соединение потеряно или прервано).
Вот основная часть кода (я удаляю не относящиеся к делу части).
JProxy в режиме клиента (прослушивание порта и отправка в HTTP всех полученных пакетов):
...
ServerSocket server = new ServerSocket(port, 100, InetAddress.getByName(host));
...
Socket client = server.accept();
...
client.setTcpNoDelay(true);
Thread t = new Thread(new HttpClient(client));
t.start();
...
public class HttpClient implements Runnable {
private Socket socket;
public HttpClient(Socket sock){
super();
socket = sock;
}
public void run() {
// Prepare HTTP client to communicate with JProxy server
CloseableHttpClient httpclient = creat_httpclient();
while(!socket.isClosed()) {
try {
// PART I
// Retrieve Data received from the sock server :
socket.setSoTimeout(300);
ByteArrayOutputStream full_buffer = new ByteArrayOutputStream();
DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
byte[] buffer = new byte[4096];
int count=0;
try {
while (( count = in.read(buffer)) > 0) {
full_buffer.write(buffer,0,count);
}
} catch(java.net.SocketTimeoutException ex) {
}
// Send message to JProxy server
if(full_buffer.size() > 0) {
if(JProxy.packet_dump)
FileUtils.appendBytesArrayToFile("JProxy.dump", false, full_buffer.toByteArray());
// PART II
// Scramble data to send to JProxy Server
/*
ByteArrayInputStream toSwab = new ByteArrayInputStream(full_buffer.toByteArray());
full_buffer.flush();
full_buffer.write(Swabber.runSwab(toSwab));
System.out.println("DEBUG SWAB IN = " + new String(full_buffer.toByteArray()));
*/
// PART III
// Send data to JProxy HTTP Server in HTTP
// Post to the HTTP JProxy Server
HttpPost httpPost = new HttpPost("http://" + JProxy.remotHost + ":" + JProxy.remotePort);
HttpEntity bienEntity = EntityBuilder.create().setBinary(full_buffer.toByteArray()).build();
httpPost.setEntity(bienEntity);
full_buffer.close();
CloseableHttpResponse response = httpclient.execute(httpPost);
// PART III
// Get response from JProxy server :
HttpEntity entity = response.getEntity();
byte[] data;
if (entity == null) {
data = new byte [0];
} else {
data = EntityUtils.toByteArray(entity);
}
EntityUtils.consume(response.getEntity());
response.close();
if(JProxy.packet_dump)
FileUtils.appendBytesArrayToFile("JProxy.dump", true, data);
// PART IV
// Send response back to the original client
BufferedOutputStream back = new BufferedOutputStream(socket.getOutputStream());
back.write(data);
back.flush();
back.close();
}
} catch (Exception ex ) {
ex.printStackTrace();
}
}
// Socket has been closed by the original client
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
static public CloseableHttpClient creat_httpclient() {
CloseableHttpClient httpclient = null;
if(JProxy.proxy_host != null && JProxy.proxy_host.length() > 0) {
Lookup<AuthSchemeProvider> authProviders = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.build();
RequestConfig config = RequestConfig.custom()
.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.KERBEROS, AuthSchemes.SPNEGO))
.build();
HttpHost proxy = new HttpHost(JProxy.proxy_host, JProxy.proxy_port);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
if(JProxy.proxy_login != null && JProxy.proxy_login.length() > 0) {
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(JProxy.proxy_login, JProxy.proxy_password);
provider.setCredentials(AuthScope.ANY, credentials);
httpclient = HttpClients.custom()
.setDefaultAuthSchemeRegistry(authProviders)
.setDefaultRequestConfig(config)
.setDefaultCredentialsProvider(provider)
.setRoutePlanner(routePlanner)
.build();
} else {
httpclient = HttpClients.custom()
.setDefaultAuthSchemeRegistry(authProviders)
.setDefaultRequestConfig(config)
.setRoutePlanner(routePlanner)
.build();
}
} else {
httpclient = HttpClients.createDefault();
}
return httpclient;
}
}
JProxy в режиме сервера (прослушивание как веб-сервер и отправка пакетов на целевой сервер):
public class HttpdServer {
public HttpdServer(int port) {
try {
// Run listener on jproxy_port
final IOReactorConfig config = IOReactorConfig.custom()
.setSoTimeout(15000)
.setTcpNoDelay(true)
.build();
final HttpServer server = ServerBootstrap.bootstrap()
.setListenerPort(port)
.setServerInfo("JProxy/0.1")
.setIOReactorConfig(config)
.setExceptionLogger(ExceptionLogger.STD_ERR)
.registerHandler("*", new SocketClient())
.create();
server.start();
System.out.println("JProxy HTTP Server started on port " + port + " and targeting " + JProxy.remotHost + ":" + JProxy.remotePort);
server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
server.shutdown(5, TimeUnit.SECONDS);
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public class SocketClient implements HttpAsyncRequestHandler<HttpRequest> {
public SocketClient() {
super();
}
public void handle(final HttpRequest request, final HttpAsyncExchange httpexchange, final HttpContext httpContext) throws HttpException, IOException {
final HttpResponse response = httpexchange.getResponse();
// Gather what has been received by the HTTP Server
HttpEntity entity = null;
if (request instanceof HttpEntityEnclosingRequest)
entity = ((HttpEntityEnclosingRequest)request).getEntity();
byte[] data;
if (entity == null) {
data = new byte [0];
} else {
data = EntityUtils.toByteArray(entity);
}
if(JProxy.packet_dump)
FileUtils.appendBytesArrayToFile("JProxy.dump", false, data);
Socket socket = new Socket(JProxy.remotHost,JProxy.remotePort);
socket.setSoTimeout(300);
// Run a socket client to target the next destination
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(data);
bos.flush();
// Get response from final target
ByteArrayOutputStream full_buffer = new ByteArrayOutputStream();
DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
byte[] buffer = new byte[4096];
int count=0;
try {
while (( count = in.read(buffer)) > 0) {
full_buffer.write(buffer,0,count);
}
} catch(java.net.SocketTimeoutException ex) {
//System.out.println("DEBUG BUFFER TO SENT BACK = " + new String(full_buffer.toByteArray()));
}
bos.close();
socket.close();
if(JProxy.packet_dump)
FileUtils.appendBytesArrayToFile("JProxy.dump", true, full_buffer.toByteArray());
// Send response to JProxy client
HttpEntity binaryEntity = EntityBuilder.create().setBinary(full_buffer.toByteArray()).build();
response.setEntity(binaryEntity);
full_buffer.close();
httpexchange.submitResponse(new BasicAsyncResponseProducer(response));
EntityUtils.consume(response.getEntity());
}
public HttpAsyncRequestConsumer<HttpRequest> processRequest(HttpRequest arg0, HttpContext arg1) throws HttpException, IOException {
return new BasicAsyncRequestConsumer();
}
}
JProxy можно запустить в командной строке, например, на стороне клиента:
java -jar JProxy.jar -client 127.0.0.1:3128 -target X.X.X.X:3129
и на стороне сервера:
java -jar JProxy.jar -server 3129 -target Y.Y.Y.Y:Z
Например, давайте представим следующий сценарий:
локальный рабочий стол ПК с Chrome
удаленный прокси-сервер HTTP на сервере IP XXXX, прослушивающий порт 8118
1 / Запустите локальную JProxy:
java -jar JProxy.jar -client 127.0.0.1:3128 -target X.X.X.X:3128
2 / Запустите JProxy на удаленном сервере:
java -jar JProxy.jar -server 3128 -target 127.0.0.1:8118
(учитывая, что порт 3128 доступен через Интернет, а порт 8118 локально открыт с HTTP-прокси, таким как Privoxy или Squid)
3 / Настройте Chrome на использование HTTP-прокси с IP: 127.0.0.1 и портом 3128 и просматривайте веб-страницы
Это прекрасно работает при просмотре HTTP сайта, но с HTTPS это не работает, и я не знаю почему. Что я делаю не так? На уровне JProxy ошибки нет вообще, но в Chrome у меня есть ошибка: ERR_CONNECTION_ABORTED.
Я прочитал документацию по HTTPS, и кажется, что это туннель SSL, используемый для обмена HTTP-запросами / ответами. Так что, может быть, есть что-то делать по этому поводу, но я не знаю, что делать.
Я попытался проверить связь SSH, и вот журнал, который я собираю:
OpenSSH_7.9p1, LibreSSL 2.7.3
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 48: Applying options for *
debug1: Connecting to localhost port 2222.
debug1: Connection established.
debug1: identity file /Users/fabrice/.ssh/id_rsa type -1
debug1: identity file /Users/fabrice/.ssh/id_rsa-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_dsa type -1
debug1: identity file /Users/fabrice/.ssh/id_dsa-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_ecdsa type -1
debug1: identity file /Users/fabrice/.ssh/id_ecdsa-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_ed25519 type -1
debug1: identity file /Users/fabrice/.ssh/id_ed25519-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_xmss type -1
debug1: identity file /Users/fabrice/.ssh/id_xmss-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_7.9
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.9
debug1: match: OpenSSH_7.9 pat OpenSSH* compat 0x04000000
debug1: Authenticating to localhost:2222 as 'fabrice'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
ssh_dispatch_run_fatal: Connection to 127.0.0.1 port 2222: Broken pipe
Глядя здесь: протокол связи SSH кажется, что сервер sshd отправляет несколько пакетов в качестве ответа клиенту, и мой код сделан для получения только одного ответа.
Большое спасибо за вашу помощь.
PS: полный исходный код и скомпилированный JAR можно скачать здесь: https://mega.nz/#F!mUF0QQJA!JVBDIc44scLRq7dKo5u4GA