Skip to content

Commit

Permalink
Better batch size and timeout to improve performance.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Jun 12, 2018
1 parent e2ce784 commit 07040f5
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 32 deletions.
35 changes: 17 additions & 18 deletions examples/port_scanner/port_scanner.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import (
"fmt"
"golang.org/x/sync/semaphore"
"net"
"os/exec"
"strconv"
"syscall"
"strings"
"sync"
"time"
Expand All @@ -20,27 +19,26 @@ type PortScanner struct {
}

// Provides a simple wrapper to initializing a PortScanner.
func NewPortScanner(ip string, limit int64) *PortScanner {
func NewPortScanner(ip string, limit uint64) *PortScanner {
return &PortScanner{
ip: "127.0.0.1",
lock: semaphore.NewWeighted(limit),
lock: semaphore.NewWeighted(int64(limit)),
}
}

// Returns the output from the ulimit builtin shell command for the
// maximum number of open connections, used to limit the number
// of concurrent port scans.
func Ulimit() int64 {
out, err := exec.Command("ulimit", "-n").Output()
// Compute the maximum number of files we can open.
func FileLimit(max uint64) uint64 {
var rlimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
if err != nil {
panic(err)
}
s := strings.TrimSpace(string(out))
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
panic(err)

if (max < rlimit.Cur) {
return max
}
return i

return rlimit.Cur
}

// As the name might suggest, this function checks if a given port
Expand All @@ -51,8 +49,7 @@ func checkPortOpen(ip string, port int, timeout time.Duration) {

if err != nil {
if strings.Contains(err.Error(), "socket") {
time.Sleep(500 * time.Millisecond)
checkPortOpen(ip, port, timeout)
fmt.Println(port, "timeout")
} else {
fmt.Println(port, "closed")
}
Expand Down Expand Up @@ -97,9 +94,11 @@ func (ps *PortScanner) Start(start, stop int, timeout time.Duration) {
// This function kicks off the whole shindig' and provides a
// basic example of the internal API usage.
func main() {
batch_size := FileLimit(512)

// Create a new PortScanner for localhost.
ps := NewPortScanner("127.0.0.1", Ulimit())
ps := NewPortScanner("0.0.0.0", batch_size)

// Start scanning all the ports on localhost.
ps.Start(1, 1024, 500*time.Millisecond)
ps.Start(1, 65535, 1000*time.Millisecond)
}
18 changes: 9 additions & 9 deletions examples/port_scanner/port_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,32 @@
import asyncio

class PortScanner:
def __init__(self, host="0.0.0.0", ports=range(1, 1024+1)):
def __init__(self, host="0.0.0.0", ports=range(1, 1024+1), batch_size=1024):
self.host = host
self.ports = ports
limits = resource.getrlimit(resource.RLIMIT_NOFILE)
self.semaphore = asyncio.Semaphore(value=limits[0])
self.semaphore = asyncio.Semaphore(value=batch_size)
self.loop = asyncio.get_event_loop()

async def scan_port(self, port, timeout=0.5):
async def scan_port(self, port, timeout):
async with self.semaphore:
try:
future = asyncio.open_connection(self.host, port, loop=self.loop)
reader, writer = await asyncio.wait_for(future, timeout=0.5)
reader, writer = await asyncio.wait_for(future, timeout=timeout)
print("{} open".format(port))
reader.close()
writer.close()
except ConnectionRefusedError:
print("{} closed")
print("{} closed".format(port))
except asyncio.TimeoutError:
print("{} timeout".format(port))

def start(self, timeout=0.5):
def start(self, timeout=1.0):
self.loop.run_until_complete(asyncio.gather(
*[self.scan_port(port, timeout) for port in self.ports]
))

limits = resource.getrlimit(resource.RLIMIT_NOFILE)
batch_size = min(512, limits[0])

scanner = PortScanner(ports=range(1, 65536))
scanner = PortScanner(ports=range(1, 65536), batch_size=batch_size)

scanner.start()
12 changes: 7 additions & 5 deletions examples/port_scanner/port_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ class PortScanner
include Async::Await
include Async::IO

def initialize(host: '0.0.0.0', ports:)
def initialize(host: '0.0.0.0', ports:, batch_size: 1024)
@host = host
@ports = ports
limits = Process.getrlimit(Process::RLIMIT_NOFILE)
@semaphore = Async::Semaphore.new(1024)
@semaphore = Async::Semaphore.new(batch_size)
end

def scan_port(port, timeout)
Expand All @@ -28,7 +27,7 @@ def scan_port(port, timeout)
puts "#{port} timeout"
end

async def start(timeout = 0.5)
async def start(timeout = 1.0)
@ports.map do |port|
@semaphore.async do
scan_port(port, timeout)
Expand All @@ -37,6 +36,9 @@ def scan_port(port, timeout)
end
end

scanner = PortScanner.new(ports: (1...65536))
limits = Process.getrlimit(Process::RLIMIT_NOFILE)
batch_size = [512, limits.first].min

scanner = PortScanner.new(ports: (1...65536), batch_size: batch_size)

scanner.start

0 comments on commit 07040f5

Please sign in to comment.