Skip to content

Commit

Permalink
Merge branch 'master' of github.com:progrium/localtunnel
Browse files Browse the repository at this point in the history
  • Loading branch information
progrium committed Feb 23, 2011
2 parents 9232fbb + 74ccabb commit 3ee78a5
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 120 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
.idea
.idea/*
.idea/**/*
33 changes: 0 additions & 33 deletions README

This file was deleted.

57 changes: 57 additions & 0 deletions README.rdoc
@@ -0,0 +1,57 @@
= localtunnel -- instant public tunnel for local web servers

Dependencies:
- Ruby (with libopenssl) and Rubygems.
- A public key.

To get the dependencies, type:

sudo apt-get install ruby ruby1.8-dev rubygems1.8 libopenssl-ruby

If you have never made a public key, then run:

ssh-keygen

== Install

sudo gem install localtunnel

or to get the source:

git clone http://github.com/progrium/localtunnel.git


== Usage

localtunnel [options] <localport>
-k, --key FILE upload a public key for authentication

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.

This is how you make your local port 8080 public:

$ localtunnel 8080

Port 8080 is now publicly accessible from http://8bv2.localtunnel.com ...

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.

If have never run localtunnel before, you'll need to upload a public
key to authenticate. You do this once:

$ localtunnel -k ~/.ssh/id_rsa.pub 8080

After that, you shouldn't have to use -k again.

Localtunnel can be started before or after the local web server. It
tunnels through to the url given in that status message "publicly
accessible from..." for as long as the command is running. The tunnel
is closed if the command exits.

94 changes: 9 additions & 85 deletions bin/localtunnel
@@ -1,6 +1,6 @@
#!/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
Expand All @@ -9,10 +9,10 @@
# 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
Expand All @@ -22,88 +22,10 @@
# 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'
require 'net/http'
require 'uri'
require 'optparse'
require 'json'
base_dir = File.dirname(File.expand_path(__FILE__))

def register_tunnel(key=nil)
url = URI.parse("http://open.localtunnel.com/")
if key
resp = JSON.parse(Net::HTTP.post_form(url, {"key" => key}).body)
else
resp = JSON.parse(Net::HTTP.get(url))
end
if resp.has_key? 'error'
puts " [Error] #{resp['error']}"
exit
end
return resp
rescue
puts " [Error] Unable to register tunnel. Perhaps service is down?"
exit
end

def start_tunnel(port, tunnel)
gateway = Net::SSH::Gateway.new(tunnel['host'], tunnel['user'])
gateway.open_remote(port.to_i, '127.0.0.1', tunnel['through_port'].to_i) do |rp,rh|
puts " " << tunnel['banner'] if tunnel.has_key? 'banner'
puts " Port #{port} is now publicly accessible from http://#{tunnel['host']} ..."
begin
sleep 1 while true
rescue Interrupt
gateway.close_remote(rp, rh)
exit
end
end
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'} #{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
require base_dir + '/../lib/local_tunnel'
require base_dir + '/../lib/net_ssh_gateway_patch'

key = nil
options = OptionParser.new do |o|
Expand All @@ -121,4 +43,6 @@ unless local_port
exit
end

start_tunnel(local_port, register_tunnel(key))
x = LocalTunnel.new(local_port, key)
x.register_tunnel
x.start_tunnel
59 changes: 59 additions & 0 deletions lib/local_tunnel.rb
@@ -0,0 +1,59 @@
require 'rubygems'
require 'net/ssh'
require 'net/ssh/gateway'
require 'net/http'
require 'uri'
require 'optparse'
require 'json'

class LocalTunnel

attr_accessor :port, :key, :host

def initialize(port, key)
@port = port
@key = key
@host = ""
end

def register_tunnel(key=@key)
url = URI.parse("http://open.localtunnel.com/")
if key
resp = JSON.parse(Net::HTTP.post_form(url, {"key" => key}).body)
else
resp = JSON.parse(Net::HTTP.get(url))
end
if resp.has_key? 'error'
puts " [Error] #{resp['error']}"
exit
end
@host = resp['host']
@tunnel = resp
return resp
rescue
puts " [Error] Unable to register tunnel. Perhaps service is down?"
exit
end

def start_tunnel
port = @port
tunnel = @tunnel
gateway = Net::SSH::Gateway.new(tunnel['host'], tunnel['user'])
gateway.open_remote(port.to_i, '127.0.0.1', tunnel['through_port'].to_i) do |rp,rh|
puts " " << tunnel['banner'] if tunnel.has_key? 'banner'
puts " Port #{port} is now publicly accessible from http://#{tunnel['host']} ..."
begin
sleep 1 while true
rescue Interrupt
gateway.close_remote(rp, rh)
exit
end
end
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'} #{port}"
exit
end
end
43 changes: 43 additions & 0 deletions lib/net_ssh_gateway_patch.rb
@@ -0,0 +1,43 @@
require 'rubygems'
require 'net/ssh'
require 'net/ssh/gateway'
require 'net/http'
require 'uri'
require 'optparse'
require 'json'

# 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
8 changes: 6 additions & 2 deletions server.py
@@ -1,3 +1,7 @@
try:
from twisted.internet import pollreactor
pollreactor.install()
except: pass
from twisted.internet import protocol, reactor, defer, task
from twisted.web import http, proxy, resource, server
from twisted.python import log
Expand All @@ -21,7 +25,7 @@ def port_available(port):
except socket.error:
return True

def baseN(num,b=36,numerals="0123456789abcdefghijklmnopqrstuvwxyz"):
def baseN(num,b=32,numerals="23456789abcdefghijkmnpqrstuvwxyz"):
return ((num == 0) and "0" ) or (baseN(num // b, b).lstrip("0") + numerals[num % b])

class LocalTunnelReverseProxy(proxy.ReverseProxyResource):
Expand Down Expand Up @@ -99,4 +103,4 @@ def render(self, request):

log.startLogging(sys.stdout)
reactor.listenTCP(80, server.Site(LocalTunnelReverseProxy(SSH_USER)))
reactor.run()
reactor.run()

0 comments on commit 3ee78a5

Please sign in to comment.