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