rendezvous-socket 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 well known rendezvous endpoint establish a peer to
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. On a server with a known IP address run the server.
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
- RENDEZVOUS_SERVER=n.n.n.n:5000 rendezvous-client
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
 
@@ -6,14 +6,7 @@ require 'rendezvous/socket'
6
6
 
7
7
  $DEBUG = true if ENV['DEBUG']
8
8
 
9
- s = Rendezvous::Socket.new(ENV['RENDEZVOUS_SERVER']).open
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
@@ -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(server)
8
- RendezvousSocket.new(server)
12
+ def self.new(url)
13
+ RendezvousSocket.new(url)
9
14
  end
10
15
 
11
16
  class RendezvousSocket
12
- attr_reader :rendezvous_server
17
+ attr_reader :rendezvous_url
13
18
 
14
- def initialize(rendezvous_server)
15
- @rendezvous_server = rendezvous_server
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
- socket = CustomSocket.new
31
- socket.bind(lport)
32
- socket.connect(*rendezvous_server.split(":"))
33
-
34
- rhost, rport = socket.gets.chomp.split(":")
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, rhost, rport)
39
- peer = CustomSocket.new
40
- peer.bind(lport)
41
- peer if peer.connect(rhost, rport)
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
- lport, rhost, rport = nil
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
 
@@ -1,5 +1,5 @@
1
1
  module Rendezvous
2
2
  module Socket
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -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.1
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: 2013-12-15 00:00:00.000000000 Z
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 well known rendezvous endpoint establish a peer
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