From d189139933563e31e5b8cbec151b8623b072e214 Mon Sep 17 00:00:00 2001 From: Rob Gonnella Date: Wed, 20 Mar 2024 20:39:04 -0400 Subject: [PATCH] Adds reverse dns lookup option --- README.md | 24 +++++++++++++++- examples/arp/arpscan.go | 1 + internal/cli/root.go | 13 +++++++++ internal/core/core.go | 11 ++++++-- internal/core/core_test.go | 9 +++--- internal/mock/core/core.go | 1 + .../scripts/bump-version/version/version.go | 1 + mock/network/network.go | 1 + mock/oui/oui.go | 1 + mock/scanner/scanner.go | 13 +++++++++ pkg/scanner/arpscan.go | 28 ++++++++++++++----- pkg/scanner/fullscan.go | 15 ++++++---- pkg/scanner/options.go | 7 +++++ pkg/scanner/options_test.go | 3 ++ pkg/scanner/synscan.go | 13 ++++++--- pkg/scanner/types.go | 22 +++++++++------ 16 files changed, 130 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index bfa03d5..6456468 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # go-lanscan -![Coverage](https://img.shields.io/badge/Coverage-92.2%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-91.8%25-brightgreen) A network cli and golang package that allows you to perform arp and syn scanning on a local area network. @@ -70,6 +70,9 @@ sudo go-lanscan --targets 192.22.22.1,192.168.1.1-192.168.1.50,192.56.42.1/24 # include vendor look-ups on mac addresses (scan will be a little slower) sudo go-lanscan --vendor +# include reverse dns lookup for hostnames +sudo go-lanscan --hostnames + # update static database used for vendor lookups # static file is located at ~/.config/go-lanscan/oui.txt sudo go-lanscan update-vendors @@ -215,5 +218,24 @@ queries against this file. The file is stored at `~/.config/go-lanscan/oui.txt` option(arpScanner) ``` +- Perform reverse dns lookup to find hostnames for found devices + +```go + arpScanner := scanner.NewArpScanner( + targets, + netInfo, + arpResults, + arpDone, + scanner.WithHostnames(true) + ) + + // or + arpScanner.IncludeHostnames(true) + + // or + option := scanner.WithHostnames(true) + option(arpScanner) +``` + [golang]: https://go.dev/doc/install [libpcap]: https://github.com/the-tcpdump-group/libpcap diff --git a/examples/arp/arpscan.go b/examples/arp/arpscan.go index 4e9b458..3b9f7ac 100644 --- a/examples/arp/arpscan.go +++ b/examples/arp/arpscan.go @@ -38,6 +38,7 @@ func main() { userNet, scanner.WithIdleTimeout(time.Second*time.Duration(idleTimeout)), scanner.WithVendorInfo(vendorRepo), + scanner.WithHostnames(true), ) go func() { diff --git a/internal/cli/root.go b/internal/cli/root.go index 61dae34..d56b8e4 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -27,6 +27,7 @@ func printConfiguration( listenPort uint16, timing string, vendorInfo, + hostNames, printJSON, arpOnly, progress bool, @@ -76,6 +77,11 @@ func printConfiguration( vendorInfo, }) + configTable.AppendRow(table.Row{ + "hostNames", + hostNames, + }) + configTable.AppendRow(table.Row{ "json", printJSON, @@ -114,6 +120,7 @@ func Root( var ifaceName string var targets []string var vendorInfo bool + var hostNames bool var arpOnly bool var outFile string @@ -160,6 +167,10 @@ func Root( coreScanner.IncludeVendorInfo(vendorRepo) } + if hostNames { + coreScanner.IncludeHostNames(true) + } + timingDuration, err := time.ParseDuration(timing) if err != nil { @@ -197,6 +208,7 @@ func Root( listenPort, timing, vendorInfo, + hostNames, printJSON, arpOnly, !progressDisabled, @@ -219,6 +231,7 @@ func Root( cmd.Flags().StringSliceVarP(&targets, "targets", "t", []string{userNet.Cidr()}, "set targets for scanning") cmd.Flags().StringVar(&outFile, "out-file", "", "outputs final report to file") cmd.Flags().BoolVar(&vendorInfo, "vendor", false, "include vendor info (takes a little longer)") + cmd.Flags().BoolVar(&hostNames, "hostnames", false, "perform reverse dns lookup for hostnames") cmd.AddCommand(newVersion()) cmd.AddCommand(newUpdateVendors(vendorRepo)) diff --git a/internal/core/core.go b/internal/core/core.go index f33e55f..597feee 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -24,6 +24,7 @@ import ( type DeviceResult struct { IP net.IP `json:"ip"` MAC net.HardwareAddr `json:"mac"` + Hostname string `json:"hostname"` Vendor string `json:"vendor"` Status scanner.Status `json:"status"` OpenPorts []scanner.Port `json:"openPorts"` @@ -34,12 +35,14 @@ func (r *DeviceResult) Serializable() interface{} { return struct { IP string `json:"ip"` MAC string `json:"mac"` + Hostname string `json:"hostname"` Vendor string `json:"vendor"` Status string `json:"status"` OpenPorts []scanner.Port `json:"openPorts"` }{ IP: r.IP.String(), MAC: r.MAC.String(), + Hostname: r.Hostname, Vendor: r.Vendor, Status: string(r.Status), OpenPorts: r.OpenPorts, @@ -217,6 +220,7 @@ func (c *Core) processArpResult(result *scanner.ArpScanResult) { IP: result.IP, MAC: result.MAC, Status: scanner.StatusOnline, + Hostname: result.Hostname, Vendor: result.Vendor, OpenPorts: []scanner.Port{}, }) @@ -277,10 +281,10 @@ func (c *Core) printArpResults() { var arpTable = table.NewWriter() arpTable.SetOutputMirror(os.Stdout) - arpTable.AppendHeader(table.Row{"IP", "MAC", "VENDOR"}) + arpTable.AppendHeader(table.Row{"IP", "MAC", "HOSTNAME", "VENDOR"}) for _, t := range c.results.Devices { - arpTable.AppendRow(table.Row{t.IP.String(), t.MAC.String(), t.Vendor}) + arpTable.AppendRow(table.Row{t.IP.String(), t.MAC.String(), t.Hostname, t.Vendor}) } output := arpTable.Render() @@ -319,7 +323,7 @@ func (c *Core) printSynResults() { var synTable = table.NewWriter() synTable.SetOutputMirror(os.Stdout) - synTable.AppendHeader(table.Row{"IP", "MAC", "VENDOR", "STATUS", "OPEN PORTS"}) + synTable.AppendHeader(table.Row{"IP", "MAC", "HOSTNAME", "VENDOR", "STATUS", "OPEN PORTS"}) for _, r := range c.results.Devices { openPorts := []string{} @@ -331,6 +335,7 @@ func (c *Core) printSynResults() { synTable.AppendRow(table.Row{ r.IP.String(), r.MAC.String(), + r.Hostname, r.Vendor, r.Status, openPorts, diff --git a/internal/core/core_test.go b/internal/core/core_test.go index abc9af4..47d8f6e 100644 --- a/internal/core/core_test.go +++ b/internal/core/core_test.go @@ -21,10 +21,11 @@ func TestDeviceResult(t *testing.T) { t.Run("is serializable", func(st *testing.T) { mac, _ := net.ParseMAC("00:00:00:00:00:00") result := &core.DeviceResult{ - IP: net.ParseIP("127.0.0.1"), - MAC: mac, - Vendor: "unknown", - Status: scanner.StatusOnline, + IP: net.ParseIP("127.0.0.1"), + MAC: mac, + Hostname: "unknown", + Vendor: "unknown", + Status: scanner.StatusOnline, OpenPorts: []scanner.Port{ { ID: 22, diff --git a/internal/mock/core/core.go b/internal/mock/core/core.go index bf9c36d..5faa37f 100644 --- a/internal/mock/core/core.go +++ b/internal/mock/core/core.go @@ -5,6 +5,7 @@ // // mockgen -destination=../mock/core/core.go -package=mock_core . Runner // + // Package mock_core is a generated GoMock package. package mock_core diff --git a/internal/mock/scripts/bump-version/version/version.go b/internal/mock/scripts/bump-version/version/version.go index 820405c..23c716d 100644 --- a/internal/mock/scripts/bump-version/version/version.go +++ b/internal/mock/scripts/bump-version/version/version.go @@ -5,6 +5,7 @@ // // mockgen -destination=../../../mock/scripts/bump-version/version/version.go -package=mock_version . VersionControl,VersionGenerator // + // Package mock_version is a generated GoMock package. package mock_version diff --git a/mock/network/network.go b/mock/network/network.go index 929246d..ca07d89 100644 --- a/mock/network/network.go +++ b/mock/network/network.go @@ -5,6 +5,7 @@ // // mockgen -destination=../../mock/network/network.go -package=mock_network . Network // + // Package mock_network is a generated GoMock package. package mock_network diff --git a/mock/oui/oui.go b/mock/oui/oui.go index 9e79624..a8cf221 100644 --- a/mock/oui/oui.go +++ b/mock/oui/oui.go @@ -5,6 +5,7 @@ // // mockgen -destination=../../mock/oui/oui.go -package=mock_oui . VendorRepo // + // Package mock_oui is a generated GoMock package. package mock_oui diff --git a/mock/scanner/scanner.go b/mock/scanner/scanner.go index 88061f7..efbc983 100644 --- a/mock/scanner/scanner.go +++ b/mock/scanner/scanner.go @@ -5,6 +5,7 @@ // // mockgen -destination=../../mock/scanner/scanner.go -package=mock_scanner . Scanner,PacketCaptureHandle,PacketCapture // + // Package mock_scanner is a generated GoMock package. package mock_scanner @@ -41,6 +42,18 @@ func (m *MockScanner) EXPECT() *MockScannerMockRecorder { return m.recorder } +// IncludeHostNames mocks base method. +func (m *MockScanner) IncludeHostNames(arg0 bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "IncludeHostNames", arg0) +} + +// IncludeHostNames indicates an expected call of IncludeHostNames. +func (mr *MockScannerMockRecorder) IncludeHostNames(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncludeHostNames", reflect.TypeOf((*MockScanner)(nil).IncludeHostNames), arg0) +} + // IncludeVendorInfo mocks base method. func (m *MockScanner) IncludeVendorInfo(arg0 oui.VendorRepo) { m.ctrl.T.Helper() diff --git a/pkg/scanner/arpscan.go b/pkg/scanner/arpscan.go index 653125a..00f95b0 100644 --- a/pkg/scanner/arpscan.go +++ b/pkg/scanner/arpscan.go @@ -32,6 +32,7 @@ type ArpScanner struct { timing time.Duration idleTimeout time.Duration vendorRepo oui.VendorRepo + hostNamesEnables bool scanningMux *sync.RWMutex packetSentAtMux *sync.RWMutex debug logger.DebugLogger @@ -44,6 +45,7 @@ func NewArpScanner( options ...Option, ) *ArpScanner { scanner := &ArpScanner{ + cancel: make(chan struct{}), targets: targets, cap: &defaultPacketCapture{}, networkInfo: networkInfo, @@ -106,7 +108,6 @@ func (s *ArpScanner) Scan() error { s.scanningMux.Unlock() s.handle = handle - s.cancel = make(chan struct{}) go s.readPackets() @@ -138,9 +139,9 @@ func (s *ArpScanner) Scan() error { // Stop stops the scanner func (s *ArpScanner) Stop() { - if s.cancel != nil { - close(s.cancel) - } + go func() { + s.cancel <- struct{}{} + }() if s.handle != nil { s.handle.Close() @@ -164,6 +165,11 @@ func (s *ArpScanner) SetIdleTimeout(duration time.Duration) { s.idleTimeout = duration } +// IncludeHostNames sets whether reverse dns look up is performed to find hostname +func (s *ArpScanner) IncludeHostNames(v bool) { + s.hostNamesEnables = v +} + // IncludeVendorInfo sets whether or not to include vendor info in the scan func (s *ArpScanner) IncludeVendorInfo(repo oui.VendorRepo) { s.vendorRepo = repo @@ -316,9 +322,10 @@ func (s *ArpScanner) writePacketData(ip net.IP) error { func (s *ArpScanner) processResult(ip net.IP, mac net.HardwareAddr) { arpResult := &ArpScanResult{ - IP: ip, - MAC: mac, - Vendor: "unknown", + IP: ip, + MAC: mac, + Hostname: "unknown", + Vendor: "unknown", } if s.vendorRepo != nil { @@ -329,6 +336,13 @@ func (s *ArpScanner) processResult(ip net.IP, mac net.HardwareAddr) { } } + if s.hostNamesEnables { + addr, err := net.LookupAddr(ip.String()) + if err == nil && len(addr) > 0 { + arpResult.Hostname = addr[0] + } + } + go func() { s.resultChan <- &ScanResult{ Type: ARPResult, diff --git a/pkg/scanner/fullscan.go b/pkg/scanner/fullscan.go index 71a866a..790e057 100644 --- a/pkg/scanner/fullscan.go +++ b/pkg/scanner/fullscan.go @@ -54,6 +54,7 @@ func NewFullScanner( ) scanner := &FullScanner{ + cancel: make(chan struct{}), netInfo: netInfo, targets: targets, listenPort: listenPort, @@ -95,8 +96,6 @@ func (s *FullScanner) Scan() error { s.scanning = true s.scanningMux.Unlock() - s.cancel = make(chan struct{}) - defer s.reset() go func() { @@ -138,9 +137,9 @@ func (s *FullScanner) Scan() error { // Stop stops the scanner func (s *FullScanner) Stop() { - if s.cancel != nil { - close(s.cancel) - } + go func() { + s.cancel <- struct{}{} + }() if s.arpScanner != nil { s.arpScanner.Stop() @@ -171,6 +170,12 @@ func (s *FullScanner) SetIdleTimeout(d time.Duration) { s.synScanner.SetIdleTimeout(d) } +// IncludeHostNames sets whether reverse dns look up is performed to find hostname +func (s *FullScanner) IncludeHostNames(v bool) { + s.arpScanner.IncludeHostNames(v) + s.synScanner.IncludeHostNames(v) +} + // IncludeVendorInfo sets whether or not to include vendor info when scanning func (s *FullScanner) IncludeVendorInfo(repo oui.VendorRepo) { s.arpScanner.IncludeVendorInfo(repo) diff --git a/pkg/scanner/options.go b/pkg/scanner/options.go index fc49a40..9cddf64 100644 --- a/pkg/scanner/options.go +++ b/pkg/scanner/options.go @@ -41,6 +41,13 @@ func WithVendorInfo(repo oui.VendorRepo) Option { } } +// WithHostnames sets whether or not to perform reverse dns lookup +func WithHostnames(v bool) Option { + return func(s Scanner) { + s.IncludeHostNames(v) + } +} + // WithPacketCapture sets the packet capture implementation for the scanner func WithPacketCapture(cap PacketCapture) Option { return func(s Scanner) { diff --git a/pkg/scanner/options_test.go b/pkg/scanner/options_test.go index 3003e32..d188ac6 100644 --- a/pkg/scanner/options_test.go +++ b/pkg/scanner/options_test.go @@ -35,6 +35,7 @@ func TestOptions(t *testing.T) { scanner.WithPacketCapture(testPacketCapture), scanner.WithRequestNotifications(requestNotifier), scanner.WithVendorInfo(vendorRepo), + scanner.WithHostnames(true), ) scanner.NewFullScanner( @@ -47,6 +48,7 @@ func TestOptions(t *testing.T) { scanner.WithPacketCapture(testPacketCapture), scanner.WithRequestNotifications(requestNotifier), scanner.WithVendorInfo(vendorRepo), + scanner.WithHostnames(true), ) scanner.NewSynScanner( @@ -59,6 +61,7 @@ func TestOptions(t *testing.T) { scanner.WithPacketCapture(testPacketCapture), scanner.WithRequestNotifications(requestNotifier), scanner.WithVendorInfo(vendorRepo), + scanner.WithHostnames(true), ) }) } diff --git a/pkg/scanner/synscan.go b/pkg/scanner/synscan.go index 077ccb4..842aec1 100644 --- a/pkg/scanner/synscan.go +++ b/pkg/scanner/synscan.go @@ -54,6 +54,7 @@ func NewSynScanner( options ...Option, ) *SynScanner { scanner := &SynScanner{ + cancel: make(chan struct{}), targets: targets, networkInfo: networkInfo, cap: &defaultPacketCapture{}, @@ -134,7 +135,6 @@ func (s *SynScanner) Scan() error { s.scanningMux.Unlock() s.handle = handle - s.cancel = make(chan struct{}) go s.readPackets() @@ -167,9 +167,9 @@ func (s *SynScanner) Scan() error { // Stop stops the scanner func (s *SynScanner) Stop() { - if s.cancel != nil { - close(s.cancel) - } + go func() { + s.cancel <- struct{}{} + }() if s.handle != nil { s.handle.Close() @@ -193,6 +193,11 @@ func (s *SynScanner) SetIdleTimeout(duration time.Duration) { s.idleTimeout = duration } +// IncludeHostNames sets whether reverse dns look up is performed to find hostname +func (s *SynScanner) IncludeHostNames(v bool) { + // nothing to do +} + // IncludeVendorInfo N/A for SYN scanner but here to satisfy Scanner interface func (s *SynScanner) IncludeVendorInfo(_ oui.VendorRepo) { // nothing to do diff --git a/pkg/scanner/types.go b/pkg/scanner/types.go index 0b00abf..56b1eb3 100644 --- a/pkg/scanner/types.go +++ b/pkg/scanner/types.go @@ -38,6 +38,7 @@ type Scanner interface { SetRequestNotifications(c chan *Request) SetIdleTimeout(d time.Duration) IncludeVendorInfo(repo oui.VendorRepo) + IncludeHostNames(v bool) SetPacketCapture(cap PacketCapture) } @@ -97,21 +98,24 @@ type SynScanResult struct { // ArpScanResult represents a single network device result from arp scan type ArpScanResult struct { - IP net.IP - MAC net.HardwareAddr - Vendor string + IP net.IP + MAC net.HardwareAddr + Hostname string + Vendor string } // Serializable returns a serializable version of ArpScanResult func (r *ArpScanResult) Serializable() interface{} { return struct { - IP string `json:"ip"` - MAC string `json:"mac"` - Vendor string `json:"vendor"` + IP string `json:"ip"` + MAC string `json:"mac"` + Hostname string `json:"hostname"` + Vendor string `json:"vendor"` }{ - IP: r.IP.String(), - MAC: r.MAC.String(), - Vendor: r.Vendor, + IP: r.IP.String(), + MAC: r.MAC.String(), + Hostname: r.Hostname, + Vendor: r.Vendor, } }