File: pscan/go.mod
   1 module pscan
   2 
   3 go 1.18

     File: pscan/info.txt
   1 pscan [options...] [hostname/ports...]
   2 
   3 A TCP port scanner showing all open ports. Output is lines of tab-separated
   4 values (TSV), and starts with a header line, which has all column names.

     File: pscan/main.go
   1 package main
   2 
   3 import (
   4     "io"
   5     "net"
   6     "os"
   7     "strconv"
   8     "sync"
   9     "time"
  10 
  11     _ "embed"
  12 )
  13 
  14 // Note: the code is avoiding using the fmt package to save hundreds of
  15 // kilobytes on the resulting executable, which is a noticeable difference.
  16 
  17 //go:embed info.txt
  18 var info string
  19 
  20 const (
  21     // maxConcurrency is how many ports can be open at once, to save memory
  22     maxConcurrency = 100
  23 
  24     // maxPort is `calculated` to avoid typo-style bugs
  25     maxPort = 1<<16 - 1
  26 
  27     // timeout gives plenty of waiting-time to check localhost ports
  28     timeout = 500 * time.Millisecond
  29 )
  30 
  31 func main() {
  32     if len(os.Args) > 1 {
  33         switch os.Args[1] {
  34         case `-h`, `--h`, `-help`, `--help`:
  35             os.Stderr.WriteString(info)
  36             return
  37         }
  38     }
  39 
  40     pscan(os.Stdout)
  41 }
  42 
  43 type valueWriter interface {
  44     io.Writer
  45     io.StringWriter
  46 }
  47 
  48 func pscan(w valueWriter) {
  49     _, err := w.WriteString("date\ttime\tTCP port\n")
  50     if err != nil {
  51         return
  52     }
  53 
  54     // quit isn't strictly needed, as this app could just quit abruptly, but
  55     // it makes the scanning funcs more general/reusable when copied verbatim
  56     quit := make(chan struct{})
  57     defer close(quit)
  58 
  59     used := make(chan int)
  60     go pscanDispatch(quit, used)
  61 
  62     for port := range used {
  63         w.WriteString(time.Now().Format("2006-01-02\t15:04:05"))
  64         w.WriteString("\t")
  65         w.WriteString(strconv.FormatInt(int64(port), 10))
  66         _, err := w.WriteString("\n")
  67 
  68         // assume output-errors are always due to later tools in a pipe only
  69         // wanting part of this app's output, so always quit successfully and
  70         // (relatively) quickly
  71         if err != nil {
  72             quit <- struct{}{}
  73             return
  74         }
  75     }
  76 }
  77 
  78 // pscanDispatch simplifies control-flow for func pscan
  79 func pscanDispatch(quit <-chan struct{}, used chan int) {
  80     defer close(used)
  81 
  82     // permissions limits how many ports are being checked at any time,
  83     // avoiding using hundreds of megabytes in the process, while still
  84     // being done checking all ports in a few seconds
  85     permissions := make(chan struct{}, maxConcurrency)
  86     defer close(permissions)
  87 
  88     var tasks sync.WaitGroup
  89 
  90     for port := 1; port <= maxPort; port++ {
  91         select {
  92         case <-quit:
  93             tasks.Wait()
  94             return
  95         default:
  96             // default clause avoids hanging on the quit-signaller,
  97             // preventing any task from ever starting
  98         }
  99 
 100         permissions <- struct{}{}
 101         tasks.Add(1)
 102 
 103         go func(port int) {
 104             defer tasks.Done()
 105             defer func() { <-permissions }()
 106             if checkPortTCP(port) {
 107                 used <- port
 108             }
 109         }(port)
 110     }
 111 
 112     tasks.Wait()
 113 }
 114 
 115 // checkPortTCP handles each concurrently-dispatched port-checking task
 116 func checkPortTCP(port int) (inUse bool) {
 117     addr := `:` + strconv.FormatInt(int64(port), 10)
 118     conn, err := net.DialTimeout(`tcp`, addr, timeout)
 119     if err != nil {
 120         return false
 121     }
 122     conn.Close()
 123     return true
 124 }

     File: pscan/mit-license.txt
   1 The MIT License (MIT)
   2 
   3 Copyright © 2024 pacman64
   4 
   5 Permission is hereby granted, free of charge, to any person obtaining a copy of
   6 this software and associated documentation files (the “Software”), to deal
   7 in the Software without restriction, including without limitation the rights to
   8 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
   9 of the Software, and to permit persons to whom the Software is furnished to do
  10 so, subject to the following conditions:
  11 
  12 The above copyright notice and this permission notice shall be included in all
  13 copies or substantial portions of the Software.
  14 
  15 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21 SOFTWARE.