diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c69cf28..dd5b98e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: - uses: golangci/golangci-lint-action@v2 with: - version: v1.38 + version: v1.45.2 go-mod-tidy: runs-on: ubuntu-20.04 @@ -24,6 +24,8 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 + with: + go-version: "1.17" - run: | go mod download diff --git a/.golangci.yml b/.golangci.yml index 9ab04e0..6d0de66 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,17 +2,28 @@ linters: enable: - bodyclose + - dupl - exportloopref - gochecknoinits - gocritic - gofmt - gofumpt - - golint - misspell + - lll - prealloc + - revive - unconvert + - whitespace disable: - errcheck issues: exclude-use-default: false + +linters-settings: + govet: + enable-all: true + disable: + - fieldalignment + - reflectvaluecompare + - shadow diff --git a/Makefile b/Makefile index dee4614..d4dac83 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ -BASE_IMAGE = golang:1.15-alpine3.12 -LINT_IMAGE = golangci/golangci-lint:v1.38.0 +BASE_IMAGE = golang:1.17-alpine3.14 +LINT_IMAGE = golangci/golangci-lint:v1.45.2 .PHONY: $(shell ls) +all: build + help: @echo "usage: make [action]" @echo "" @@ -26,19 +28,18 @@ endef mod-tidy: docker run --rm -it -v $(PWD):/s -w /s amd64/$(BASE_IMAGE) \ - sh -c "go get && GOPROXY=direct go mod tidy" + sh -c "apk add git && go get && GOPROXY=direct go mod tidy" define DOCKERFILE_FORMAT FROM $(BASE_IMAGE) -RUN apk add --no-cache git -RUN GO111MODULE=on go get mvdan.cc/gofumpt +RUN go install mvdan.cc/gofumpt@v0.3.1 endef export DOCKERFILE_FORMAT format: echo "$$DOCKERFILE_FORMAT" | docker build -q . -f - -t temp docker run --rm -it -v $(PWD):/s -w /s temp \ - sh -c "find . -type f -name '*.go' | xargs gofumpt -l -w" + sh -c "gofumpt -l -w ." define DOCKERFILE_TEST FROM amd64/$(BASE_IMAGE) @@ -177,3 +178,11 @@ dockerhub: docker buildx rm builder rm -rf $$HOME/.docker/manifests/* + +build: + $(eval export CGO_ENABLED=0) + go build -o landiscover . +build-freebsd: + $(eval export GOOS=freebsd) + $(eval export GOARCH=386) + go build -o landiscover-freebsd . diff --git a/README.md b/README.md index f6c8317..672e72e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +# Fork additions + +In this fork i've added simple web interface (it also updating entries on the fly). + + # landiscover @@ -40,6 +45,7 @@ Machine and service discovery tool. Flags: --help Show context-sensitive help (also try --help-long and --help-man). --passive do not send any packet + --httpd Run in httpd mode (web interface) Args: [] Interface to listen to diff --git a/go.mod b/go.mod index 4480ab0..47bc177 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,17 @@ module github.com/gswly/landiscover -go 1.14 +go 1.17 + +require ( + github.com/google/gopacket v1.1.19-0.20200930201211-6bf70f9bd488 + github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 + gopkg.in/alecthomas/kingpin.v2 v2.2.6 +) require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect - github.com/google/gopacket v1.1.18 github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 - gopkg.in/alecthomas/kingpin.v2 v2.2.6 + golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect ) diff --git a/go.sum b/go.sum index d1bb170..360e2d4 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/gopacket v1.1.18 h1:lum7VRA9kdlvBi7/v2p7/zcbkduHaCH/SVVyurs7OpY= github.com/google/gopacket v1.1.18/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= +github.com/google/gopacket v1.1.19-0.20200930201211-6bf70f9bd488 h1:n/lGruDFTKDqwZjBL+DkRMxHyWpyfDbe7UbR3QvYThI= +github.com/google/gopacket v1.1.19-0.20200930201211-6bf70f9bd488/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag= @@ -16,15 +20,28 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= +golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/httpd.go b/httpd.go new file mode 100644 index 0000000..a275b5c --- /dev/null +++ b/httpd.go @@ -0,0 +1,175 @@ +package main + +import ( + "fmt" + "net/http" + "sort" + "net" + "bytes" + "encoding/json" +) + +func httpdaemonize(nodes map[nodeKey]*node) { + var htmlPage = ` + + + + + + + + + Landiscover + + + + + + + + + + + + + + + + + + + + + +
Last seenHardware AddressIPVendorDNSNetBIOSmulticast DNS
+ + + + ` + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w,"%s",htmlPage) + }) + + http.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request){ + tableSortBy := "mac" + tableSortAsc := true + + var tableRows []uiTableRow + tableRows = func() []uiTableRow { + var ret []uiTableRow + for _, n := range nodes { + row := uiTableRow{ + id: fmt.Sprintf("%s_%s", n.mac.String(), n.ip.String()), + cells: []string{ + n.lastSeen.Format("Jan 2 15:04:05"), + n.mac.String(), + n.ip.String(), + macVendor(n.mac), + func() string { + if n.dns == "" { + return "-" + } + return n.dns + }(), + func() string { + if n.nbns == "" { + return "-" + } + return n.nbns + }(), + func() string { + if n.mdns == "" { + return "-" + } + return n.mdns + }(), + }, + } + ret = append(ret, row) + } + return ret + }() + + sort.Slice(tableRows, func(i, j int) bool { + n := 0 + switch tableSortBy { + case "last seen": + n = 0 + case "mac": + n = 1 + case "ip": + n = 2 + case "vendor": + n = 3 + case "dns": + n = 4 + case "nbns": + n = 5 + case "mdns": + n = 6 + } + + if tableSortBy == "ip" { + if tableRows[i].cells[n] != tableRows[j].cells[n] { + ipa := net.ParseIP(tableRows[i].cells[n]) + ipb := net.ParseIP(tableRows[j].cells[n]) + + if tableSortAsc { + return bytes.Compare(ipa, ipb) < 0 + } + return bytes.Compare(ipa, ipb) >= 0 + } + } else { + if tableRows[i].cells[n] != tableRows[j].cells[n] { + if tableSortAsc { + return tableRows[i].cells[n] < tableRows[j].cells[n] + } + return tableRows[i].cells[n] > tableRows[j].cells[n] + } + } + + return tableRows[i].cells[2] < tableRows[j].cells[2] + }) + + type JsonResp struct { + Id string `json:"id"` + Last string `json:"last_seen"` + Mac string `json:"mac_addr"` + Ip string `json:"ip"` + Vendor string `json:"vendor"` + Dns string `json:"dns"` + Nbns string `json:"nbns"` + Mdns string `json:"mdns"` + } + Rows := []JsonResp{} + for _, node := range tableRows { + Rows = append(Rows,JsonResp{Id: node.id, Last: node.cells[0], Mac: node.cells[1], Ip: node.cells[2], Vendor: node.cells[3], Dns: node.cells[4], Nbns: node.cells[5], Mdns: node.cells[6]}) + } + resp, err := json.Marshal(Rows) + if err != nil { + fmt.Printf("Unable to encode json response: %s\n",err) + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, string(resp)) + }) + + http.ListenAndServe(":8090", nil) +} \ No newline at end of file diff --git a/landiscover b/landiscover new file mode 100755 index 0000000..73664db Binary files /dev/null and b/landiscover differ diff --git a/main.go b/main.go index c7f7fd7..d5c441c 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,7 @@ type program struct { passiveMode bool intf *net.Interface ownIP net.IP + httpd bool ls *listener ma *methodArp mm *methodMdns @@ -85,6 +86,7 @@ func newProgram() error { argInterface := k.Arg("interface", "Interface to listen to").String() argPassiveMode := k.Flag("passive", "do not send any packet").Default("false").Bool() + argHttpd := k.Flag("httpd","Run in httpd mode (web interface)").Default("false").Bool() kingpin.MustParse(k.Parse(os.Args[1:])) @@ -132,7 +134,7 @@ func newProgram() error { for _, a := range addrs { if ipn, ok := a.(*net.IPNet); ok { if ip4 := ipn.IP.To4(); ip4 != nil { - if bytes.Equal(ipn.Mask, []byte{255, 255, 255, 0}) { + if bytes.Equal(ipn.Mask, []byte{255, 255, 0, 0}) { return ip4, nil } } @@ -145,10 +147,13 @@ func newProgram() error { return err } + + p := &program{ passiveMode: *argPassiveMode, intf: intf, ownIP: ownIP, + httpd: *argHttpd, arp: make(chan arpReq), dns: make(chan dnsReq), mdns: make(chan mdnsReq), @@ -192,10 +197,17 @@ func (p *program) run() { go p.ma.run() go p.mm.run() go p.mn.run() + if p.httpd == false { go p.ui.run() + } nodes := make(map[nodeKey]*node) + if p.httpd { + fmt.Printf("Running in httpd mode...\n") + go httpdaemonize(nodes) + } + outer: for { select { @@ -280,7 +292,9 @@ outer: } }() + if p.httpd == false { p.ui.close() + } /*close(p.arp) close(p.dns)