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 +4 -4
- data/.editorconfig +2 -1
- data/.travis.yml +12 -11
- data/Gemfile +0 -3
- data/async-await.gemspec +2 -1
- data/examples/port_scanner/README.md +90 -0
- data/examples/port_scanner/port_scanner.go +107 -0
- data/examples/port_scanner/port_scanner.py +36 -0
- data/examples/port_scanner/port_scanner.rb +44 -0
- data/lib/async/await/methods.rb +1 -1
- data/lib/async/await/version.rb +1 -1
- metadata +25 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c115c979974936f460f8c73b51c209005bc175fbb3f1d252d46f3257515cbb0a
|
4
|
+
data.tar.gz: 00e46441e57ff43924bc62c3563739bdf036f4f2999aabfe80d54862d5c096e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b579efc91d103e3819a3928dc370c11403bee51460c0089774e1374f1587a8913a80d745829ebe57cd7f0eaa349732453ab8e7ad06329f2053bfc3c0711dc53b
|
7
|
+
data.tar.gz: d72e8143f192204a6b061ae05f6bc4bb0753e621b69e765e55ecfdc8d21da7a1b0306c88eb5d71841debb6628fdd24f324009176c54922e945b6b0271e5767e4
|
data/.editorconfig
CHANGED
data/.travis.yml
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
language: ruby
|
2
|
-
sudo: false
|
3
|
-
dist: trusty
|
4
2
|
cache: bundler
|
5
|
-
|
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:
|
18
|
+
- rvm: truffleruby
|
data/Gemfile
CHANGED
data/async-await.gemspec
CHANGED
@@ -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 "
|
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
|
data/lib/async/await/methods.rb
CHANGED
data/lib/async/await/version.rb
CHANGED
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.
|
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:
|
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: '
|
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: '
|
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
|
-
|
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 :)
|