From c95b57a6f8b8f7b54a3a76db831610a9e8377da7 Mon Sep 17 00:00:00 2001 From: au Date: Mon, 29 May 2023 18:36:22 +0300 Subject: [PATCH 1/4] add context methods and handle context closing --- gomap.go | 28 +++++++++++++++----- gomap_funcs.go | 2 +- gomap_scan.go | 71 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/gomap.go b/gomap.go index cf49390..d7b0015 100644 --- a/gomap.go +++ b/gomap.go @@ -2,6 +2,7 @@ package gomap import ( "bytes" + "context" "encoding/json" "fmt" "net" @@ -55,32 +56,47 @@ type RangeScanResult []*IPScanResult // ScanIP scans a single IP for open ports func ScanIP(hostname string, proto string, fastscan bool, stealth bool) (*IPScanResult, error) { + return ScanIPCtx(context.Background(), hostname, proto, fastscan, stealth) +} + +// ScanIP scans a single IP for open ports +func ScanIPCtx( + ctx context.Context, hostname string, proto string, fastscan bool, stealth bool, +) (*IPScanResult, error) { laddr, err := getLocalIP() if err != nil { return nil, err } if stealth { - if canSocketBind(laddr) == false { + if !canSocketBind(laddr) { return nil, fmt.Errorf("socket: operation not permitted") } } - return scanIPPorts(hostname, laddr, proto, fastscan, stealth) + + return scanIPPorts(ctx, hostname, laddr, proto, fastscan, stealth) } // ScanRange scans every address on a CIDR for open ports func ScanRange(proto string, fastscan bool, stealth bool) (RangeScanResult, error) { + return ScanRangeCtx(context.Background(), proto, fastscan, stealth) +} + +// ScanRangeCtx scans every address on a CIDR for open ports +func ScanRangeCtx( + ctx context.Context, proto string, fastscan bool, stealth bool, +) (RangeScanResult, error) { laddr, err := getLocalIP() if err != nil { return nil, err } if stealth { - if canSocketBind(laddr) == false { + if !canSocketBind(laddr) { return nil, fmt.Errorf("socket: operation not permitted") } } - return scanIPRange(laddr, proto, fastscan, stealth) + return scanIPRange(ctx, laddr, proto, fastscan, stealth) } // String with the results of a single scanned IP @@ -146,7 +162,7 @@ func (results RangeScanResult) String() string { func (results *IPScanResult) Json() (string, error) { var ipdata JsonIP fmt.Println(results.IP) - ipdata.IP = fmt.Sprintf("%s", results.IP[len(results.IP)-1]) + ipdata.IP = results.IP[len(results.IP)-1].String() ipdata.Hostname = results.Hostname active := false @@ -180,7 +196,7 @@ func (results RangeScanResult) Json() (string, error) { for _, r := range results { var ipdata JsonIP - ipdata.IP = fmt.Sprintf("%s", r.IP[len(r.IP)-1]) + ipdata.IP = r.IP[len(r.IP)-1].String() ipdata.Hostname = r.Hostname active := false diff --git a/gomap_funcs.go b/gomap_funcs.go index e62553a..419bb3c 100644 --- a/gomap_funcs.go +++ b/gomap_funcs.go @@ -77,5 +77,5 @@ func getLocalIP() (string, error) { } } } - return "", fmt.Errorf("No IP Found") + return "", fmt.Errorf("no IP found") } diff --git a/gomap_scan.go b/gomap_scan.go index c749e82..1b5b078 100644 --- a/gomap_scan.go +++ b/gomap_scan.go @@ -1,22 +1,26 @@ package gomap import ( + "context" "fmt" "net" "strconv" + "sync" "time" ) // scanIPRange scans an entire cidr range for open ports // I am fairly happy with this code since its just iterating // over scanIPPorts. Most issues are deeper in the code. -func scanIPRange(laddr string, proto string, fastscan bool, stealth bool) (RangeScanResult, error) { +func scanIPRange( + ctx context.Context, laddr string, proto string, fastscan bool, stealth bool, +) (RangeScanResult, error) { iprange := getLocalRange() hosts := createHostRange(iprange) var results RangeScanResult for _, h := range hosts { - scan, err := scanIPPorts(h, laddr, proto, fastscan, stealth) + scan, err := scanIPPorts(ctx, h, laddr, proto, fastscan, stealth) if err != nil { continue } @@ -27,9 +31,9 @@ func scanIPRange(laddr string, proto string, fastscan bool, stealth bool) (Range } // scanIPPorts scans a list of ports on -func scanIPPorts(hostname string, laddr string, proto string, fastscan bool, stealth bool) (*IPScanResult, error) { - var results []portResult - +func scanIPPorts( + ctx context.Context, hostname string, laddr string, proto string, fastscan bool, stealth bool, +) (*IPScanResult, error) { // checks if device is online addr, err := net.LookupIP(hostname) if err != nil { @@ -53,10 +57,15 @@ func scanIPPorts(hostname string, laddr string, proto string, fastscan bool, ste // Start prepping channels and vars for worker pool in := make(chan int) go func() { + defer close(in) + for i := 0; i <= 65535; i++ { - in <- i + select { + case <-ctx.Done(): + return + case in <- i: + } } - close(in) }() var list map[int]string @@ -73,33 +82,51 @@ func scanIPPorts(hostname string, laddr string, proto string, fastscan bool, ste tasks := len(list) // Create results channel and worker function - resultChannel := make(chan portResult, tasks) + resultChannel := make(chan portResult) + + var wg sync.WaitGroup worker := func() { - for port := range in { - if service, ok := list[port]; ok { - if stealth { - scanPortSyn(resultChannel, proto, hostname, service, port, laddr) - } else { - scanPort(resultChannel, proto, hostname, service, port) + defer wg.Done() + + for { + select { + case <-ctx.Done(): + return + case port, ok := <-in: + if !ok { + return + } + + if service, ok := list[port]; ok { + if stealth { + scanPortSyn(resultChannel, proto, hostname, service, port, laddr) + } else { + scanPort(resultChannel, proto, hostname, service, port) + } } } + } } // Deploy a pool of workers for i := 0; i < depth; i++ { + wg.Add(1) go worker() } - // Combines all results from resultChannel and return a IPScanResult - for result := range resultChannel { - results = append(results, result) - fmt.Printf("\033[2K\rHost: %s | Ports Scanned %d/%d", hostname, len(results), tasks) - - if len(results) == tasks { - close(resultChannel) + var results []portResult + go func() { + // Combines all results from resultChannel and return a IPScanResult + for result := range resultChannel { + results = append(results, result) + fmt.Printf("\033[2K\rHost: %s | Ports Scanned %d/%d", hostname, len(results), tasks) } - } + }() + + wg.Wait() + + close(resultChannel) return &IPScanResult{ Hostname: hname[0], From 26f14a8e4245fc6e3fc183a766311611fc0b694e Mon Sep 17 00:00:00 2001 From: Andrii Ursulenko Date: Tue, 30 May 2023 11:52:51 +0300 Subject: [PATCH 2/4] fix comment --- gomap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gomap.go b/gomap.go index d7b0015..1c03651 100644 --- a/gomap.go +++ b/gomap.go @@ -59,7 +59,7 @@ func ScanIP(hostname string, proto string, fastscan bool, stealth bool) (*IPScan return ScanIPCtx(context.Background(), hostname, proto, fastscan, stealth) } -// ScanIP scans a single IP for open ports +// ScanIPCtx scans a single IP for open ports func ScanIPCtx( ctx context.Context, hostname string, proto string, fastscan bool, stealth bool, ) (*IPScanResult, error) { From d7b32fe789778383ec537f93b99a0350d7110cfc Mon Sep 17 00:00:00 2001 From: au Date: Wed, 11 Sep 2024 14:24:41 +0300 Subject: [PATCH 3/4] bunch of shit --- gomap.go | 18 ++++++++----- gomap_funcs.go | 21 ++++++++------- gomap_scan.go | 72 +++++++++++++++++++++++++++++--------------------- gomap_test.go | 3 ++- 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/gomap.go b/gomap.go index 1c03651..fba7d39 100644 --- a/gomap.go +++ b/gomap.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net" + "net/netip" ) // IPScanResult contains the results of a scan on a single ip @@ -61,7 +62,7 @@ func ScanIP(hostname string, proto string, fastscan bool, stealth bool) (*IPScan // ScanIPCtx scans a single IP for open ports func ScanIPCtx( - ctx context.Context, hostname string, proto string, fastscan bool, stealth bool, + ctx context.Context, hostname string, proto string, fastscan bool, stealth bool, ports ...int, ) (*IPScanResult, error) { laddr, err := getLocalIP() if err != nil { @@ -74,17 +75,17 @@ func ScanIPCtx( } } - return scanIPPorts(ctx, hostname, laddr, proto, fastscan, stealth) + return scanIPPorts(ctx, hostname, laddr, proto, fastscan, stealth, ports...) } // ScanRange scans every address on a CIDR for open ports -func ScanRange(proto string, fastscan bool, stealth bool) (RangeScanResult, error) { - return ScanRangeCtx(context.Background(), proto, fastscan, stealth) +func ScanRange(prefix netip.Prefix, proto string, fastscan bool, stealth bool) (RangeScanResult, error) { + return ScanRangeCtx(context.Background(), prefix, proto, fastscan, stealth) } // ScanRangeCtx scans every address on a CIDR for open ports func ScanRangeCtx( - ctx context.Context, proto string, fastscan bool, stealth bool, + ctx context.Context, prefix netip.Prefix, proto string, fastscan bool, stealth bool, ports ...int, ) (RangeScanResult, error) { laddr, err := getLocalIP() if err != nil { @@ -96,7 +97,12 @@ func ScanRangeCtx( return nil, fmt.Errorf("socket: operation not permitted") } } - return scanIPRange(ctx, laddr, proto, fastscan, stealth) + + return scanIPRange(ctx, prefix, laddr, proto, fastscan, stealth, ports...) +} + +func ScanLocal(ctx context.Context, proto string, fastscan bool, stealth bool, ports ...int) (RangeScanResult, error) { + return ScanRangeCtx(ctx, getLocalRange(), proto, fastscan, stealth, ports...) } // String with the results of a single scanned IP diff --git a/gomap_funcs.go b/gomap_funcs.go index 419bb3c..8bb207d 100644 --- a/gomap_funcs.go +++ b/gomap_funcs.go @@ -5,7 +5,7 @@ import ( "fmt" "log" "net" - "strings" + "net/netip" ) func canSocketBind(laddr string) bool { @@ -25,8 +25,8 @@ func canSocketBind(laddr string) bool { } // createHostRange converts a input ip addr string to a slice of ips on the cidr -func createHostRange(netw string) []string { - _, ipv4Net, err := net.ParseCIDR(netw) +func createHostRange(netw netip.Prefix) []string { + _, ipv4Net, err := net.ParseCIDR(netw.Masked().String()) if err != nil { log.Fatal(err) } @@ -36,7 +36,7 @@ func createHostRange(netw string) []string { finish := (start & mask) | (mask ^ 0xffffffff) var hosts []string - for i := start + 1; i <= finish-1; i++ { + for i := start; i <= finish; i++ { ip := make(net.IP, 4) binary.BigEndian.PutUint32(ip, i) hosts = append(hosts, ip.String()) @@ -46,21 +46,22 @@ func createHostRange(netw string) []string { } // getLocalRange returns local ip range or defaults on error to most common -func getLocalRange() string { +func getLocalRange() netip.Prefix { addrs, err := net.InterfaceAddrs() if err != nil { - return "192.168.1.0/24" + return netip.MustParsePrefix("192.168.1.0/24") } + for _, address := range addrs { // check the address type and if it is not a loopback the display it if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - split := strings.Split(ipnet.IP.String(), ".") - return split[0] + "." + split[1] + "." + split[2] + ".0/24" + if v4 := ipnet.IP.To4(); v4 != nil { + netip.PrefixFrom(netip.AddrFrom4([4]byte{v4[0], v4[1], v4[2], v4[3]}), 24) } } } - return "192.168.1.0/24" + + return netip.MustParsePrefix("192.168.1.0/24") } // getLocalRange returns local ip range or defaults on error to most common diff --git a/gomap_scan.go b/gomap_scan.go index 1b5b078..3be9718 100644 --- a/gomap_scan.go +++ b/gomap_scan.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "net/netip" "strconv" "sync" "time" @@ -13,17 +14,17 @@ import ( // I am fairly happy with this code since its just iterating // over scanIPPorts. Most issues are deeper in the code. func scanIPRange( - ctx context.Context, laddr string, proto string, fastscan bool, stealth bool, + ctx context.Context, iprange netip.Prefix, laddr string, proto string, fastscan bool, stealth bool, ports ...int, ) (RangeScanResult, error) { - iprange := getLocalRange() hosts := createHostRange(iprange) var results RangeScanResult for _, h := range hosts { - scan, err := scanIPPorts(ctx, h, laddr, proto, fastscan, stealth) + scan, err := scanIPPorts(ctx, h, laddr, proto, fastscan, stealth, ports...) if err != nil { continue } + results = append(results, scan) } @@ -32,7 +33,7 @@ func scanIPRange( // scanIPPorts scans a list of ports on func scanIPPorts( - ctx context.Context, hostname string, laddr string, proto string, fastscan bool, stealth bool, + ctx context.Context, hostname string, laddr string, proto string, fastscan bool, stealth bool, ports ...int, ) (*IPScanResult, error) { // checks if device is online addr, err := net.LookupIP(hostname) @@ -43,42 +44,44 @@ func scanIPPorts( // This gets the device name. ('/etc/hostname') // This is typically a good indication of if a host is 'up' // but can cause false-negatives in certain situations. - // For this reason when in fastscan mode, devices without - // names are ignored but are fully scanned in slowmode. hname, err := net.LookupAddr(hostname) - if fastscan { - if err != nil { - return nil, err - } - } else if err != nil { + if err != nil { hname = append(hname, "Unknown") } + depth := 4 + list := make(map[int]string) + + for _, p := range ports { + if svc, ok := detailedlist[p]; ok { + list[p] = svc + } + } + + if len(list) == 0 { + if fastscan { + list = commonlist + depth = 8 + } else { + list = detailedlist + depth = 16 + } + } + // Start prepping channels and vars for worker pool in := make(chan int) go func() { defer close(in) - for i := 0; i <= 65535; i++ { + for p := range list { select { case <-ctx.Done(): return - case in <- i: + case in <- p: } } }() - var list map[int]string - var depth int - - if fastscan { - list = commonlist - depth = 50 - } else { - list = detailedlist - depth = 500 - } - tasks := len(list) // Create results channel and worker function @@ -97,12 +100,12 @@ func scanIPPorts( return } - if service, ok := list[port]; ok { - if stealth { - scanPortSyn(resultChannel, proto, hostname, service, port, laddr) - } else { - scanPort(resultChannel, proto, hostname, service, port) - } + service := list[port] + + if stealth { + scanPortSyn(resultChannel, proto, hostname, service, port, laddr) + } else { + scanPort(resultChannel, proto, hostname, service, port) } } @@ -116,7 +119,12 @@ func scanIPPorts( } var results []portResult + + var wgResult sync.WaitGroup + + wgResult.Add(1) go func() { + defer wgResult.Done() // Combines all results from resultChannel and return a IPScanResult for result := range resultChannel { results = append(results, result) @@ -128,6 +136,8 @@ func scanIPPorts( close(resultChannel) + wgResult.Wait() + return &IPScanResult{ Hostname: hname[0], IP: addr, @@ -141,6 +151,7 @@ func scanIPPorts( func scanPort(resultChannel chan<- portResult, protocol, hostname, service string, port int) { result := portResult{Port: port, Service: service} address := hostname + ":" + strconv.Itoa(port) + conn, err := net.DialTimeout(protocol, address, 3*time.Second) if err != nil { result.State = false @@ -149,6 +160,7 @@ func scanPort(resultChannel chan<- portResult, protocol, hostname, service strin } defer conn.Close() + result.State = true resultChannel <- result } diff --git a/gomap_test.go b/gomap_test.go index 58218da..6f7ded9 100644 --- a/gomap_test.go +++ b/gomap_test.go @@ -1,6 +1,7 @@ package gomap_test import ( + "context" "fmt" "testing" @@ -15,7 +16,7 @@ func TestMain(m *testing.M) { ) // results, err := gomap.ScanIP("192.168.1.1", proto, fastscan, stealth) - results, err := gomap.ScanRange(proto, fastscan, stealth) + results, err := gomap.ScanLocal(context.Background(), proto, fastscan, stealth) if err != nil { panic(err) } else { From 546fe323536daf3df91bf3426649224ae8c96e72 Mon Sep 17 00:00:00 2001 From: au Date: Tue, 15 Apr 2025 15:19:05 +0300 Subject: [PATCH 4/4] do not skip unknown ports --- gomap_scan.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gomap_scan.go b/gomap_scan.go index 3be9718..7497553 100644 --- a/gomap_scan.go +++ b/gomap_scan.go @@ -55,6 +55,8 @@ func scanIPPorts( for _, p := range ports { if svc, ok := detailedlist[p]; ok { list[p] = svc + } else { + list[p] = "unknown" } }