mrs*_*dge 6 java sockets servlets tunnel http-tunneling
I am trying to write an HTTP tunnel as we want to be able to connect to a remote machine through our web application. While I am aware of the security risks involved, it's something we would like to do. It's not hosted on the internet, but on private networks, so the risk is considered low.
The basic requirement is to allow the Java Debugging tool to connect through a servlet to a machine. We have some clients that insist on having development boxes their side of the firewall and as the return port on the java debug server is not fixed, we can't simply ask them to open up a specific port.
The code isn't perfect yet. I have simply been trying to get something communicating in a bi-directional manner.
There are a few components. A standalone server which the java debug in Eclipse connects to. This server is configured to know where it's heading based on the port connected to. So if port 1166 is hit, it knows to connect to a servlet on machine x.
i.e. Eclipse Debugger --> Debug Proxy Server --> Application Servlet --> Application JVM
So far for my efforts, I appear to be able to connect, but the streams are not fully functional. Eclipse sends a JDWP-Handshake to the JVM, which is supposed to reply with JDWP-Handshake back. I'm finding that when JDWP-Handshake is sent by Eclipse, it is written to the Debug Proxy Server and then relayed onto the Servlet, but it appears this is then being ignored in the servlet. The logs I am receiving are the following:
[INFO] Started Jetty Server
2012-06-18 10:00:53,356 INFO ProxySocket - Connection received, forwarding to tidevwls03:1166 via http://localhost:8080/tunnel/debug-proxy
2012-06-18 10:00:53,361 INFO ProxySocket - Connected to http://localhost:8080/tunnel/debug-proxy
2012-06-18 10:00:53,603 INFO ProxyServlet - Received incoming http connection, attempting to forward to endpoint tidevwls03:1166
2012-06-18 10:00:53,604 INFO ProxyServlet - Connecting to endpoint tidevwls03:1166
2012-06-18 10:00:53,613 INFO StreamProxy - [endpoint-read -> http-write ] beginning proxy transport.
2012-06-18 10:00:53,613 INFO StreamProxy - [http-read -> endpoint-write] beginning proxy transport.
2012-06-18 10:00:53,619 INFO ProxySocket - Response Header: HTTP/1.1 200 OK
2012-06-18 10:00:53,619 INFO ProxySocket - Response Header: Content-Length: 0
2012-06-18 10:00:53,623 INFO ProxySocket - Response Header: Server: Jetty(6.1.22)
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] beginning proxy transport.
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'J'
2012-06-18 10:00:53,624 INFO StreamProxy - [servlet-read -> client-write ] beginning proxy transport.
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'D'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'W'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'P'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] '-'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'H'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'a'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'n'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'd'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 's'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'h'
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'a'
2012-06-18 10:00:53,625 INFO StreamProxy - [client-read -> servlet-write ] 'k'
2012-06-18 10:00:53,625 INFO StreamProxy - [client-read -> servlet-write ] 'e'
Run Code Online (Sandbox Code Playgroud)
I'm wondering if I need to change my thinking on this so that the streams are broken up into multiple requests and a session based connection is used. One request would become a never ending downstream (i.e. infinite response), then when the client sends to the servlet, it would create a new request each time. Is this the key to getting this working?
Below is the code for the Debug Proxy Server that can either run standalone or I have temporarily configured it to run as a servlet on a Jetty server for quick testing turn around time. (ProxySocket.java)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.List;
import javax.net.SocketFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ProxySocket extends HttpServlet {
private static final Logger logger = Logger.getLogger( ProxySocket.class );
private static final ApplicationContext springContext = new ClassPathXmlApplicationContext( "env-spring/applicationContext*.xml" );
@Override
public void init() throws ServletException {
List<HttpDebugConfig> configs = ( List<HttpDebugConfig> ) springContext.getBean( "DebugProxyHosts" );
for ( HttpDebugConfig config : configs ) {
ProxyServer proxyServer = new ProxyServer( config );
proxyServer.start();
}
}
class ProxyServer extends Thread {
private HttpDebugConfig config;
public ProxyServer( HttpDebugConfig config ) {
this.config = config;
}
public void run() {
ServerSocket ss = null;
StreamProxy streamToTunnel = null;
StreamProxy streamToClient = null;
try {
ss = new ServerSocket( config.getLocalPort() );
Socket inbound = null;
Socket outbound = null;
logger.info( String.format( "Listening for connections on port %d. Proxying to %s:%d", config.getLocalPort(), config.getRemoteHost(), config.getRemotePort() ) );
while ( ( inbound = ss.accept() ) != null ) {
try {
logger.info( String.format( "Connection received, forwarding to %s:%d via %s", config.getRemoteHost(), config.getRemotePort(), config.getProxyUrl() ) );
URL proxy = new URL( config.getProxyUrl() );
outbound = SocketFactory.getDefault().createSocket( proxy.getHost(), proxy.getPort() );
logger.info( String.format( "Connected to %s", config.getProxyUrl() ) );
OutputStream out = outbound.getOutputStream();
BufferedReader in = new BufferedReader( new InputStreamReader( outbound.getInputStream() ) );
writeLine( out, String.format( "POST %s HTTP/1.1", config.getProxyUrl() ) );
writeLine( out, String.format( "Host: http://%s:%s", proxy.getHost(), proxy.getPort() ) );
writeLine( out, "Connection: keep-alive" );
writeLine( out, String.format( "tunnel_host: %s", config.getRemoteHost() ) );
writeLine( out, String.format( "tunnel_port: %s", String.valueOf( config.getRemotePort() ) ) );
writeLine( out, "" );
// read the http response and then we can start tunnelling.
for ( String line = ""; StringUtils.isNotBlank( line = in.readLine() ); ) {
logger.info( String.format( "Response Header: %s", line ) );
}
streamToTunnel = new StreamProxy( "[client-read -> servlet-write ]", inbound.getInputStream(), outbound.getOutputStream() );
streamToClient = new StreamProxy( "[servlet-read -> client-write ]", outbound.getInputStream(), inbound.getOutputStream() );
streamToTunnel.start();
streamToClient.start();
while ( streamToClient.isAlive() || streamToTunnel.isAlive() ) {
try { Thread.sleep( 100 ); } catch ( InterruptedException e ) { }
}
logger.info( String.format( "Shutting down socket-to-%s.", config.getProxyUrl() ) );
} finally {
IOUtils.closeQuietly( inbound );
IOUtils.closeQuietly( outbound );
}
}
} catch ( IOException e ) {
logger.error( String.format( "No longer listening for connections on port %d. Proxying to %s:%d", config.getLocalPort(), config.getRemoteHost(), config.getRemotePort() ), e );
} finally {
if ( ss != null ) {
try { ss.close(); } catch ( Exception e ) { }
}
}
}
private void writeLine( OutputStream out, String msg ) throws IOException {
out.write( String.format( "%s\n", StringUtils.defaultString( msg ) ).getBytes() );
}
}
}
Run Code Online (Sandbox Code Playgroud)
The next section of code is the spring configuration (/env-spring/applicationContext.xml).
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
">
<util:list id="DebugProxyHosts" list-class="java.util.ArrayList">
<bean class="HttpDebugConfig">
<property name="localPort" value="1166" />
<property name="proxyUrl" value="http://localhost:8080/tunnel/debug-proxy" />
<property name="remoteHost" value="tidevwls03" />
<property name="remotePort" value="1166" />
</bean>
</util:list>
</beans>
Run Code Online (Sandbox Code Playgroud)
The configuration bean (HttpDebugConfig.java).
public class HttpDebugConfig {
private int localPort;
private String remoteHost;
private int remotePort;
private String proxyUrl;
public int getLocalPort() {
return localPort;
}
public void setLocalPort( int localPort ) {
this.localPort = localPort;
}
public String getRemoteHost() {
return remoteHost;
}
public void setRemoteHost( String remoteHost ) {
this.remoteHost = remoteHost;
}
public int getRemotePort() {
return remotePort;
}
public void setRemotePort( int remotePort ) {
this.remotePort = remotePort;
}
public String getProxyUrl() {
return proxyUrl;
}
public void setProxyUrl( String proxyUrl ) {
this.proxyUrl = proxyUrl;
}
}
Run Code Online (Sandbox Code Playgroud)
The input stream to output stream copier (StreamProxy.java)
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
public class StreamProxy extends Thread {
private static final Logger logger = Logger.getLogger( StreamProxy.class );
private InputStream in;
private OutputStream out;
private boolean kill = false;
public StreamProxy( String name, InputStream in, OutputStream out ) {
this.in = in;
this.out = out;
setName( name );
}
@Override
public void interrupt() {
this.kill = true;
super.interrupt();
}
@Override
public void run() {
try {
logger.info( String.format( "%s beginning proxy transport.", getName() ) );
do {
int n = 0;
while ( -1 != ( n = in.read() ) ) {
logger.info( getName() + " '" + ( char ) n + "'" );
out.write( n );
// out.flush();
}
try { Thread.sleep( 1 ); } catch ( Exception e ) { }
} while ( ! kill );
logger.info( String.format( "%s completed proxy transport.", getName() ) );
} catch ( IOException e ) {
logger.error( String.format( "%s Failed to copy from input stream to output stream. Aborting thread.", getName() ), e );
kill = true;
} finally {
IOUtils.closeQuietly( in );
IOUtils.closeQuietly( out );
}
}
}
Run Code Online (Sandbox Code Playgroud)
This section is the Tunnel Servlet (ProxyServlet.java)
import java.io.IOException;
import java.net.Socket;
import javax.net.SocketFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.Logger;
public class ProxyServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger( ProxyServlet.class );
private static final long serialVersionUID = -686421490573011755L;
@Override
protected void service( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
new Runner( request, response ).start();
}
class Runner extends Thread {
private HttpServletRequest request;
private HttpServletResponse response;
public Runner( HttpServletRequest request, HttpServletResponse response ) {
this.request = request;
this.response = response;
}
@Override
public void run() {
Socket endpoint = null;
StreamProxy streamToHttp = null;
StreamProxy streamToEndpoint = null;
String host = StringUtils.defaultIfEmpty( request.getHeader( "tunnel_host" ), "localhost" );
int port = NumberUtils.toInt( request.getHeader( "tunnel_port" ), 8000 );
try {
logger.info( String.format( "Received incoming http connection, attempting to forward to endpoint %s:%d", host, port ) );
logger.info( String.format( "Connecting to endpoint %s:%d", host, port ) );
endpoint = SocketFactory.getDefault().createSocket( host, port );
streamToHttp = new StreamProxy( "[endpoint-read -> http-write ]", endpoint.getInputStream(), response.getOutputStream() );
streamToEndpoint = new StreamProxy( "[http-read -> endpoint-write]", request.getInputStream(), endpoint.getOutputStream() );
streamToHttp.start();
streamToEndpoint.start();
while ( streamToEndpoint.isAlive() || streamToHttp.isAlive() ) {
try { Thread.sleep( 100 ); } catch ( InterruptedException e ) { }
}
logger.info( String.format( "Safely shut down servlet-to-%s:%d proxy.", host, port ) );
} catch ( IOException e ) {
logger.error( String.format( "Shutting down servlet-to-%s:%d proxy.", host, port ), e );
} finally {
if ( streamToHttp != null ) {
streamToHttp.interrupt();
}
if ( streamToEndpoint != null ) {
streamToEndpoint.interrupt();
}
IOUtils.closeQuietly( endpoint );
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
应用程序容器配置(web.xml)
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>tunnel</display-name>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:env-spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>Debug Proxy</servlet-name>
<servlet-class>ProxyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Debug Proxy</servlet-name>
<url-pattern>/debug-proxy</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Debug Socket</servlet-name>
<servlet-class>ProxySocket</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Debug Socket</servlet-name>
<url-pattern>/debug-socket</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Run Code Online (Sandbox Code Playgroud)
最后,我正在用maven构建我的pom.xml.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tunnel</groupId>
<artifactId>tunnel</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<version.spring>3.1.1.RELEASE</version.spring>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.22</version>
<configuration>
<webApp>${project.build.directory}/${project.build.finalName}.${project.packaging}</webApp>
<stopPort>9966</stopPort>
<stopKey>foo</stopKey>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.0.1</version>
<exclusions>
<exclusion>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.2</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<!-- Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${version.spring}</version>
</dependency>
</dependencies>
</project>
Run Code Online (Sandbox Code Playgroud)
我使用以下Maven目标运行Jetty服务器
jetty:stop clean install jetty:run-war
Run Code Online (Sandbox Code Playgroud)
希望你发现这个小项目很有趣!我期待听到您的想法和意见.
谢谢,斯图尔特
SSH 隧道 - 我的选择http://en.wikipedia.org/wiki/Tunneling_protocol
ssh -L [bind_address:]port:sshserverhostname:targetmachinehostname port
Run Code Online (Sandbox Code Playgroud)
-L 指定本地(客户端)主机上的给定端口将转发到远程端的给定主机和端口。这是通过分配一个套接字来侦听本地端的端口来实现的,可以选择绑定到指定的bind_address。每当与此端口建立连接时,都会通过安全通道转发连接,并从远程计算机建立与主机端口 hostport 的连接。端口转发也可以在配置文件中指定。IPv6 地址可以通过将地址括在方括号中来指定。只有超级用户才能转发特权端口。默认情况下,本地端口根据 GatewayPorts 设置进行绑定。然而,可以使用显式的bind_address 将连接绑定到特定地址。*'的绑定地址`localhost'' indicates that the listen-
ing port be bound for local use only, while an empty address or
表示该端口应可从所有接口使用。
归档时间: |
|
查看次数: |
5760 次 |
最近记录: |