cleaning up, new readme

This commit is contained in:
Jeff Lindsay 2010-05-08 19:22:15 -07:00
parent 8b08c02215
commit 9faa31dd23
5 changed files with 105 additions and 148 deletions

43
README
View File

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

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

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

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

@ -11,6 +11,7 @@ SSH_USER = 'localtunnel'
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 @@ class LocalTunnelReverseProxy(proxy.ReverseProxyResource):
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')