async-await 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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 :)