Skip to content

Commit

Permalink
#1 implemented mac to ip feature
Browse files Browse the repository at this point in the history
  • Loading branch information
mikan committed Jul 3, 2020
1 parent c5c6fe1 commit c048510
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 101 deletions.
38 changes: 38 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.DEFAULT_GOAL := help

.PHONY: setup
setup: ## Resolve dependencies using Go Modules
go mod download

.PHONY: clean
clean: ## Remove build artifact directory
-rm -rfv arping-gui*

.PHONY: lint
lint: ## Run static code analysis
command -v golint >/dev/null 2>&1 || { go get -u golang.org/x/lint/golint; }
golint -set_exit_status ./...

.PHONY: run
run: ## Run app locally
go run . -d

.PHONY: build-linux
build-linux: ## Build linux package
command -v fyne >/dev/null 2>&1 || { go get -u fyne.io/fyne/cmd/fyne; }
fyne package -os linux -icon icon.png -release

.PHONY: build-mac
build-mac: ## Build mac package
command -v fyne >/dev/null 2>&1 || { go get -u fyne.io/fyne/cmd/fyne; }
fyne package -os darwin -icon icon.png -release
zip -r arping-gui_macos.zip arping-gui.app

.PHONY: build-win
build-win: ## Build windows package
command -v fyne >/dev/null 2>&1 || { go get -u fyne.io/fyne/cmd/fyne; }
fyne package -os windows -icon icon.png -release

.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,38 @@ A simple arping-like GUI tool written in Go programming language.

![screenshot](screenshot.png)

## Key features

- Resolve MAC address from IP address
- Resolve IP address from MAC address
- Works on Windows, macOS and Linux

## Download

See [releases](https://github.com/mikan/arping-gui/releases) page.

## How it works

### IP to MAC

1. Check available network adapter information (e.g. eth0)
2. Send ICMP ping to the target using native `ping` command
3. Lookup ARP table using native `arp` command

### IP to MAC

1. Check available network adapter information (e.g. eth0)
2. Send ICMP ping to the broadcast address using native `ping` command
3. Lookup ARP table using native `arp` command

## Limitations

- Works only within a local area network
- IPv6 is not supported

## License

arping-gui licensed under the BSD 3-Clause License.
arping-gui licensed under the [BSD 3-Clause License](LICENSE).

## Author

Expand Down
70 changes: 70 additions & 0 deletions adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"encoding/binary"
"errors"
"fmt"
"net"
"strings"
)

type adapter struct {
name string
ip string
broadcast string
}

func adapters() ([]adapter, error) {
var adapters []adapter
interfaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("failed to collect network interface: %w", err)
}
for _, ifEntry := range interfaces {
if ifEntry.HardwareAddr == nil || !strings.Contains(ifEntry.Flags.String(), "up") {
continue // ignore odd or down adapters
}
addresses, err := ifEntry.Addrs()
if err != nil || len(addresses) == 0 {
continue // ignore unassigned adapters
}
for _, address := range addresses {
if !strings.Contains(address.String(), ".") {
continue
}
ip, n, err := net.ParseCIDR(address.String())
if err != nil {
continue
}
broadcast := broadcast(n)
adapters = append(adapters, adapter{ifEntry.Name, ip.String(), broadcast})
}
}
if len(adapters) == 0 {
return nil, errors.New("no adapters available")
}
return adapters, nil
}

func broadcast(n *net.IPNet) string {
bc := make(net.IP, len(n.IP.To4()))
binary.BigEndian.PutUint32(bc, binary.BigEndian.Uint32(n.IP.To4())|^binary.BigEndian.Uint32(net.IP(n.Mask).To4()))
return bc.String()
}

func adapterNames(adapters []adapter) []string {
s := make([]string, len(adapters))
for i, a := range adapters {
s[i] = fmt.Sprintf("%s [%s]", a.name, a.ip)
}
return s
}

func findAdapter(adapters []adapter, name string) adapter {
for _, a := range adapters {
if strings.HasPrefix(name, a.name) {
return a
}
}
return adapter{}
}
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluN
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
Expand Down
115 changes: 15 additions & 100 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,116 +1,31 @@
package main

import (
"errors"
"os/exec"
"regexp"
"runtime"
"strings"

"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/dialog"
"fyne.io/fyne/widget"
)

var macPattern = regexp.MustCompile("([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})")

type enterEntry struct {
widget.Entry
tapped func()
}

func (e *enterEntry) setOnEnter(tapped func()) {
e.tapped = tapped
}

func newEnterEntry() *enterEntry {
entry := &enterEntry{}
entry.ExtendBaseWidget(entry)
return entry
}

func (e *enterEntry) KeyDown(key *fyne.KeyEvent) {
switch key.Name {
case fyne.KeyReturn:
e.tapped()
default:
e.Entry.KeyDown(key)
}
}
var (
macPattern = regexp.MustCompile("([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})")
ipPattern = regexp.MustCompile("(?:[0-9]{1,3}\\.){3}[0-9]{1,3}")
)

func main() {
a := app.New()
w := a.NewWindow("arping-gui")
macEntry := widget.NewEntry()
var macCopyButton *widget.Button
macCopyButton = widget.NewButton("Copy to clipboard", func() {
w.Clipboard().SetContent(macEntry.Text)
macCopyButton.Disable()
macCopyButton.SetText("Copied!")
})
macResult := widget.NewVBox(
widget.NewLabel("MAC address:"),
macEntry,
macCopyButton,
)
macResult.Hide()
ipEntry := newEnterEntry()
ipEntry.SetPlaceHolder("ex. 192.168.1.1")
var resolveButton *widget.Button
resolveButton = widget.NewButton("Resolve", func() {
resolveButton.Disable()
resolveButton.SetText("Resolving...")
defer func() {
resolveButton.Enable()
resolveButton.SetText("Resolve")
}()
mac, err := resolve(ipEntry.Text)
if err != nil {
macEntry.SetText("ERROR: " + err.Error())
} else {
macEntry.SetText(mac)
}
macCopyButton.SetText("Copy to clipboard")
macCopyButton.Enable()
macResult.Show()
})
ipEntry.setOnEnter(resolveButton.OnTapped)
resolveButton.Disable()
ipEntry.OnChanged = func(s string) {
if len(s) > 0 {
resolveButton.Enable()
} else {
resolveButton.Disable()
}
}
w.SetContent(widget.NewVBox(
widget.NewLabel("Target IP address:"),
ipEntry,
resolveButton,
macResult,
))
w.ShowAndRun()
}

func resolve(ip string) (string, error) {
var pingCmd, arpCmd *exec.Cmd
if runtime.GOOS == "windows" {
pingCmd = exec.Command("ping", "-n", "1", ip)
arpCmd = exec.Command("arp", "-a", ip)
} else {
pingCmd = exec.Command("ping", "-c", "1", ip)
arpCmd = exec.Command("arp", ip)
}
if err := pingCmd.Run(); err != nil {
return "", err
}
mac, err := arpCmd.Output()
w.SetMainMenu(newMainMenu(a, w))
adapters, err := adapters()
if err != nil {
return "", err
}
matches := macPattern.FindAll(mac, -1)
if len(matches) == 0 {
return "", errors.New("no data")
dialog.NewError(err, w)
}
return strings.ToLower(strings.ReplaceAll(string(matches[0]), "-", ":")), nil
w.SetContent(
widget.NewTabContainer(
widget.NewTabItem("MAC to IP", newIP2MACTab(w, adapters)),
widget.NewTabItem("IP to MAC", newMAC2IPTab(w, adapters)),
),
)
w.ShowAndRun()
}
75 changes: 75 additions & 0 deletions resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"bufio"
"bytes"
"errors"
"os/exec"
"runtime"
"strings"
)

func ip2mac(ip string, adapter adapter) (string, error) {
var pingCmd, arpCmd *exec.Cmd
switch runtime.GOOS {
case "windows":
pingCmd = exec.Command("ping", "-n", "1", ip)
arpCmd = exec.Command("arp", "-a", ip, "-N", adapter.ip)
case "darwin":
pingCmd = exec.Command("ping", "-b", adapter.name, "-c", "1", ip)
arpCmd = exec.Command("arp", "-i", adapter.name, ip)
default:
pingCmd = exec.Command("ping", "-I", adapter.name, "-c", "1", ip)
arpCmd = exec.Command("arp", "-i", adapter.name, ip)
}
if err := pingCmd.Run(); err != nil {
return "", err
}
mac, err := arpCmd.Output()
if err != nil {
return "", err
}
matches := macPattern.FindAll(mac, -1)
if len(matches) == 0 {
return "", errors.New("no data")
}
return strings.ReplaceAll(strings.ToLower(string(matches[0])), "-", ":"), nil
}

func mac2ip(mac string, adapter adapter) (string, error) {
var pingCmd, arpCmd *exec.Cmd
switch runtime.GOOS {
case "windows":
mac = strings.ReplaceAll(strings.ToLower(mac), ":", "-")
pingCmd = exec.Command("ping", "-n", "1", adapter.broadcast)
arpCmd = exec.Command("arp", "-a", "-N", adapter.ip)
case "darwin":
mac = strings.ReplaceAll(strings.ToLower(mac), "-", ":")
pingCmd = exec.Command("ping", "-b", adapter.name, "-c", "1", adapter.broadcast)
arpCmd = exec.Command("arp", "-a", "-i", adapter.name)
default:
mac = strings.ReplaceAll(strings.ToLower(mac), "-", ":")
pingCmd = exec.Command("ping", "-I", adapter.name, "-c", "1", adapter.broadcast, "-b")
arpCmd = exec.Command("arp", "-a", "-i", adapter.name)
}
if err := pingCmd.Run(); err != nil {
return "", err
}
arpOut, err := arpCmd.Output()
if err != nil {
return "", err
}
scanner := bufio.NewScanner(bytes.NewReader(arpOut))
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, mac) {
continue
}
matches := ipPattern.FindAllString(line, -1)
if len(matches) == 0 {
return "", errors.New("no data")
}
return matches[0], nil
}
return "", errors.New("no data")
}
Binary file modified screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c048510

Please sign in to comment.