rendezvous-socket 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.ruby-version +1 -0
- data/README.md +9 -5
- data/bin/rendezvous-client +2 -9
- data/lib/rendezvous/socket.rb +40 -23
- data/lib/rendezvous/socket/accept_or_connect.rb +87 -0
- data/lib/rendezvous/socket/custom_socket.rb +12 -0
- data/lib/rendezvous/socket/version.rb +1 -1
- data/rendezvous-socket.gemspec +1 -0
- metadata +28 -4
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p448
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Rendezvous::Socket
|
2
2
|
|
3
|
-
Using the help of a
|
3
|
+
Using the help of a known publically routable [rendezvous-server](https://github.com/mikehale/rendezvous-server) endpoint establish a peer to
|
4
4
|
peer connection between clients which may be behind NAT firewalls. In
|
5
5
|
the event that NAT traversal techniques are not successful fallback to
|
6
6
|
relaying the connection through the rendezvous server. All connections
|
@@ -22,15 +22,18 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
1.
|
25
|
+
1. Deploy the server
|
26
26
|
|
27
27
|
```bash
|
28
|
-
rendezvous-server
|
28
|
+
git clone https://github.com/mikehale/rendezvous-server.git
|
29
|
+
heroku create
|
30
|
+
git push heroku master
|
31
|
+
heroku labs:enable websockets
|
29
32
|
```
|
30
33
|
2. On 2 peer machines that wish to establish a direct connection with each other run the client.
|
31
34
|
|
32
35
|
```bash
|
33
|
-
|
36
|
+
RENDEZVOUS_URL=https://rendezvous-server.herokuapp.com bundle exec bin/rendezvous-client
|
34
37
|
```
|
35
38
|
|
36
39
|
You will know it worked when you see the hostname of each peer in the
|
@@ -46,11 +49,12 @@ output of it's peer.
|
|
46
49
|
|
47
50
|
## TODO
|
48
51
|
|
49
|
-
* Speed. Can a connection be established in < 4 seconds?
|
50
52
|
* Server managed sessions
|
51
53
|
* SSL
|
52
54
|
* Clean interface (as similar to socket interface as possible)
|
53
55
|
* Fallback to relay mode
|
56
|
+
* Link local connection attempt
|
57
|
+
* Figure out why linux does not even attempt to send packets somtimes. Connect vs Accept first?
|
54
58
|
|
55
59
|
## Inspiration
|
56
60
|
|
data/bin/rendezvous-client
CHANGED
@@ -6,14 +6,7 @@ require 'rendezvous/socket'
|
|
6
6
|
|
7
7
|
$DEBUG = true if ENV['DEBUG']
|
8
8
|
|
9
|
-
s = Rendezvous::Socket.new(ENV['
|
10
|
-
s.close
|
9
|
+
s, addrinfo, type = Rendezvous::Socket.new(ENV['RENDEZVOUS_URL']).open
|
11
10
|
s.puts Socket.gethostname
|
12
11
|
puts s.gets
|
13
|
-
|
14
|
-
i = 0
|
15
|
-
while true
|
16
|
-
s.puts i = i + 1
|
17
|
-
puts s.gets
|
18
|
-
sleep 1
|
19
|
-
end
|
12
|
+
s.close
|
data/lib/rendezvous/socket.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
require "rendezvous/socket/version"
|
2
2
|
require "rendezvous/socket/custom_socket"
|
3
|
+
require "rendezvous/socket/accept_or_connect"
|
4
|
+
require "timeout"
|
5
|
+
require "excon"
|
6
|
+
|
7
|
+
Excon.defaults[:ssl_verify_peer] = false
|
3
8
|
|
4
9
|
module Rendezvous
|
5
10
|
module Socket
|
6
11
|
|
7
|
-
def self.new(
|
8
|
-
RendezvousSocket.new(
|
12
|
+
def self.new(url)
|
13
|
+
RendezvousSocket.new(url)
|
9
14
|
end
|
10
15
|
|
11
16
|
class RendezvousSocket
|
12
|
-
attr_reader :
|
17
|
+
attr_reader :rendezvous_url
|
13
18
|
|
14
|
-
def initialize(
|
15
|
-
@
|
16
|
-
super()
|
19
|
+
def initialize(rendezvous_url)
|
20
|
+
@rendezvous_url = rendezvous_url
|
17
21
|
end
|
18
22
|
|
19
23
|
def log(args={})
|
@@ -25,30 +29,43 @@ module Rendezvous
|
|
25
29
|
result
|
26
30
|
end
|
27
31
|
|
32
|
+
def punch_nat_nonblock(lport, rhost, rport)
|
33
|
+
log(step: :punch_nat) {
|
34
|
+
#Thread.new do
|
35
|
+
begin
|
36
|
+
socket = CustomSocket.new
|
37
|
+
# set ttl low enough so it crosses our nat but won't reach remote peer.
|
38
|
+
socket.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_TTL, [2].pack("L"))
|
39
|
+
socket.bind(lport)
|
40
|
+
|
41
|
+
Timeout::timeout(0.3) do
|
42
|
+
socket.connect(rhost,rport)
|
43
|
+
end
|
44
|
+
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
|
45
|
+
socket.close
|
46
|
+
end
|
47
|
+
#end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
28
51
|
# lport=0 causes OS to select a random high port
|
29
52
|
def get_peer_endpoint(lport=0)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
[socket.local_port, rhost, rport.to_i]
|
53
|
+
log(step: :get_peer_endpoint) {
|
54
|
+
response = Excon.get(rendezvous_url, reuseaddr: true)
|
55
|
+
rhost, rport = response.body.split(":")
|
56
|
+
[response.local_port, response.local_address, rport.to_i, rhost]
|
57
|
+
}
|
36
58
|
end
|
37
59
|
|
38
|
-
def peer_socket(lport,
|
39
|
-
|
40
|
-
|
41
|
-
|
60
|
+
def peer_socket(lport, lhost, rport, rhost)
|
61
|
+
log(step: :connect, lport: lport, rport: rport, rhost: rhost) {
|
62
|
+
#punch_nat_nonblock(lport, rhost, rport)
|
63
|
+
AcceptOrConnect.new(lport, lhost, rport, rhost).socket
|
64
|
+
}
|
42
65
|
end
|
43
66
|
|
44
67
|
def open
|
45
|
-
|
46
|
-
log(step: :get_peer_endpoint) {
|
47
|
-
lport, rhost, rport = get_peer_endpoint
|
48
|
-
}
|
49
|
-
log(step: :connect) {
|
50
|
-
peer_socket(lport, rhost, rport)
|
51
|
-
}
|
68
|
+
peer_socket(*get_peer_endpoint)
|
52
69
|
end
|
53
70
|
end
|
54
71
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'socket'
|
3
|
+
module Rendezvous
|
4
|
+
|
5
|
+
class AcceptOrConnect
|
6
|
+
include ::Socket::Constants
|
7
|
+
|
8
|
+
CONN_REFUSED_DELAY = 0.5
|
9
|
+
ADDR_IN_USE_DELAY = 0.5
|
10
|
+
CONNECTION_TIMEOUT = 10
|
11
|
+
|
12
|
+
attr_reader :bind_port, :bind_addr, :dest_port, :dest_addr
|
13
|
+
|
14
|
+
def initialize(bind_port, bind_addr, dest_port, dest_addr)
|
15
|
+
@bind_port = bind_port
|
16
|
+
@bind_addr = bind_addr
|
17
|
+
@dest_port = dest_port
|
18
|
+
@dest_addr = dest_addr
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_socket
|
22
|
+
::Socket.new(AF_INET, SOCK_STREAM, 0).tap do |s|
|
23
|
+
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, true)
|
24
|
+
s.setsockopt(SOL_SOCKET, SO_REUSEPORT, true) if defined?(SO_REUSEPORT)
|
25
|
+
s.bind(::Socket.sockaddr_in(bind_port, bind_addr))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def socket
|
30
|
+
Timeout::timeout(CONNECTION_TIMEOUT) {
|
31
|
+
wait_for_accept_or_connect
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def wait_for_accept_or_connect
|
36
|
+
a_thread = Thread.new {
|
37
|
+
# prefer connect over accept
|
38
|
+
if RUBY_PLATFORM =~ /linux/
|
39
|
+
sleep 2
|
40
|
+
end
|
41
|
+
|
42
|
+
s = new_socket
|
43
|
+
s.listen(5)
|
44
|
+
client_socket, addrinfo = s.accept
|
45
|
+
Thread.current["socket"] = client_socket
|
46
|
+
Thread.current["addrinfo"] = addrinfo
|
47
|
+
Thread.current.exit
|
48
|
+
}
|
49
|
+
a_thread.abort_on_exception = true
|
50
|
+
|
51
|
+
c_thread = Thread.new {
|
52
|
+
begin
|
53
|
+
s = new_socket
|
54
|
+
sockaddr = ::Socket.sockaddr_in(dest_port, dest_addr)
|
55
|
+
s.connect(sockaddr)
|
56
|
+
Thread.current["socket"] = s
|
57
|
+
Thread.current["addrinfo"] = Addrinfo.new(sockaddr)
|
58
|
+
rescue Errno::ECONNREFUSED
|
59
|
+
sleep CONN_REFUSED_DELAY
|
60
|
+
retry
|
61
|
+
rescue Errno::EADDRINUSE
|
62
|
+
sleep ADDR_IN_USE_DELAY
|
63
|
+
retry
|
64
|
+
end
|
65
|
+
Thread.current.exit
|
66
|
+
}
|
67
|
+
c_thread.abort_on_exception = true
|
68
|
+
|
69
|
+
# wait while both a and c are alive
|
70
|
+
while a_thread.alive? && c_thread.alive?
|
71
|
+
sleep 0.1
|
72
|
+
end
|
73
|
+
|
74
|
+
if a_thread.status == false
|
75
|
+
# accept succeded
|
76
|
+
Thread.kill(c_thread)
|
77
|
+
[a_thread["socket"], a_thread["addrinfo"], :accept]
|
78
|
+
elsif c_thread.status == false
|
79
|
+
# connect succeded
|
80
|
+
Thread.kill(a_thread)
|
81
|
+
[c_thread["socket"], c_thread["addrinfo"], :connect]
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -29,7 +29,19 @@ module Rendezvous
|
|
29
29
|
super(addr_remote)
|
30
30
|
end
|
31
31
|
|
32
|
+
def connect_nonblock(ip, port)
|
33
|
+
$stderr.puts "attempting to connect_nonblock to #{ip}:#{port}" if $DEBUG
|
34
|
+
addr_remote = ::Socket.pack_sockaddr_in(port, ip)
|
35
|
+
super(addr_remote)
|
36
|
+
end
|
37
|
+
|
32
38
|
def accept
|
39
|
+
$stderr.puts "attempting to accept" if $DEBUG
|
40
|
+
super[0]
|
41
|
+
end
|
42
|
+
|
43
|
+
def accept_nonblock
|
44
|
+
$stderr.puts "attempting to accept_nonblock" if $DEBUG
|
33
45
|
super[0]
|
34
46
|
end
|
35
47
|
|
data/rendezvous-socket.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "excon", "~> 0.32.0"
|
21
22
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
23
|
spec.add_development_dependency "rake"
|
23
24
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rendezvous-socket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: excon
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.32.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.32.0
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: bundler
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -43,8 +59,8 @@ dependencies:
|
|
43
59
|
- - ! '>='
|
44
60
|
- !ruby/object:Gem::Version
|
45
61
|
version: '0'
|
46
|
-
description: ! 'Using the help of a
|
47
|
-
to
|
62
|
+
description: ! 'Using the help of a known publically routable [rendezvous-server](https://github.com/mikehale/rendezvous-server)
|
63
|
+
endpoint establish a peer to
|
48
64
|
|
49
65
|
peer connection between clients which may be behind NAT firewalls. In
|
50
66
|
|
@@ -62,6 +78,7 @@ extensions: []
|
|
62
78
|
extra_rdoc_files: []
|
63
79
|
files:
|
64
80
|
- .gitignore
|
81
|
+
- .ruby-version
|
65
82
|
- Gemfile
|
66
83
|
- LICENSE.txt
|
67
84
|
- README.md
|
@@ -69,6 +86,7 @@ files:
|
|
69
86
|
- bin/rendezvous-client
|
70
87
|
- bin/rendezvous-server
|
71
88
|
- lib/rendezvous/socket.rb
|
89
|
+
- lib/rendezvous/socket/accept_or_connect.rb
|
72
90
|
- lib/rendezvous/socket/custom_socket.rb
|
73
91
|
- lib/rendezvous/socket/version.rb
|
74
92
|
- rendezvous-socket.gemspec
|
@@ -85,12 +103,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
103
|
- - ! '>='
|
86
104
|
- !ruby/object:Gem::Version
|
87
105
|
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: 1685353518646290618
|
88
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
110
|
none: false
|
90
111
|
requirements:
|
91
112
|
- - ! '>='
|
92
113
|
- !ruby/object:Gem::Version
|
93
114
|
version: '0'
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
hash: 1685353518646290618
|
94
118
|
requirements: []
|
95
119
|
rubyforge_project:
|
96
120
|
rubygems_version: 1.8.23
|