2012-06-18 12 views
7

Sto provando a scrivere un tunnel HTTP perché vogliamo essere in grado di connettersi a una macchina remota tramite la nostra applicazione web. Pur essendo consapevole dei rischi per la sicurezza, è qualcosa che vorremmo fare. Non è ospitato su Internet, ma su reti private, quindi il rischio è considerato basso.Servlet del tunnel HTTP (Java)

Il requisito di base è consentire allo strumento di debug di Java di connettersi tramite un servlet a una macchina. Abbiamo alcuni clienti che insistono per avere caselle di sviluppo sul lato del firewall e poiché la porta di ritorno sul server di debug di java non è riparata, non possiamo semplicemente chiedere loro di aprire una porta specifica.

Il codice non è ancora perfetto. Ho semplicemente cercato di ottenere qualcosa che comunichi in modo bidirezionale.

Ci sono alcuni componenti. Un server standalone su cui si connette il debug di java in Eclipse. Questo server è configurato per sapere dove si sta dirigendo in base alla porta connessa a. Quindi se viene colpita la porta 1166, è in grado di connettersi a un servlet sulla macchina x.

cioè Eclipse Debugger -> Debug Proxy Server -> Application Servlet -> Application JVM

Finora per i miei sforzi, mi sembrano essere in grado di connettersi, ma i flussi non sono completamente funzionali. Eclipse invia un JDWP-Handshake alla JVM, che dovrebbe rispondere con JDWP-Handshake. Sto scoprendo che quando JDWP-Handshake viene inviato da Eclipse, viene scritto nel Debug Proxy Server e quindi trasmesso sul Servlet, ma sembra che questo venga ignorato nel servlet. I registri che ricevo sono i seguenti:

[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' 

mi chiedo se ho bisogno di cambiare il mio pensiero su questo in modo che i flussi sono suddivisi in più richieste e di una connessione di sessione a base viene utilizzato. Una richiesta diventerebbe un downstream senza fine (cioè una risposta infinita), quindi quando il client invia al servlet, creerebbe ogni volta una nuova richiesta. È questa la chiave per far funzionare questo?

Di seguito è riportato il codice per il Debug Proxy Server che può essere eseguito standalone oppure l'ho temporaneamente configurato per l'esecuzione come servlet su un server Jetty per una rapida verifica del tempo di rotazione. (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()); 
    } 
    } 
} 

La successiva sezione di codice è la configurazione molla (/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> 

Il bean di configurazione (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; 
    } 
} 

Il flusso in ingresso di flusso di uscita copiatrice (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); 
    } 
    } 
} 

Questa sezione è la 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); 
     } 
    } 
    } 
} 

La configurazione contenitore applicazione (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> 

Infine, il mio pom.xml come sto costruendo con Maven.

<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> 

corro assistente Jetty con il seguente Maven rivolge

jetty:stop clean install jetty:run-war 

Speranza trovate questo piccolo progetto interessante! Non vedo l'ora di sentire le vostre idee e commenti.

Grazie, Stuart

+0

Non è possibile specificare la porta di debug remota sul Jetty? Ho pensato che puoi farlo su qualsiasi server ... – Thihara

+0

Puoi, ma sto solo testando su Jetty durante lo sviluppo di questo servlet tunnel. L'implementazione effettiva andrà su un server WebLogic dietro un firewall. Ciò significa che non riesco a connettermi alla porta poiché il firewall blocca la porta e anche perché la porta di ritorno dalla JVM è casuale e non può essere configurata, quindi anche se apriamo la porta che va dal client di debug, non possiamo dire quale porta la JVM utilizzerà quando tornerà al client. – mrswadge

+0

+1 per il problema interessante, non sapevo nulla della porta casuale JVM ... – Thihara

risposta

0

SSH tunnel - la mia scelta http://en.wikipedia.org/wiki/Tunneling_protocol

ssh -L [bind_address:]port:sshserverhostname:targetmachinehostname port 

-L Specifica che la porta data sull'host locale (client) deve essere inoltrato al dato host e la porta sul lato remoto. Funziona allocando un socket per ascoltare la porta sul lato locale , facoltativamente associato all'indirizzo bind specificato. Ogni volta che viene effettuata una connessione a questa porta , la connessione viene inoltrata tramite il canale protetto e viene stabilita una connessione per ospitare l'host della porta dalla macchina remota. I port forwarding possono anche essere specificati nel file di configurazione . Gli indirizzi IPv6 possono essere specificati racchiudendo l'indirizzo tra parentesi quadre. Solo il superutente può inoltrare porte privilegiate. Per impostazione predefinita, la porta locale è vincolata in base allo standard con l'impostazione GatewayPorts. Tuttavia, un bind_address esplicito può essere utilizzato per associare la connessione a un indirizzo specifico. Bind_indirizzo `localhost'' indicates that the listen- ing port be bound for local use only, while an empty address or * indica che la porta deve essere disponibile da tutte le interfacce.

Problemi correlati