Skip to content

Commit

Permalink
Initial version.
Browse files Browse the repository at this point in the history
Includes concurrency, concurrency controls (number of agents and rate limit),
and timing, with some performance counters; inludes port-to-service mapping
for open ports; includes progress bar; and includes unit tests.
  • Loading branch information
Webb Scales committed Oct 11, 2016
1 parent 841cd11 commit a945fcf
Show file tree
Hide file tree
Showing 13 changed files with 1,381 additions and 1 deletion.
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,46 @@
# webbscan
Port scanner, written in golang

This is intended to be a simple(ish) port scanner tool, written as a
demonstration of my programming abilities and as an exercise in learning the
Go programming language.

The source code is divided up in the several packages, nominally to maximize
reusability of each module:

portserv - a simple library for translating TCP and UDP port numbers into
service names, using /etc/services
progbar - a simple ASCII-text-based "progress bar" which gives the user
feedback during the portscan, visually displaying the progress of
the scan, as well as a "spining" effect showing that probes are
actively being sent.
tcpProbe - a simple library for probing sockets.
vdiag - a handy package for producing diagnositic output.
workflow - a library for administering task execution, supporting concurrent
as well as rate-limited dispatching.
webbscan - the port scanner tool.


Highlights of the code include:
- understanding of various Go syntax, constructions, and usage, including
arrays, maps, slices, ranges, structures, methods, strings,
initialization, interfaces, enumerations (using iota), bytes-Buffers,
Writers, command line flag support, goroutines, channels, timers,
functions-as-parameters, and closures
- regular-expression-based parsing
- concurrency and synchronization
- Go unit testing support, test-case objects, use of interfaces for mocking
functions


The tool provides several command-line switches which control its execution:

-agents (default 8): the number of concurrent probes
-host (default "127.0.0.1"): the target host to probe
-protocol (default "tcp"): Protocol ("tcp" or "udp")
-rate (default unlimited): the maximum number of probes to be sent per
second (0: unlimited)
-verbose (default none): The level of verbosity for diagnostic messages
(-v is a shorthand for "level 2")

In addition to the tool source code, the source includes unit tests for
(nearly) all functions.
44 changes: 44 additions & 0 deletions portserv/portserv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Package portserv provides interfaces which translate TCP and UDP ports into
// service names.
package portserv

import (
"fmt"
"io/ioutil"
"regexp"
"strconv"
)

var tcpServices map[int]string = make(map[int]string)
var udpServices map[int]string = make(map[int]string)

func init() {
re := regexp.MustCompile(`\n(\S+)\s+(\d+)/(tcp|udp)`)
text, err := ioutil.ReadFile("/etc/services")
if err != nil {
panic(err)
}
matches := re.FindAllStringSubmatch(string(text), -1)
if matches == nil {
panic("Failed to parse services file")
}

for _, v := range matches {
port, err := strconv.Atoi(v[2])
if err != nil {
panic(fmt.Sprintf("regex match returned non-numeric number: \"%s\"", v[1]))
}
switch v[3] {
case "tcp":
tcpServices[port] = v[1]
case "udp":
udpServices[port] = v[1]
default:
panic(fmt.Sprintf("Unexpected protocol value: \"%s\"",
v[3]))
}
}
}

func Tcp(port int) string { return tcpServices[port] }
func Udp(port int) string { return udpServices[port] }
38 changes: 38 additions & 0 deletions portserv/portserv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Unit tests for package portserv.
package portserv

import (
"net"
"testing"
)

func check(t *testing.T, network string, getService func(int) string) {
var checked int
for i := 1; i <= 65535; i++ {
service := getService(i)
if service != "" {
port, err := net.LookupPort(network, service)
if err != nil {
t.Errorf("Port %d(%s): error looking up port for returned service \"%s\": %v\n", i, network, service, err)
} else if port != i {
t.Errorf("Port %d(%s): got mismatched port (%d) for returned service \"%s\".\n", i, network, port, service)
}
checked++
}
}

if checked == 0 {
t.Errorf("No services returned for %s ports.\n", network)
} else {
t.Logf("Confirmed matches for %d %s ports/services.\n",
checked, network)
}
}

func TestTcp(t *testing.T) {
check(t, "tcp", Tcp)
}

func TestUdp(t *testing.T) {
check(t, "udp", Udp)
}
80 changes: 80 additions & 0 deletions progbar/progbar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Package progbar provides a simple ASCII progress bar
//
// After creating the bar with New(), the bar can be painted on the screen (or
// repainted again) using Paint(), it can be updated using Update(), and it
// can be finished with Done(). The function Spin() can be used to show
// intermediate activity by causing a spinning effect at the end of the bar.
package progbar

import (
"io"
"strings"
)

// The magic strings which, when printed in sequence, make the spinner appear
// to spin.
var spinStrs []string = []string{"-\b", "\\\b", "|\b", "/\b"}

// The Bar structure represents the parameters and state of the progress bar.
type Bar struct {
// The width of the bar on the screen in columns.
width int
// The total size of the bar in "progress units".
total int
// The current amount of progress in the bar in "progress units".
current int
// The current orientation of the end-of-bar "spinner".
curSpin int
// The Writer used to display the bar.
w io.Writer
}

// New creates a new bar which will grow to the specified width as the number
// of "progress units" approaches the specified size. The specified Writer is
// used to display the bar.
func New(width int, size int, w io.Writer) *Bar {
if width <= 0 || size <= 0 || w == nil {
return nil
}
return &Bar{width: width, total: size, w: w}
}

// Paint displays an empty progress bar on the screen with vertical bars
// marking the beginning and end, fills the bar up to the current progress
// point, and leaves the cursor at the next column.
func (b *Bar) Paint() {
s := []string{
strings.Repeat(" ", b.width+1), // Move the cursor to the right
"|\r|", // Last bar, return, first bar
strings.Repeat("=", b.current*b.width/b.total), // Any progress
}
io.WriteString(b.w, strings.Join(s, ""))
}

// Update advances the bar by one "progress unit" and, if appropriate, adds a
// character to the bar on the screen.
func (b *Bar) Update() {
b.current++
if b.current%(b.total/b.width) == 0 {
io.WriteString(b.w, "=")
}
}

// Done erases the bar.
func (b *Bar) Done() {
b.current = b.total // For completeness

s := []string{
"\r", // Return the cursor to the beginning of the line
strings.Repeat(" ", b.width+2), // Clear the whole line
"\r", // Like it was never there
}
io.WriteString(b.w, strings.Join(s, ""))
}

// Spin a little "wheel" at the end of the progress bar to indicate
// intermediate activity.
func (b *Bar) Spin() {
b.curSpin = (b.curSpin + 1) & 0x3
io.WriteString(b.w, spinStrs[b.curSpin])
}
Loading

0 comments on commit a945fcf

Please sign in to comment.