Permalink
Browse files

cleaning up, new readme

  • Loading branch information...
1 parent 8b08c02 commit 9faa31dd23f7428656f5c128dd92ed9f191dc720 @progrium committed May 9, 2010
Showing with 105 additions and 148 deletions.
  1. +23 −20 README
  2. +80 −23 bin/localtunnel
  3. +0 −69 client.py
  4. +0 −35 lib/gateway.rb
  5. +2 −1 server.py
View
@@ -1,27 +1,30 @@
-LocalTunnel
-===========
+localtunnel -- instant reverse tunnel for local web servers
-This is the vision:
+Install: sudo gem install localtunnel
- $ localtunnel 8080
- http://d8w72a.localtunnel.com is now forwarding to your local port 8080...
+Usage: localtunnel [options] <localport>
+ -k, --key FILE upload a public key for authentication
-Wouldn't that be magical? Compare to http://novas007.livejournal.com/42971.html
+localtunnel is a client to a free and open source reverse tunneling service
+made specifically for web traffic. It's intended to be used to temporarily
+expose local web servers to the greater Internet for debugging, unit tests,
+demos, etc.
-Currently the focus is the forwarding plumbing. Works, but not so great with many concurrent requests.
+Using localtunnel is comparable to using SSH reverse/remote port forwarding on
+a remote host that has GatewayPorts enabled, but without all the configuration
+or the need of a host. The localtunnel command works with a server component
+that is running on localtunnel.com, which is provided as a free service.
-It should probably be modeled more after this:
-http://twistedmatrix.com/trac/browser/tags/releases/twisted-8.1.0/twisted/conch/ssh/forwarding.py
+You typically run it like this:
-To try it out:
+ $ localtunnel 8080
+
+However, if you haven't run the command before, you'll need to upload a public
+key to authenticate. You do this like so:
-1) Start the server.
- python server.py
-
-2) Point the client at a local port serving HTTP.
- python client.py 8080
-
-3) Use curl to request a page from your HTTP through the server.
- curl http://localhost:8999/
-
-Browsers work, but it will choke trying to serve up all your assets.
+ $ localtunnel -k ~/.ssh/id_rsa.pub 8080
+
+After that, you shouldn't have to use -k again.
+
+The tunnel remains open for as long as the command is running. The tunnel is
+closed if the command exists.
View
@@ -1,4 +1,27 @@
#!/usr/bin/env ruby
+# Copyright (c) 2010 Jeff Lindsay
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
require 'rubygems'
require 'net/ssh'
require 'net/ssh/gateway'
@@ -7,24 +30,6 @@ require 'uri'
require 'optparse'
require 'json'
-require 'lib/gateway'
-
-key = nil
-options = OptionParser.new do |o|
- o.banner = "Usage: localtunnel [options] <localport>"
- o.on("-k", "--key FILE", "upload a public key for authentication") do |k|
- key = File.exist?(k.to_s) ? File.open(k).read : nil
- end
- o.on('-h', "--help", "show this help") { puts o; exit }
-end
-
-args = options.parse!
-local_port = args[0]
-unless local_port
- puts options
- exit
-end
-
def register_tunnel(key=nil)
url = URI.parse("http://open.localtunnel.com/")
if key
@@ -49,14 +54,66 @@ def start_tunnel(port, tunnel)
exit
end
end
-end
-
-begin
- start_tunnel(local_port, register_tunnel(key))
rescue Net::SSH::AuthenticationFailed
possible_key = Dir[File.expand_path('~/.ssh/*.pub')].first
puts " Failed to authenticate. If this is your first tunnel, you need to"
puts " upload a public key using the -k option. Try this:\n\n"
- puts " localtunnel -k #{possible_key ? possible_key : '~/path/to/key'} #{local_port}"
+ puts " localtunnel -k #{possible_key ? possible_key : '~/path/to/key'} #{port}"
+ exit
+end
+
+# http://groups.google.com/group/capistrano/browse_thread/thread/455c0c8a6faa9cc8?pli=1
+class Net::SSH::Gateway
+ # Opens a SSH tunnel from a port on a remote host to a given host and port
+ # on the local side
+ # (equivalent to openssh -R parameter)
+ def open_remote(port, host, remote_port, remote_host = "127.0.0.1")
+ ensure_open!
+
+ @session_mutex.synchronize do
+ @session.forward.remote(port, host, remote_port, remote_host)
+ end
+
+ if block_given?
+ begin
+ yield [remote_port, remote_host]
+ ensure
+ close_remote(remote_port, remote_host)
+ end
+ else
+ return [remote_port, remote_host]
+ end
+ rescue Errno::EADDRINUSE
+ retry
+ end
+
+ # Cancels port-forwarding over an open port that was previously opened via
+ # #open_remote.
+ def close_remote(port, host = "127.0.0.1")
+ ensure_open!
+
+ @session_mutex.synchronize do
+ @session.forward.cancel_remote(port, host)
+ end
+ end
+end
+
+### Main
+
+key = nil
+options = OptionParser.new do |o|
+ o.banner = "Usage: localtunnel [options] <localport>"
+ o.on("-k", "--key FILE", "upload a public key for authentication") do |k|
+ key = File.exist?(k.to_s) ? File.open(k).read : nil
+ end
+ o.on('-h', "--help", "show this help") { puts o; exit }
+end
+
+args = options.parse!
+local_port = args[0]
+unless local_port
+ puts options
+ exit
end
+start_tunnel(local_port, register_tunnel(key))
View
@@ -1,69 +0,0 @@
-from twisted.internet import protocol, reactor, defer, task
-from twisted.internet.protocol import Protocol, ClientFactory
-from twisted.protocols import basic
-from twisted.python import log
-from twisted.web import http
-import sys
-
-class OutgoingChannel(protocol.Protocol):
- def __init__(self, reqId, factory):
- self.reqId = reqId
- self.factory = factory
- self.tunnel = factory.tunnel
-
- def dataReceived(self, data):
- self.tunnel.transport.write(data)
-
- def connectionMade(self):
- self.tunnel.requests[self.reqId] = self
- for l in self.tunnel.buffers[self.reqId]:
- self.transport.write(l + "\r\n")
-
-class OutgoingFactory(ClientFactory):
- def __init__(self, reqId, tunnel):
- self.reqId = reqId
- self.tunnel = tunnel
-
- def buildProtocol(self, addr):
- self.p = OutgoingChannel(self.reqId, self)
- return self.p
-
-class TunnelClientProtocol(basic.LineReceiver):
- requests = {}
- buffers = {}
-
- def lineReceived(self, line):
- reqId, line = line.split('|',1)
- if not reqId in self.requests:
- if line == '^^CONNECT--':
- self.buffers[reqId] = []
- reactor.connectTCP("localhost", self.factory.port, OutgoingFactory(reqId, self))
- else:
- self.buffers[reqId].append(line)
- else:
- if line == '^^CLOSE--':
- if reqId in self.requests and self.requests[reqId]:
- self.requests[reqId].transport.loseConnection()
- self.requests[reqId] = None
- else:
- self.requests[reqId].transport.write(line + "\r\n")
-
- def rawDataReceived(self, data):
- print "HUH?", data
-
- def connectionMade(self):
- print "Listening on port %s. See server for host information." % self.factory.port
-
-class TunnelClientFactory(ClientFactory):
- protocol = TunnelClientProtocol
-
- def __init__(self, port):
- self.port = port
-
-#log.startLogging(sys.stdout)
-try:
- reactor.connectTCP("localhost", 8777, TunnelClientFactory(int(sys.argv[1])))
- reactor.run()
-except IndexError:
- print "Usage: %s <port>" % sys.argv[0]
- print " You need to specify a port to forward to."
View
@@ -1,35 +0,0 @@
-# http://groups.google.com/group/capistrano/browse_thread/thread/455c0c8a6faa9cc8?pli=1
-class Net::SSH::Gateway
- # Opens a SSH tunnel from a port on a remote host to a given host and port
- # on the local side
- # (equivalent to openssh -R parameter)
- def open_remote(port, host, remote_port, remote_host = "127.0.0.1")
- ensure_open!
-
- @session_mutex.synchronize do
- @session.forward.remote(port, host, remote_port, remote_host)
- end
-
- if block_given?
- begin
- yield [remote_port, remote_host]
- ensure
- close_remote(remote_port, remote_host)
- end
- else
- return [remote_port, remote_host]
- end
- rescue Errno::EADDRINUSE
- retry
- end
-
- # Cancels port-forwarding over an open port that was previously opened via
- # #open_remote.
- def close_remote(port, host = "127.0.0.1")
- ensure_open!
-
- @session_mutex.synchronize do
- @session.forward.cancel_remote(port, host)
- end
- end
-end
View
@@ -11,6 +11,7 @@
AUTHORIZED_KEYS = '/home/localtunnel/.ssh/authorized_keys'
PORT_RANGE = [32000, 64000]
BANNER = "This localtunnel service is brought to you by Twilio."
+SSH_OPTIONS = 'command="/bin/echo Shell access denied",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding '
def port_available(port):
try:
@@ -53,7 +54,7 @@ def garbage_collect(self):
del self.tunnels[name]
def install_key(self, key):
- key = key.strip()+"\n"
+ key = ''.join([SSH_OPTIONS, key.strip(), "\n"])
fr = open(AUTHORIZED_KEYS, 'r')
if not key in fr.readlines():
fa = open(AUTHORIZED_KEYS, 'a')

0 comments on commit 9faa31d

Please sign in to comment.