Skip to content

Instantly share code, notes, and snippets.

@hazcod
Last active April 21, 2020 15:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hazcod/00af72341a5347e90673785ff02efd0d to your computer and use it in GitHub Desktop.
Save hazcod/00af72341a5347e90673785ff02efd0d to your computer and use it in GitHub Desktop.
Simple port scanner. Scans full host in 24sec.
package main
import (
"context"
"fmt"
"github.com/pkg/errors"
"net"
"strings"
"sync"
"syscall"
"time"
"log"
"golang.org/x/sync/semaphore"
)
const (
timeout = time.Second * 2
MaxPort = 65535
ProtocolTCP = "tcp"
ProtocolUDP = "udp"
ProtocolICMP = "icmp"
)
type PortScanner struct {
lock *semaphore.Weighted
MaxConcurrent uint64
}
var (
PrivateIPNetworks = []net.IPNet{
{
IP: net.ParseIP("10.0.0.0"),
Mask: net.CIDRMask(8, 32),
},
{
IP: net.ParseIP("172.16.0.0"),
Mask: net.CIDRMask(12, 32),
},
{
IP: net.ParseIP("192.168.0.0"),
Mask: net.CIDRMask(16, 32),
},
};
ulimitSlack = 0.9
)
func IsIPPrivate(ip net.IP) bool {
for _, ipNet := range PrivateIPNetworks {
if ipNet.Contains(ip) {
return true
}
}
return false
}
func getInodeLimit() (int64, error) {
var rLimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return 0, errors.Wrap(err, "could not get ulimit")
}
return int64(float64(rLimit.Max - rLimit.Cur) * ulimitSlack), nil
}
func (ps *PortScanner) scanPort(ip net.IP, protocol string, port uint, timeout time.Duration) bool {
target := fmt.Sprintf("%s:%d", ip, port)
conn, err := net.DialTimeout(protocol, target, timeout)
if err != nil {
if strings.Contains(err.Error(), "too many open files") {
time.Sleep(timeout)
return ps.scanPort(ip, protocol, port, timeout)
}
return false
}
conn.Close()
return true
}
func (ps *PortScanner) Start(address net.IP, protocol string, firstPort, lastPort uint) ([]bool, error) {
if address == nil || address.IsLoopback() || IsIPPrivate(address) {
return []bool{}, errors.New(fmt.Sprintf("invalid address: %s", address))
}
if firstPort < 0 || lastPort < firstPort || lastPort > MaxPort {
return []bool{}, errors.New(fmt.Sprintf("invalid ports: %d/%d", firstPort, lastPort))
}
maxDesc, err := getInodeLimit()
if err != nil {
return []bool{}, err
}
if ps.MaxConcurrent == 0 || ps.MaxConcurrent > uint64(maxDesc) {
ps.MaxConcurrent = uint64(maxDesc)
}
resultLength := lastPort - firstPort +1
results := make([]bool, resultLength)
wg := sync.WaitGroup{}
wg.Add(int(resultLength))
ps.lock = semaphore.NewWeighted(int64(ps.MaxConcurrent))
for port := firstPort; port <= lastPort; port++ {
if err := ps.lock.Acquire(context.TODO(), 1); err != nil {
return []bool{}, errors.Wrap(err, "could not acquire lock")
}
go func(port uint) {
defer func(){
ps.lock.Release(1)
wg.Done()
}()
pos := port - firstPort
results[pos] = ps.scanPort(address, protocol, port, timeout)
}(port)
}
wg.Wait()
return results, nil
}
func main() {
now := time.Now()
ps := PortScanner{}
ports, err := ps.Start(net.ParseIP("140.82.114.3"), ProtocolTCP, 0, MaxPort)
if err != nil { log.Fatal(err) }
log.Print("time spent: " + time.Now().Sub(now).String())
for port, open := range ports {
if open {
log.Printf("%d -> open", port)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment