diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 97d3640..ee134fb 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -25,6 +25,11 @@ jobs: - name: Install dependencies run: sudo apt update && sudo apt install -y make libpcap-dev + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.55.2 + - name: Run Test run: | make test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d2e619..9d6c101 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,5 +25,10 @@ jobs: - name: Build run: make + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.55.2 + - name: Test run: make test diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..b202f0a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,20 @@ +linters: + enable: + - revive +linters-settings: + revive: + rules: + - name: exported +issues: + exclude-rules: + - path: '(.+)_test\.go' + linters: + - errcheck + - path: '(.+)test-helper.go' + linters: + - errcheck + include: + - EXC0012 # EXC0012 revive: Annoying issue about not having a comment. The rare codebase has such comments + - EXC0013 # EXC0013 revive: Annoying issue about not having a comment. The rare codebase has such comments + - EXC0014 # EXC0014 revive: Annoying issue about not having a comment. The rare codebase has such comments + - EXC0015 # EXC0015 revive: Annoying issue about not having a comment. The rare codebase has such comments diff --git a/Makefile b/Makefile index e814084..953569d 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ mock: .PHONY: lint lint: - golint -set_exit_status ./... + golangci-lint run .PHONY: test test: @@ -116,7 +116,9 @@ test-report: .PHONY: deps deps: go install go.uber.org/mock/mockgen@latest - go install golang.org/x/lint/golint@latest + curl \ + -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ + sh -s -- -b $(shell go env GOPATH)/bin v1.55.2 # remove buid directory and installed executable .PHONY: clean diff --git a/README.md b/README.md index 0f18a66..5998ba7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # go-lanscan -![Coverage](https://img.shields.io/badge/Coverage-92.6%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-92.5%25-brightgreen) A network cli and golang package that allows you to perform arp and syn scanning on a local area network. diff --git a/internal/cli/root.go b/internal/cli/root.go index a81896a..eed973b 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -27,7 +27,7 @@ func printConfiguration( listenPort uint16, timing string, vendorInfo, - printJson, + printJSON, arpOnly, progress bool, outFile string, @@ -78,7 +78,7 @@ func printConfiguration( configTable.AppendRow(table.Row{ "json", - printJson, + printJSON, }) configTable.AppendRow(table.Row{ @@ -91,15 +91,21 @@ func printConfiguration( progress, }) + configTable.AppendRow(table.Row{ + "outFile", + outFile, + }) + configTable.Render() } +// Root returns root command for cli func Root( runner core.Runner, userNet network.Network, vendorRepo oui.VendorRepo, ) (*cobra.Command, error) { - var printJson bool + var printJSON bool var noProgress bool var ports []string var timing string @@ -177,7 +183,7 @@ func Root( portLen, noProgress, arpOnly, - printJson, + printJSON, outFile, ) @@ -191,7 +197,7 @@ func Root( listenPort, timing, vendorInfo, - printJson, + printJSON, arpOnly, !noProgress, outFile, @@ -203,7 +209,7 @@ func Root( } cmd.Flags().StringVar(&timing, "timing", "100µs", "set time between packet sends - the faster you send the less accurate the result will be") - cmd.Flags().BoolVar(&printJson, "json", false, "output json instead of table text") + cmd.Flags().BoolVar(&printJSON, "json", false, "output json instead of table text") cmd.Flags().BoolVar(&arpOnly, "arp-only", false, "only perform arp scanning (skip syn scanning)") cmd.Flags().BoolVar(&noProgress, "no-progress", false, "disable all output except for final results") cmd.Flags().StringSliceVarP(&ports, "ports", "p", []string{"1-65535"}, "target ports") diff --git a/internal/core/core.go b/internal/core/core.go index c15078b..756edfa 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -20,6 +20,7 @@ import ( "github.com/rs/zerolog" ) +// DeviceResult represents a discovered network device type DeviceResult struct { IP net.IP `json:"ip"` MAC net.HardwareAddr `json:"mac"` @@ -28,6 +29,7 @@ type DeviceResult struct { OpenPorts []scanner.Port `json:"openPorts"` } +// Serializable returns a serializable version of DeviceResult func (r *DeviceResult) Serializable() interface{} { return struct { IP string `json:"ip"` @@ -44,10 +46,12 @@ func (r *DeviceResult) Serializable() interface{} { } } +// Results data structure for holding discovered network devices type Results struct { Devices []*DeviceResult `json:"devices"` } +// MarshalJSON returns marshaled JSON of Results func (r *Results) MarshalJSON() ([]byte, error) { data := []interface{}{} @@ -58,9 +62,10 @@ func (r *Results) MarshalJSON() ([]byte, error) { return json.Marshal(data) } +// Core implements the Runner interface for performing network scanning type Core struct { arpOnly bool - printJson bool + printJSON bool noProgress bool outFile string portLen int @@ -75,6 +80,7 @@ type Core struct { log logger.Logger } +// New returns a new instance of Core func New() *Core { return &Core{ requestNotifier: make(chan *scanner.Request), @@ -83,13 +89,14 @@ func New() *Core { } } +// Initialize initializes the Core before performing network scanning func (c *Core) Initialize( coreScanner scanner.Scanner, targetLen int, portLen int, noProgress bool, arpOnly bool, - printJson bool, + printJSON bool, outFile string, ) { pw := progressWriter() @@ -116,10 +123,11 @@ func (c *Core) Initialize( c.synTracker = &progress.Tracker{Message: "starting syn scan"} c.noProgress = noProgress c.arpOnly = arpOnly - c.printJson = printJson + c.printJSON = printJSON c.outFile = outFile } +// Run executes a network scan func (c *Core) Run() error { start := time.Now() @@ -256,7 +264,7 @@ func (c *Core) printArpResults() { c.mux.RLock() defer c.mux.RUnlock() - if c.printJson { + if c.printJSON { data, err := c.results.MarshalJSON() if err != nil { @@ -300,7 +308,7 @@ func (c *Core) printSynResults() { c.mux.RLock() defer c.mux.RUnlock() - if c.printJson { + if c.printJSON { data, err := c.results.MarshalJSON() if err != nil { diff --git a/internal/core/interface.go b/internal/core/interface.go index da2035f..1aa7a2b 100644 --- a/internal/core/interface.go +++ b/internal/core/interface.go @@ -6,6 +6,7 @@ import "github.com/robgonnella/go-lanscan/pkg/scanner" //go:generate mockgen -destination=../mock/core/core.go -package=mock_core . Runner +// Runner interface for performing network scanning type Runner interface { Initialize( coreScanner scanner.Scanner, @@ -13,7 +14,7 @@ type Runner interface { portLen int, noProgress bool, arpOnly bool, - printJson bool, + printJSON bool, outFile string, ) Run() error diff --git a/internal/info/version.go b/internal/info/version.go index d57a9cb..613f1fa 100644 --- a/internal/info/version.go +++ b/internal/info/version.go @@ -2,4 +2,5 @@ package info +// VERSION the version of this library var VERSION = "v1.14.0" diff --git a/internal/logger/debug.go b/internal/logger/debug.go index b02ad51..314584f 100644 --- a/internal/logger/debug.go +++ b/internal/logger/debug.go @@ -29,14 +29,17 @@ func init() { } } -func NewDebugLogger() DebugLogger { - return debugLogger -} - +// DebugLogger represents a Logger implementation that is only turned on +// when build with the "debug" tag type DebugLogger struct { zl *zerolog.Logger } +// NewDebugLogger returns a instance of DebugLogger +func NewDebugLogger() DebugLogger { + return debugLogger +} + // Info wrapper around zerolog Info func (l DebugLogger) Info() *zerolog.Event { return l.zl.Info() diff --git a/internal/mock/scripts/bump-version/version/version.go b/internal/mock/scripts/bump-version/version/version.go index a2874f6..820405c 100644 --- a/internal/mock/scripts/bump-version/version/version.go +++ b/internal/mock/scripts/bump-version/version/version.go @@ -104,7 +104,7 @@ func (m *MockVersionGenerator) EXPECT() *MockVersionGeneratorMockRecorder { } // Generate mocks base method. -func (m *MockVersionGenerator) Generate(arg0 version.VersionData) error { +func (m *MockVersionGenerator) Generate(arg0 version.Data) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Generate", arg0) ret0, _ := ret[0].(error) diff --git a/internal/scripts/bump-version/version/generator.go b/internal/scripts/bump-version/version/generator.go index 3f5ffef..35a6e01 100644 --- a/internal/scripts/bump-version/version/generator.go +++ b/internal/scripts/bump-version/version/generator.go @@ -8,6 +8,7 @@ import ( "path/filepath" ) +// TemplateGenerator implements the VersionGenerator interface using templates type TemplateGenerator struct { outFile string outDir string @@ -15,6 +16,7 @@ type TemplateGenerator struct { templateName string } +// NewTemplateGenerator returns a new instance of TemplateGenerator func NewTemplateGenerator(outFile, templatePath string) *TemplateGenerator { return &TemplateGenerator{ outFile: outFile, @@ -24,7 +26,8 @@ func NewTemplateGenerator(outFile, templatePath string) *TemplateGenerator { } } -func (t *TemplateGenerator) Generate(data VersionData) error { +// Generate implements the Generate interface method using templates +func (t *TemplateGenerator) Generate(data Data) error { if err := os.MkdirAll(t.outDir, 0751); err != nil { return err } diff --git a/internal/scripts/bump-version/version/generator_test.go b/internal/scripts/bump-version/version/generator_test.go index 971f5c9..7c8f032 100644 --- a/internal/scripts/bump-version/version/generator_test.go +++ b/internal/scripts/bump-version/version/generator_test.go @@ -32,7 +32,7 @@ func TestTemplateGenerator(t *testing.T) { generator := version.NewTemplateGenerator(outFile, templatePath) - data := version.VersionData{ + data := version.Data{ VERSION: "v2.2.2", } diff --git a/internal/scripts/bump-version/version/git.go b/internal/scripts/bump-version/version/git.go index 79d2852..abbb3d2 100644 --- a/internal/scripts/bump-version/version/git.go +++ b/internal/scripts/bump-version/version/git.go @@ -4,22 +4,27 @@ package version import "os/exec" +// Git implementation of the VersionControl interface using git type Git struct{} +// NewGit returns a new instance of Git func NewGit() *Git { return &Git{} } +// Add implements the Add method using git func (g *Git) Add(filePath string) error { cmd := exec.Command("git", "add", filePath) return cmd.Run() } +// Commit implements the Commit method using git func (g *Git) Commit(message string) error { cmd := exec.Command("git", "commit", "-m", message) return cmd.Run() } +// Tag implements the tag method using git func (g *Git) Tag(version string) error { cmd := exec.Command("git", "tag", "-m", version, version) return cmd.Run() diff --git a/internal/scripts/bump-version/version/interface.go b/internal/scripts/bump-version/version/interface.go index 45b4577..2aa587a 100644 --- a/internal/scripts/bump-version/version/interface.go +++ b/internal/scripts/bump-version/version/interface.go @@ -2,18 +2,24 @@ package version -type VersionData struct { +// Data represents the version data passed to template generator +type Data struct { VERSION string } //go:generate mockgen -destination=../../../mock/scripts/bump-version/version/version.go -package=mock_version . VersionControl,VersionGenerator +// nolint:revive +// VersionControl interface representing a version control system type VersionControl interface { Add(filePath string) error Commit(message string) error Tag(version string) error } +// nolint:revive +// VersionGenerator interface representing a generator of version files +// for this library type VersionGenerator interface { - Generate(data VersionData) error + Generate(data Data) error } diff --git a/internal/scripts/bump-version/version/version.go b/internal/scripts/bump-version/version/version.go index a4859dd..50879dc 100644 --- a/internal/scripts/bump-version/version/version.go +++ b/internal/scripts/bump-version/version/version.go @@ -7,12 +7,15 @@ import ( "fmt" ) +// BumpData represents the data required to perform a version bump for this +// library type BumpData struct { Version string OutFile string TemplatePath string } +// Bump executes a version bump for this library func Bump(data BumpData, vg VersionGenerator, vc VersionControl) error { if string(data.Version[0]) != "v" { return errors.New("version must begin with a \"v\"") diff --git a/internal/scripts/bump-version/version/version_test.go b/internal/scripts/bump-version/version/version_test.go index fb1401b..088a172 100644 --- a/internal/scripts/bump-version/version/version_test.go +++ b/internal/scripts/bump-version/version/version_test.go @@ -51,7 +51,7 @@ func TestBumpVersion(t *testing.T) { TemplatePath: templatePath, } - data := version.VersionData{ + data := version.Data{ VERSION: versionStr, } @@ -81,7 +81,7 @@ func TestBumpVersion(t *testing.T) { TemplatePath: templatePath, } - data := version.VersionData{ + data := version.Data{ VERSION: versionStr, } @@ -109,7 +109,7 @@ func TestBumpVersion(t *testing.T) { TemplatePath: templatePath, } - data := version.VersionData{ + data := version.Data{ VERSION: versionStr, } @@ -138,7 +138,7 @@ func TestBumpVersion(t *testing.T) { TemplatePath: templatePath, } - data := version.VersionData{ + data := version.Data{ VERSION: versionStr, } @@ -170,7 +170,7 @@ func TestBumpVersion(t *testing.T) { TemplatePath: templatePath, } - data := version.VersionData{ + data := version.Data{ VERSION: versionStr, } diff --git a/internal/templates/version.go.tmpl b/internal/templates/version.go.tmpl index f373d8f..61f26ea 100644 --- a/internal/templates/version.go.tmpl +++ b/internal/templates/version.go.tmpl @@ -2,4 +2,5 @@ package info +// VERSION the version of this library var VERSION = "{{ .VERSION }}" diff --git a/internal/util/targets.go b/internal/util/targets.go index 2b1a90a..4731198 100644 --- a/internal/util/targets.go +++ b/internal/util/targets.go @@ -30,6 +30,7 @@ func LoopNetIPHosts(ipnet *net.IPNet, f func(ip net.IP) error) error { func IPHostTotal(ipnet *net.IPNet) int { total := 0 + // nolint:errcheck LoopNetIPHosts(ipnet, func(ip net.IP) error { total++ return nil @@ -38,6 +39,7 @@ func IPHostTotal(ipnet *net.IPNet) int { return total } +// LoopTargets helper to prevent storing entire target list in memory func LoopTargets(targets []string, f func(target net.IP) error) error { for _, t := range targets { if cidrSuffix.MatchString(t) { @@ -75,9 +77,12 @@ func LoopTargets(targets []string, f func(target net.IP) error) error { return nil } +// TotalTargets returns count of targets - helper to prevent storing entire +// target list in memory func TotalTargets(targets []string) int { total := 0 + // nolint:errcheck LoopTargets(targets, func(ip net.IP) error { total++ return nil @@ -86,8 +91,11 @@ func TotalTargets(targets []string) int { return total } +// TargetsHas helper to determine if targets list includes a specific net.IP func TargetsHas(targets []string, t net.IP) bool { has := false + + // nolint:errcheck LoopTargets(targets, func(v net.IP) error { if v.Equal(t) { has = true @@ -143,6 +151,7 @@ func LoopPorts(ports []string, f func(p uint16) error) error { func PortTotal(ports []string) int { total := 0 + // nolint:errcheck LoopPorts(ports, func(p uint16) error { total++ return nil diff --git a/pkg/internal/test-helper/test-helper.go b/pkg/internal/test-helper/test-helper.go index 02a4dc5..be2fbd3 100644 --- a/pkg/internal/test-helper/test-helper.go +++ b/pkg/internal/test-helper/test-helper.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -package test_helper +package testhelper import ( "net" @@ -9,6 +9,7 @@ import ( "github.com/google/gopacket/layers" ) +// NewArpReplyReadResult creates mock ARP reply packet data func NewArpReplyReadResult(srcIP net.IP, srcHwAddr net.HardwareAddr) (data []byte, ci gopacket.CaptureInfo, err error) { eth := layers.Ethernet{ SrcMAC: srcHwAddr, @@ -43,6 +44,7 @@ func NewArpReplyReadResult(srcIP net.IP, srcHwAddr net.HardwareAddr) (data []byt return buf.Bytes(), gopacket.CaptureInfo{}, nil } +// NewArpRequestReadResult creates mock ARP request packet data func NewArpRequestReadResult() (data []byte, ci gopacket.CaptureInfo, err error) { eth := layers.Ethernet{ SrcMAC: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, @@ -77,6 +79,7 @@ func NewArpRequestReadResult() (data []byte, ci gopacket.CaptureInfo, err error) return buf.Bytes(), gopacket.CaptureInfo{}, nil } +// NewSynWithAckResponsePacketBytes creates mock SYN response packet data func NewSynWithAckResponsePacketBytes( srcIP net.IP, srcPort uint16, diff --git a/pkg/network/helper.go b/pkg/network/helper.go index 11ef5ad..348b407 100644 --- a/pkg/network/helper.go +++ b/pkg/network/helper.go @@ -12,6 +12,7 @@ import ( "github.com/jackpal/gateway" ) +// IncrementIP increments a net.IP by 1 func IncrementIP(ip net.IP) { for j := len(ip) - 1; j >= 0; j-- { ip[j]++ diff --git a/pkg/network/interface.go b/pkg/network/interface.go index b70ad20..98bbea7 100644 --- a/pkg/network/interface.go +++ b/pkg/network/interface.go @@ -6,6 +6,7 @@ import "net" //go:generate mockgen -destination=../../mock/network/network.go -package=mock_network . Network +// Network interface for accessing network properties type Network interface { Hostname() string Interface() *net.Interface diff --git a/pkg/network/network.go b/pkg/network/network.go index c3da343..d4f81a4 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -6,6 +6,7 @@ import ( "net" ) +// UserNetwork data structure for implementing Network interface type UserNetwork struct { hostname string gateway net.IP @@ -15,6 +16,7 @@ type UserNetwork struct { cidr string } +// NewDefaultNetwork returns a new instance of UserNetwork func NewDefaultNetwork() (*UserNetwork, error) { info, err := getDefaultNetworkInfo() @@ -32,6 +34,8 @@ func NewDefaultNetwork() (*UserNetwork, error) { }, nil } +// NewNetworkFromInterfaceName returns a UserNetwork instance from the +// provided interface name func NewNetworkFromInterfaceName(interfaceName string) (*UserNetwork, error) { info, err := getNetworkInfoFromInterfaceName(interfaceName) @@ -49,26 +53,32 @@ func NewNetworkFromInterfaceName(interfaceName string) (*UserNetwork, error) { }, nil } +// Hostname returns the hostname for this host func (n *UserNetwork) Hostname() string { return n.hostname } +// Gateway returns the default network gateway for this host func (n *UserNetwork) Gateway() net.IP { return n.gateway } +// UserIP returns the default IP address assigned to this network's interface func (n *UserNetwork) UserIP() net.IP { return n.userIP } +// IPNet returns the *net.IPNet associated with this network's interface func (n *UserNetwork) IPNet() *net.IPNet { return n.ipnet } +// Interface returns this network's interface func (n *UserNetwork) Interface() *net.Interface { return n.iface } +// Cidr returns the cidr block associated with this network's interface func (n *UserNetwork) Cidr() string { return n.cidr } diff --git a/pkg/oui/helper.go b/pkg/oui/helper.go index d003195..de8337b 100644 --- a/pkg/oui/helper.go +++ b/pkg/oui/helper.go @@ -7,6 +7,8 @@ import ( "path" ) +// GetDefaultVendorRepo returns the default implementation of VendorRepo +// which uses github.com/klauspost/oui func GetDefaultVendorRepo() (*OUIVendorRepo, error) { ouiTxt, err := GetDefaultOuiTxtPath() @@ -23,6 +25,7 @@ func GetDefaultVendorRepo() (*OUIVendorRepo, error) { return repo, nil } +// GetDefaultOuiTxtPath returns the default path for the static oui.txt database func GetDefaultOuiTxtPath() (*string, error) { home, err := os.UserHomeDir() diff --git a/pkg/oui/interface.go b/pkg/oui/interface.go index 1f91753..30192ee 100644 --- a/pkg/oui/interface.go +++ b/pkg/oui/interface.go @@ -8,10 +8,12 @@ import ( //go:generate mockgen -destination=../../mock/oui/oui.go -package=mock_oui . VendorRepo +// VendorResult represents a vendor result when querying the vendor repo type VendorResult struct { Name string } +// VendorRepo interface for looking up vendor info by MAC address type VendorRepo interface { UpdateVendors() error Query(mac net.HardwareAddr) (*VendorResult, error) diff --git a/pkg/oui/oui.go b/pkg/oui/oui.go index 06954bc..a734141 100644 --- a/pkg/oui/oui.go +++ b/pkg/oui/oui.go @@ -14,11 +14,14 @@ import ( kloui "github.com/klauspost/oui" ) +// nolint:all +// OUIVendorRepo oui implementation of VendorRepo type OUIVendorRepo struct { ouiTxt string db kloui.StaticDB } +// NewOUIVendorRepo returns a new instance of OUIVendorRepo func NewOUIVendorRepo(ouiTxt string) (*OUIVendorRepo, error) { repo := &OUIVendorRepo{ouiTxt: ouiTxt} @@ -31,6 +34,7 @@ func NewOUIVendorRepo(ouiTxt string) (*OUIVendorRepo, error) { return repo, nil } +// UpdateVendors implements the UpdateVendors method in VendorRepo interface func (r *OUIVendorRepo) UpdateVendors() error { dir := filepath.Dir(r.ouiTxt) @@ -65,6 +69,7 @@ func (r *OUIVendorRepo) UpdateVendors() error { return r.loadDatabase() } +// Query implements the Query method in VendorRepo interface func (r *OUIVendorRepo) Query(mac net.HardwareAddr) (*VendorResult, error) { result := &VendorResult{ Name: "unknown", diff --git a/pkg/scanner/arpscan.go b/pkg/scanner/arpscan.go index e03f94b..6bf38a8 100644 --- a/pkg/scanner/arpscan.go +++ b/pkg/scanner/arpscan.go @@ -19,6 +19,7 @@ import ( "github.com/robgonnella/go-lanscan/pkg/oui" ) +// ArpScanner implements the Scanner interface for ARP scanning type ArpScanner struct { ctx context.Context cancel context.CancelFunc @@ -38,10 +39,11 @@ type ArpScanner struct { debug logger.DebugLogger } +// NewArpScanner returns a new instance of ArpScanner func NewArpScanner( targets []string, networkInfo network.Network, - options ...ScannerOption, + options ...Option, ) *ArpScanner { ctx, cancel := context.WithCancel(context.Background()) @@ -68,10 +70,13 @@ func NewArpScanner( return scanner } +// Results returns the results channel for notifying when a +// target arp reply is detected func (s *ArpScanner) Results() chan *ScanResult { return s.resultChan } +// Scan implements the Scan method for ARP scanning func (s *ArpScanner) Scan() error { fields := map[string]interface{}{ "interface": s.networkInfo.Interface().Name, @@ -136,6 +141,7 @@ func (s *ArpScanner) Scan() error { return err } +// Stop stops the scanner func (s *ArpScanner) Stop() { s.cancel() @@ -144,18 +150,24 @@ func (s *ArpScanner) Stop() { } } +// SetTiming sets the timing duration for how long to wait in-between packet +// sends for ARP requests func (s *ArpScanner) SetTiming(d time.Duration) { s.timing = d } +// SetRequestNotifications sets the channel for notifying when ARP +// requests are sent func (s *ArpScanner) SetRequestNotifications(c chan *Request) { s.requestNotifier = c } +// SetIdleTimeout sets the idle timeout for this scanner func (s *ArpScanner) SetIdleTimeout(duration time.Duration) { s.idleTimeout = duration } +// IncludeVendorInfo sets whether or not to include vendor info in the scan func (s *ArpScanner) IncludeVendorInfo(repo oui.VendorRepo) { s.vendorRepo = repo if err := s.vendorRepo.UpdateVendors(); err != nil { @@ -163,6 +175,7 @@ func (s *ArpScanner) IncludeVendorInfo(repo oui.VendorRepo) { } } +// SetPacketCapture sets the data structure used for capture packets func (s *ArpScanner) SetPacketCapture(cap PacketCapture) { s.cap = cap } diff --git a/pkg/scanner/arpscan_test.go b/pkg/scanner/arpscan_test.go index b192b16..f143327 100644 --- a/pkg/scanner/arpscan_test.go +++ b/pkg/scanner/arpscan_test.go @@ -38,14 +38,14 @@ func TestArpScanner(t *testing.T) { mockIncludedArpSrcIP := net.ParseIP("172.17.1.1") t.Run("returns immediately if already scanning", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) arpScanner := scanner.NewArpScanner( []string{}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) mockNetInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) @@ -53,13 +53,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) mockNetInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -85,13 +85,13 @@ func TestArpScanner(t *testing.T) { }) t.Run("returns error if PacketCapture.OpenLive return error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) arpScanner := scanner.NewArpScanner( []string{}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) mockErr := errors.New("mock open-live error") @@ -99,7 +99,7 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), @@ -112,7 +112,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("prints debug message if reading packets returns error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -120,7 +120,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) wg := sync.WaitGroup{} @@ -131,13 +131,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP) mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -172,7 +172,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("performs arp scan on default network info", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -180,7 +180,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) wg := sync.WaitGroup{} @@ -191,13 +191,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP) mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -227,7 +227,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("performs arp scan on provided targets", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -235,7 +235,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) wg := sync.WaitGroup{} @@ -245,13 +245,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -281,7 +281,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("ignores non arp reply packets", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -289,7 +289,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) wg := sync.WaitGroup{} @@ -299,13 +299,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -332,7 +332,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("ignores arp reply packets that originate from scanning host", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -340,7 +340,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) wg := sync.WaitGroup{} @@ -350,13 +350,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -386,7 +386,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("process valid arp reply packet", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -394,7 +394,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) wg := sync.WaitGroup{} @@ -404,13 +404,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -440,7 +440,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("process valid arp reply packet and includes vendor info", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) vendorRepo := mock_oui.NewMockVendorRepo(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) @@ -451,7 +451,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), scanner.WithVendorInfo(vendorRepo), ) @@ -462,13 +462,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -505,7 +505,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("handles vendor query error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) vendorRepo := mock_oui.NewMockVendorRepo(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) @@ -516,7 +516,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), scanner.WithVendorInfo(vendorRepo), ) @@ -527,13 +527,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -568,7 +568,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("panics if fails to update static vendors", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) vendorRepo := mock_oui.NewMockVendorRepo(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) @@ -578,7 +578,7 @@ func TestArpScanner(t *testing.T) { scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), scanner.WithVendorInfo(vendorRepo), ) } @@ -587,7 +587,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("sends request notifications", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -602,7 +602,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), scanner.WithRequestNotifications(requestNotifier), ) @@ -613,13 +613,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -649,7 +649,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("handles serialize layers error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) @@ -663,7 +663,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), scanner.WithRequestNotifications(requestNotifier), ) @@ -671,13 +671,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers( + capture.EXPECT().SerializeLayers( gomock.Any(), gomock.Any(), gomock.Any()). @@ -699,7 +699,7 @@ func TestArpScanner(t *testing.T) { }) t.Run("handles write packet data error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) mockNetInfo := mock_network.NewMockNetwork(ctrl) @@ -713,7 +713,7 @@ func TestArpScanner(t *testing.T) { arpScanner := scanner.NewArpScanner( []string{"172.17.1.1"}, mockNetInfo, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), scanner.WithRequestNotifications(requestNotifier), ) @@ -721,13 +721,13 @@ func TestArpScanner(t *testing.T) { mockNetInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() mockNetInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().WritePacketData(gomock.Any()).DoAndReturn(func(data []byte) (err error) { return errors.New("mock write packet data error") diff --git a/pkg/scanner/capture.go b/pkg/scanner/capture.go index 0aa991f..8c603c6 100644 --- a/pkg/scanner/capture.go +++ b/pkg/scanner/capture.go @@ -11,10 +11,14 @@ import ( type defaultPacketCapture struct{} +// OpenLive uses gopacket/pcap to implement the OpenLive method of +// PacketCapture interface func (pc *defaultPacketCapture) OpenLive(device string, snaplen int32, promisc bool, timeout time.Duration) (handle PacketCaptureHandle, _ error) { return pcap.OpenLive(device, snaplen, promisc, timeout) } +// SerializeLayers uses gopacket to implement the SerializeLayers method of +// PacketCapture interface func (pc *defaultPacketCapture) SerializeLayers(w gopacket.SerializeBuffer, opts gopacket.SerializeOptions, layers ...gopacket.SerializableLayer) error { return gopacket.SerializeLayers(w, opts, layers...) } diff --git a/pkg/scanner/fullscan.go b/pkg/scanner/fullscan.go index 47b3195..8e52abc 100644 --- a/pkg/scanner/fullscan.go +++ b/pkg/scanner/fullscan.go @@ -16,6 +16,7 @@ import ( "github.com/robgonnella/go-lanscan/pkg/oui" ) +// FullScanner implements Scanner interface to perform both ARP and SYN scanning type FullScanner struct { ctx context.Context cancel context.CancelFunc @@ -34,12 +35,13 @@ type FullScanner struct { debug logger.DebugLogger } +// NewFullScanner returns a new instance of FullScanner func NewFullScanner( netInfo network.Network, targets, ports []string, listenPort uint16, - options ...ScannerOption, + options ...Option, ) *FullScanner { ctx, cancel := context.WithCancel(context.Background()) @@ -80,10 +82,12 @@ func NewFullScanner( return scanner } +// Results returns the channel used for notifying of new scan results func (s *FullScanner) Results() chan *ScanResult { return s.results } +// Scan implements ARP and SYN scanning func (s *FullScanner) Scan() error { s.scanningMux.RLock() scanning := s.scanning @@ -136,6 +140,7 @@ func (s *FullScanner) Scan() error { } } +// Stop stops the scanner func (s *FullScanner) Stop() { s.cancel() @@ -148,26 +153,33 @@ func (s *FullScanner) Stop() { s.debug.Info().Msg("all scanners stopped") } +// SetTiming sets the timing duration for how long to wait in-between packet +// sends for both ARP & SYN requests func (s *FullScanner) SetTiming(d time.Duration) { s.arpScanner.SetTiming(d) s.synScanner.SetTiming(d) } +// SetRequestNotifications sets the channel for notifying when SYN & ARP +// requests are sent func (s *FullScanner) SetRequestNotifications(c chan *Request) { s.arpScanner.SetRequestNotifications(c) s.synScanner.SetRequestNotifications(c) } +// SetIdleTimeout sets the idle timeout for ARP and SYN scanning func (s *FullScanner) SetIdleTimeout(d time.Duration) { s.arpScanner.SetIdleTimeout(d) s.synScanner.SetIdleTimeout(d) } +// IncludeVendorInfo sets whether or not to include vendor info when scanning func (s *FullScanner) IncludeVendorInfo(repo oui.VendorRepo) { s.arpScanner.IncludeVendorInfo(repo) s.synScanner.IncludeVendorInfo(repo) } +// SetPacketCapture sets the packet capture implementation for this scanner func (s *FullScanner) SetPacketCapture(cap PacketCapture) { s.arpScanner.SetPacketCapture(cap) s.synScanner.SetPacketCapture(cap) diff --git a/pkg/scanner/fullscan_test.go b/pkg/scanner/fullscan_test.go index 6cfd357..9557a0c 100644 --- a/pkg/scanner/fullscan_test.go +++ b/pkg/scanner/fullscan_test.go @@ -33,7 +33,7 @@ func TestFullScanner(t *testing.T) { mockUserIP := net.ParseIP("172.17.1.1") t.Run("returns immediately if already scanning", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) @@ -42,7 +42,7 @@ func TestFullScanner(t *testing.T) { []string{}, []string{}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) @@ -50,13 +50,13 @@ func TestFullScanner(t *testing.T) { netInfo.EXPECT().UserIP().Return(mockUserIP) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -80,7 +80,7 @@ func TestFullScanner(t *testing.T) { }) t.Run("performs full scan on default network for all ports", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) @@ -91,7 +91,7 @@ func TestFullScanner(t *testing.T) { []string{}, []string{}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) resultChan := fullScanner.Results() @@ -105,14 +105,14 @@ func TestFullScanner(t *testing.T) { netInfo.EXPECT().UserIP().Return(mockUserIP).AnyTimes() netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()).AnyTimes() - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() @@ -152,7 +152,7 @@ func TestFullScanner(t *testing.T) { }) t.Run("performs full scan on provided targets and ports", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) @@ -163,7 +163,7 @@ func TestFullScanner(t *testing.T) { []string{"172.17.1.1"}, []string{"22"}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) resultChan := fullScanner.Results() @@ -177,14 +177,14 @@ func TestFullScanner(t *testing.T) { netInfo.EXPECT().IPNet().Return(mockIPNet).AnyTimes() netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()).AnyTimes() - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() diff --git a/pkg/scanner/options.go b/pkg/scanner/options.go index 65012c0..fc49a40 100644 --- a/pkg/scanner/options.go +++ b/pkg/scanner/options.go @@ -16,33 +16,41 @@ const defaultTiming = time.Microsecond * 100 // If no packets are received for this period of time, exit with timeout const defaultIdleTimeout = time.Second * 5 -type ScannerOption = func(s Scanner) +// Option represents an option that can be passed to any of the +// "New" scanner constructors +type Option = func(s Scanner) -func WithRequestNotifications(c chan *Request) ScannerOption { +// WithRequestNotifications sets channel for request notifications +func WithRequestNotifications(c chan *Request) Option { return func(s Scanner) { s.SetRequestNotifications(c) } } -func WithIdleTimeout(duration time.Duration) ScannerOption { +// WithIdleTimeout sets the idle timeout for the scanner +func WithIdleTimeout(duration time.Duration) Option { return func(s Scanner) { s.SetIdleTimeout(duration) } } -func WithVendorInfo(repo oui.VendorRepo) ScannerOption { +// WithVendorInfo sets whether or not to include vendor info when scanning +func WithVendorInfo(repo oui.VendorRepo) Option { return func(s Scanner) { s.IncludeVendorInfo(repo) } } -func WithPacketCapture(cap PacketCapture) ScannerOption { +// WithPacketCapture sets the packet capture implementation for the scanner +func WithPacketCapture(cap PacketCapture) Option { return func(s Scanner) { s.SetPacketCapture(cap) } } -func WithTiming(duration time.Duration) ScannerOption { +// WithTiming sets the timing duration for how long to wait in-between each +// packet send +func WithTiming(duration time.Duration) Option { return func(s Scanner) { s.SetTiming(duration) } diff --git a/pkg/scanner/synscan.go b/pkg/scanner/synscan.go index 0537dd5..47beabe 100644 --- a/pkg/scanner/synscan.go +++ b/pkg/scanner/synscan.go @@ -19,11 +19,13 @@ import ( "github.com/robgonnella/go-lanscan/pkg/oui" ) +// SynPacket represents a SYN packet response from one of the targeted hosts type SynPacket struct { - Ip4 *layers.IPv4 + IP4 *layers.IPv4 TCP *layers.TCP } +// SynScanner implements the Scanner interface for SYN scanning type SynScanner struct { ctx context.Context cancel context.CancelFunc @@ -45,12 +47,13 @@ type SynScanner struct { debug logger.DebugLogger } +// NewSynScanner returns a new instance of SYNScanner func NewSynScanner( targets []*ArpScanResult, networkInfo network.Network, ports []string, listenPort uint16, - options ...ScannerOption, + options ...Option, ) *SynScanner { ctx, cancel := context.WithCancel(context.Background()) @@ -80,10 +83,13 @@ func NewSynScanner( return scanner } +// Results returns the channel for notifying when SYN responses are +// received from targeted hosts func (s *SynScanner) Results() chan *ScanResult { return s.resultChan } +// Scan implements SYN scanning func (s *SynScanner) Scan() error { fields := map[string]interface{}{ "interface": s.networkInfo.Interface().Name, @@ -164,6 +170,7 @@ func (s *SynScanner) Scan() error { return nil } +// Stop stops the scanner func (s *SynScanner) Stop() { s.cancel() @@ -172,26 +179,34 @@ func (s *SynScanner) Stop() { } } +// SetTiming sets the timing duration for how long to wait in-between packet +// sends for SYN requests func (s *SynScanner) SetTiming(d time.Duration) { s.timing = d } +// SetRequestNotifications sets the channel for notifying when SYN requests +// are sent func (s *SynScanner) SetRequestNotifications(c chan *Request) { s.requestNotifier = c } +// SetIdleTimeout sets the idle timeout for this scanner func (s *SynScanner) SetIdleTimeout(duration time.Duration) { s.idleTimeout = duration } -func (s *SynScanner) IncludeVendorInfo(repo oui.VendorRepo) { +// IncludeVendorInfo N/A for SYN scanner but here to satisfy Scanner interface +func (s *SynScanner) IncludeVendorInfo(_ oui.VendorRepo) { // nothing to do } +// SetPacketCapture sets the packet capture implementation for this scanner func (s *SynScanner) SetPacketCapture(cap PacketCapture) { s.cap = cap } +// SetTargets sets the targets for SYN scanning func (s *SynScanner) SetTargets(targets []*ArpScanResult) { s.targets = targets } @@ -231,13 +246,13 @@ func (s *SynScanner) readPackets() { for _, layerType := range decoded { switch layerType { case layers.LayerTypeIPv4: - synPacket.Ip4 = &ip4 + synPacket.IP4 = &ip4 case layers.LayerTypeTCP: synPacket.TCP = &tcp } } - if synPacket.Ip4 != nil && synPacket.TCP != nil { + if synPacket.IP4 != nil && synPacket.TCP != nil { go s.handlePacket(synPacket) } } @@ -269,7 +284,7 @@ func (s *SynScanner) readPackets() { } func (s *SynScanner) handlePacket(synPacket *SynPacket) { - srcIP := synPacket.Ip4.SrcIP + srcIP := synPacket.IP4.SrcIP var targetIdx int @@ -359,7 +374,9 @@ func (s *SynScanner) writePacketData(target *ArpScanResult, port uint16) error { SYN: true, } - tcp.SetNetworkLayerForChecksum(&ip4) + if err := tcp.SetNetworkLayerForChecksum(&ip4); err != nil { + return err + } if err := s.cap.SerializeLayers(buf, opts, ð, &ip4, &tcp); err != nil { return err diff --git a/pkg/scanner/synscan_test.go b/pkg/scanner/synscan_test.go index b83845c..75d5844 100644 --- a/pkg/scanner/synscan_test.go +++ b/pkg/scanner/synscan_test.go @@ -33,7 +33,7 @@ func TestSynScanner(t *testing.T) { mockUserIP := net.ParseIP("172.17.1.1") t.Run("returns immediately if already scanning", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) @@ -48,20 +48,20 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()) handle.EXPECT().Close().AnyTimes() @@ -85,7 +85,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("returns error if PacketCapture.OpenLive return error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) synScanner := scanner.NewSynScanner( @@ -99,7 +99,7 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) mockErr := errors.New("mock open-live error") @@ -107,7 +107,7 @@ func TestSynScanner(t *testing.T) { netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), @@ -120,7 +120,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("prints debug message if reading packets returns error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -141,20 +141,20 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, listenPort, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()) handle.EXPECT().Close().AnyTimes() @@ -192,7 +192,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("returns error if SetBPFFilter returns error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) @@ -207,7 +207,7 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) mockErr := errors.New("mock SetBPFFilter error") @@ -215,7 +215,7 @@ func TestSynScanner(t *testing.T) { netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), @@ -230,7 +230,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("returns error if WritePacketData returns error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) @@ -245,7 +245,7 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) mockErr := errors.New("mock WritePacketData error") @@ -254,14 +254,14 @@ func TestSynScanner(t *testing.T) { netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) handle.EXPECT().SetBPFFilter(gomock.Any()) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().Close().AnyTimes() handle.EXPECT().WritePacketData(gomock.Any()).DoAndReturn(func(data []byte) (err error) { @@ -283,7 +283,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("returns error if SerializeLayers returns error", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) @@ -298,7 +298,7 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, 54321, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) mockErr := errors.New("mock SerializeLayers error") @@ -307,7 +307,7 @@ func TestSynScanner(t *testing.T) { netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), @@ -315,7 +315,7 @@ func TestSynScanner(t *testing.T) { handle.EXPECT().SetBPFFilter(gomock.Any()) - cap.EXPECT().SerializeLayers( + capture.EXPECT().SerializeLayers( gomock.Any(), gomock.Any(), gomock.Any(), @@ -340,7 +340,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("performs syn scan ", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -361,20 +361,20 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, listenPort, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()) handle.EXPECT().Close().AnyTimes() @@ -406,7 +406,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("ignores packet from unexpected target ", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -427,20 +427,20 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, listenPort, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()) handle.EXPECT().Close().AnyTimes() @@ -472,7 +472,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("ignores packet that have wrong destination port", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) packetSent := false @@ -493,20 +493,20 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, listenPort, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), ) netInfo.EXPECT().Interface().AnyTimes().Return(mockInterface) netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()) handle.EXPECT().Close().AnyTimes() @@ -538,7 +538,7 @@ func TestSynScanner(t *testing.T) { }) t.Run("sends request notifications", func(st *testing.T) { - cap := mock_scanner.NewMockPacketCapture(ctrl) + capture := mock_scanner.NewMockPacketCapture(ctrl) handle := mock_scanner.NewMockPacketCaptureHandle(ctrl) netInfo := mock_network.NewMockNetwork(ctrl) requestNotifier := make(chan *scanner.Request) @@ -567,7 +567,7 @@ func TestSynScanner(t *testing.T) { netInfo, []string{"22"}, listenPort, - scanner.WithPacketCapture(cap), + scanner.WithPacketCapture(capture), scanner.WithRequestNotifications(requestNotifier), ) @@ -575,13 +575,13 @@ func TestSynScanner(t *testing.T) { netInfo.EXPECT().Cidr().AnyTimes().Return(cidr) netInfo.EXPECT().UserIP().Return(mockUserIP) - cap.EXPECT().OpenLive( + capture.EXPECT().OpenLive( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(handle, nil) - cap.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + capture.EXPECT().SerializeLayers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() handle.EXPECT().SetBPFFilter(gomock.Any()) handle.EXPECT().Close().AnyTimes() diff --git a/pkg/scanner/types.go b/pkg/scanner/types.go index b91f186..0b00abf 100644 --- a/pkg/scanner/types.go +++ b/pkg/scanner/types.go @@ -12,20 +12,24 @@ import ( //go:generate mockgen -destination=../../mock/scanner/scanner.go -package=mock_scanner . Scanner,PacketCaptureHandle,PacketCapture +// RequestType represents a type of request packet sent to target type RequestType string const ( + // ArpRequest represents an ARP request ArpRequest RequestType = "ARP" + // SynRequest represents a SYN request SynRequest RequestType = "SYN" ) +// Request represents a notification for a request packet sent to target host type Request struct { Type RequestType IP string Port uint16 } -// Scanner interface for scanning a network for devices +// Scanner interface for scanning network devices type Scanner interface { Scan() error Stop() @@ -37,6 +41,8 @@ type Scanner interface { SetPacketCapture(cap PacketCapture) } +// PacketCaptureHandle interface for writing and reading packets to and from +// the wire type PacketCaptureHandle interface { Close() ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) @@ -45,6 +51,8 @@ type PacketCaptureHandle interface { SetBPFFilter(expr string) (err error) } +// PacketCapture interface for creating PacketCaptureHandles and +// serializing packet layers type PacketCapture interface { OpenLive(device string, snaplen int32, promisc bool, timeout time.Duration) (handle PacketCaptureHandle, _ error) SerializeLayers(w gopacket.SerializeBuffer, opts gopacket.SerializeOptions, layers ...gopacket.SerializableLayer) error @@ -94,6 +102,7 @@ type ArpScanResult struct { Vendor string } +// Serializable returns a serializable version of ArpScanResult func (r *ArpScanResult) Serializable() interface{} { return struct { IP string `json:"ip"` @@ -106,15 +115,23 @@ func (r *ArpScanResult) Serializable() interface{} { } } +// ResultType represents a type of result sent through the result channel in +// each scanner implementation type ResultType string const ( + // ARPResult represents an ARP Result message ARPResult ResultType = "ARP" - ARPDone ResultType = "ARP_DONE" + // ARPDone represents an ARP Done message + ARPDone ResultType = "ARP_DONE" + // SYNResult represents an SYN Result message SYNResult ResultType = "SYN" - SYNDone ResultType = "SYN_DONE" + // SYNDone represents an SYN Done message + SYNDone ResultType = "SYN_DONE" ) +// ScanResult represents a scanning result sent through the results channel +// in each in scanner implementation type ScanResult struct { Type ResultType Payload any