async-await 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 452fef45e2da9f4b5f6a48102304e5a06ad501ca084fedba246cff271687828e
4
- data.tar.gz: 3d909df655b718a64d0a9b743e0ffe6c2dad06f9481ce22f60ec464ced45eec7
3
+ metadata.gz: c115c979974936f460f8c73b51c209005bc175fbb3f1d252d46f3257515cbb0a
4
+ data.tar.gz: 00e46441e57ff43924bc62c3563739bdf036f4f2999aabfe80d54862d5c096e4
5
5
  SHA512:
6
- metadata.gz: afe87a31cf9cbc63727362839868a7575532f824bae986643bce644e0a04116f7fe44a09049f1e373d537e768105b837575bd011ec1b5dd2983a04453977df37
7
- data.tar.gz: 2a67e3c5d77f9290a9372107ed03d55d074f0a66be4af217255534ae59085100e3ecd88485f07f5a45bd4e9109f0160a7391bd56e5cdaebb47dac7ee04bcd34d
6
+ metadata.gz: b579efc91d103e3819a3928dc370c11403bee51460c0089774e1374f1587a8913a80d745829ebe57cd7f0eaa349732453ab8e7ad06329f2053bfc3c0711dc53b
7
+ data.tar.gz: d72e8143f192204a6b061ae05f6bc4bb0753e621b69e765e55ecfdc8d21da7a1b0306c88eb5d71841debb6628fdd24f324009176c54922e945b6b0271e5767e4
@@ -2,4 +2,5 @@ root = true
2
2
 
3
3
  [*]
4
4
  indent_style = tab
5
- indent_size = 4
5
+ indent_size = 2
6
+
@@ -1,17 +1,18 @@
1
1
  language: ruby
2
- sudo: false
3
- dist: trusty
4
2
  cache: bundler
5
- rvm:
6
- - 2.2
7
- - 2.3
8
- - 2.4
9
- - 2.5
10
- - jruby-head
11
- - ruby-head
12
- - rbx-3
3
+
13
4
  matrix:
5
+ include:
6
+ - rvm: 2.3
7
+ - rvm: 2.4
8
+ - rvm: 2.5
9
+ - rvm: 2.6
10
+ - rvm: 2.6
11
+ env: COVERAGE=BriefSummary,Coveralls
12
+ - rvm: ruby-head
13
+ - rvm: jruby-head
14
+ - rvm: truffleruby
14
15
  allow_failures:
15
16
  - rvm: ruby-head
16
17
  - rvm: jruby-head
17
- - rvm: rbx-3
18
+ - rvm: truffleruby
data/Gemfile CHANGED
@@ -10,7 +10,4 @@ end
10
10
  group :test do
11
11
  gem 'benchmark-ips'
12
12
  gem 'ruby-prof', platforms: :mri
13
-
14
- gem 'simplecov'
15
- gem 'coveralls', require: false
16
13
  end
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.add_dependency "async", "~> 1.3"
20
20
  spec.add_development_dependency "async-rspec", "~> 1.1"
21
21
 
22
- spec.add_development_dependency "bundler", "~> 1.15"
22
+ spec.add_development_dependency "covered"
23
+ spec.add_development_dependency "bundler"
23
24
  spec.add_development_dependency "rake", "~> 10.0"
24
25
  spec.add_development_dependency "rspec", "~> 3.0"
25
26
  end
@@ -0,0 +1,90 @@
1
+ # Port Scanner
2
+
3
+ A simple `connect`-based port scanner. It scans locahost for all open ports.
4
+
5
+ ## Usage
6
+
7
+ ### Go
8
+
9
+ Go is pretty awesome, because when the operation would not block, it runs sequentially in the same thread. Go spins up threads and delegates work across available CPU cores.
10
+
11
+ $ go get golang.org/x/sync/semaphore
12
+ $ go build port_scanner.go
13
+ $ time ./port_scanner
14
+ 22 open
15
+ 139 open
16
+ 445 open
17
+ 3306 open
18
+ 5355 open
19
+ 5432 open
20
+ 6379 open
21
+ 9293 open
22
+ 9292 open
23
+ 9516 open
24
+ 9515 open
25
+ 12046 open
26
+ 12813 open
27
+ ./port_scanner 1.70s user 1.18s system 503% cpu 0.572 total
28
+
29
+ ### Python
30
+
31
+ Python was the slowest. This is possibly due to the implementation of semaphore. It creates all 65,535 tasks, and then most of them block on the semaphore.
32
+
33
+ $ ./port_scanner.py
34
+ 5355 open
35
+ 5432 open
36
+ 3306 open
37
+ 39610 open
38
+ 58260 open
39
+ 12813 open
40
+ 139 open
41
+ 445 open
42
+ 12046 open
43
+ 22 open
44
+ 9292 open
45
+ 9293 open
46
+ 9515 open
47
+ 9516 open
48
+ 6379 open
49
+ ./port_scanner.py 11.41s user 0.88s system 98% cpu 12.485 total
50
+
51
+ ### Ruby
52
+
53
+ Ruby performance isn't that bad. It's only about half as fast as Go, considering that Go runs across all cores, while the Ruby implementation is limited to one core.
54
+
55
+ $ ./port_scanner.rb
56
+ 22 open
57
+ 139 open
58
+ 445 open
59
+ 3306 open
60
+ 5432 open
61
+ 5355 open
62
+ 6379 open
63
+ 9516 open
64
+ 9515 open
65
+ 9293 open
66
+ 9292 open
67
+ 12046 open
68
+ 12813 open
69
+ ./port_scanner.rb 5.99s user 1.18s system 95% cpu 7.543 total
70
+
71
+ ## Notes
72
+
73
+ ### Why do I sometimes see high ports?
74
+
75
+ Believe it or not, you can connect to your own sockets.
76
+
77
+ ```ruby
78
+ require 'socket'
79
+ a = Addrinfo.tcp("127.0.0.1", 50000)
80
+ s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
81
+ s.bind(a)
82
+ s.connect(a)
83
+
84
+ s.write("Hello World")
85
+ => 11
86
+ [8] pry(main)> s.read(11)
87
+ => "Hello World"
88
+ ```
89
+
90
+ What's happening is that your socket is implicitly binding to a high port, and at the same time it's trying to connect to it.
@@ -0,0 +1,107 @@
1
+ package main
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "golang.org/x/sync/semaphore"
7
+ "net"
8
+ "syscall"
9
+ "strings"
10
+ "sync"
11
+ "time"
12
+ )
13
+
14
+ // The star of the show, of modest means,
15
+ // used to manage the port scan for a single host.
16
+ type PortScanner struct {
17
+ ip string
18
+ lock *semaphore.Weighted
19
+ }
20
+
21
+ // Provides a simple wrapper to initializing a PortScanner.
22
+ func NewPortScanner(ip string, limit uint64) *PortScanner {
23
+ return &PortScanner{
24
+ ip: ip,
25
+ lock: semaphore.NewWeighted(int64(limit)),
26
+ }
27
+ }
28
+
29
+ // Compute the maximum number of files we can open.
30
+ func FileLimit(max uint64) uint64 {
31
+ var rlimit syscall.Rlimit
32
+ err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
33
+ if err != nil {
34
+ panic(err)
35
+ }
36
+
37
+ if (max < rlimit.Cur) {
38
+ return max
39
+ }
40
+
41
+ return rlimit.Cur
42
+ }
43
+
44
+ // As the name might suggest, this function checks if a given port
45
+ // is open to TCP communication. Used in conjunction with the Start function
46
+ // to sweep over a range of ports concurrently.
47
+ func checkPortOpen(ip string, port int, timeout time.Duration) {
48
+ conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), timeout)
49
+
50
+ if err != nil {
51
+ if strings.Contains(err.Error(), "timeout") {
52
+ fmt.Println(port, "timeout", err.Error())
53
+ } else if strings.Contains(err.Error(), "deadline exceeded") {
54
+ fmt.Println(port, "timeout", err.Error())
55
+ } else if strings.Contains(err.Error(), "refused") {
56
+ // fmt.Println(port, "closed", err.Error())
57
+ } else {
58
+ panic(err)
59
+ }
60
+ return
61
+ }
62
+
63
+ fmt.Println(port, "open")
64
+ conn.Close()
65
+ }
66
+
67
+ // This function is the bread and butter of this script. It manages the
68
+ // port scanning for a given range of ports with the given timeout value
69
+ // to deal with filtered ports by a firewall typically.
70
+ func (ps *PortScanner) Start(start, stop int, timeout time.Duration) {
71
+ wg := sync.WaitGroup{}
72
+ defer wg.Wait()
73
+
74
+ for port := start; port <= stop; port++ {
75
+
76
+ ctx := context.TODO()
77
+
78
+ for {
79
+ err := ps.lock.Acquire(ctx, 1)
80
+ if err == nil {
81
+ break
82
+ }
83
+ }
84
+
85
+ wg.Add(1)
86
+
87
+ go func(ip string, port int) {
88
+ defer ps.lock.Release(1)
89
+ defer wg.Done()
90
+
91
+ checkPortOpen(ps.ip, port, timeout)
92
+ }(ps.ip, port)
93
+
94
+ }
95
+ }
96
+
97
+ // This function kicks off the whole shindig' and provides a
98
+ // basic example of the internal API usage.
99
+ func main() {
100
+ batch_size := FileLimit(512)
101
+
102
+ // Create a new PortScanner for localhost.
103
+ ps := NewPortScanner("127.0.0.1", batch_size)
104
+
105
+ // Start scanning all the ports on localhost.
106
+ ps.Start(1, 65535, 1000*time.Millisecond)
107
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python
2
+
3
+ import os, resource
4
+ import asyncio
5
+
6
+ class PortScanner:
7
+ def __init__(self, host="0.0.0.0", ports=range(1, 1024+1), batch_size=1024):
8
+ self.host = host
9
+ self.ports = ports
10
+ self.semaphore = asyncio.Semaphore(value=batch_size)
11
+ self.loop = asyncio.get_event_loop()
12
+
13
+ async def scan_port(self, port, timeout):
14
+ async with self.semaphore:
15
+ try:
16
+ future = asyncio.open_connection(self.host, port, loop=self.loop)
17
+ reader, writer = await asyncio.wait_for(future, timeout=timeout)
18
+ print("{} open".format(port))
19
+ writer.close()
20
+ except ConnectionRefusedError:
21
+ pass
22
+ # print("{} closed".format(port))
23
+ except asyncio.TimeoutError:
24
+ print("{} timeout".format(port))
25
+
26
+ def start(self, timeout=1.0):
27
+ self.loop.run_until_complete(asyncio.gather(
28
+ *[self.scan_port(port, timeout) for port in self.ports]
29
+ ))
30
+
31
+ limits = resource.getrlimit(resource.RLIMIT_NOFILE)
32
+ batch_size = min(512, limits[0])
33
+
34
+ scanner = PortScanner(host="127.0.0.1", ports=range(1, 65535+1), batch_size=batch_size)
35
+
36
+ scanner.start()
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'async/io'
4
+ require 'async/semaphore'
5
+ require_relative '../../lib/async/await'
6
+
7
+ class PortScanner
8
+ include Async::Await
9
+ include Async::IO
10
+
11
+ def initialize(host: '0.0.0.0', ports:, batch_size: 1024)
12
+ @host = host
13
+ @ports = ports
14
+ @semaphore = Async::Semaphore.new(batch_size)
15
+ end
16
+
17
+ def scan_port(port, timeout)
18
+ with_timeout(timeout) do
19
+ address = Async::IO::Address.tcp(@host, port)
20
+ peer = Socket.connect(address)
21
+ puts "#{port} open"
22
+ peer.close
23
+ end
24
+ rescue Errno::ECONNREFUSED
25
+ # puts "#{port} closed"
26
+ rescue Async::TimeoutError
27
+ puts "#{port} timeout"
28
+ end
29
+
30
+ async def start(timeout = 1.0)
31
+ @ports.map do |port|
32
+ @semaphore.async do
33
+ scan_port(port, timeout)
34
+ end
35
+ end.collect(&:result)
36
+ end
37
+ end
38
+
39
+ limits = Process.getrlimit(Process::RLIMIT_NOFILE)
40
+ batch_size = [512, limits.first].min
41
+
42
+ scanner = PortScanner.new(host: "127.0.0.1", ports: Range.new(1, 65535), batch_size: batch_size)
43
+
44
+ scanner.start
@@ -29,7 +29,7 @@ module Async
29
29
  Async::Task.current
30
30
  end
31
31
 
32
- def_delegators :task, :timeout, :sleep, :async
32
+ def_delegators :task, :with_timeout, :sleep, :async
33
33
 
34
34
  def await(&block)
35
35
  block.call.wait
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module Await
23
- VERSION = "0.2.0"
23
+ VERSION = "0.3.0"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-await
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-19 00:00:00.000000000 Z
11
+ date: 2019-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -38,20 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: covered
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: '1.15'
61
+ version: '0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: '1.15'
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -97,6 +111,10 @@ files:
97
111
  - async-await.gemspec
98
112
  - examples/chickens.rb
99
113
  - examples/echo.rb
114
+ - examples/port_scanner/README.md
115
+ - examples/port_scanner/port_scanner.go
116
+ - examples/port_scanner/port_scanner.py
117
+ - examples/port_scanner/port_scanner.rb
100
118
  - examples/sleep_sort.rb
101
119
  - lib/async/await.rb
102
120
  - lib/async/await/methods.rb
@@ -119,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
137
  - !ruby/object:Gem::Version
120
138
  version: '0'
121
139
  requirements: []
122
- rubyforge_project:
123
- rubygems_version: 2.7.6
140
+ rubygems_version: 3.0.2
124
141
  signing_key:
125
142
  specification_version: 4
126
143
  summary: Implements the async/await pattern on top of async :)