I've been working on a LAN discovery tool with a Terminal User Interface (TUI) written entirely in Go.
It's called Whosthere, and it's designed to help you explore devices on your local network without requiring elevated privileges.
- Daemon mode with a simple HTTP API to fetch devices
- Configurable theming and behavior via a YAML config file
Why I built it:
Mainly to learn, I've been programming in Go for about a year now and wanted to combine learning Go with learning more about networking in one single project. I've always been a big fan of TUI applications like lazygit, k9s, and dive. And then the idea came to build a TUI application that shows devices on your LAN. I am by no means a networking expert, but it was fun to figure out how ARP works, and discovery protocols such as mDNS and SSDP.
Example usage:
---
# install via HomeBrew
brew tap ramonvermeulen/whosthere
brew install whosthere
# or with go install
go install github.com/ramonvermeulen/whosthere@latest
# run as TUI
whosthere
# run as daemon
whosthere daemon --port 8080
---
I'd love to hear your feedback, if you have ideas for additional features or improvements that is highly appreciated! Current platform support is Linux and MacOS.
Looks great!! I had the same idea a few days ago and am so glad you posted this now! I will be using it and will let you know of any feedback. So far works great on my network!
Installed on raspbian, works wonders, much better than the thing i vibecoded yesterday. One feature I'd like: recording new arrivals to a log with all the info so it can be used as a barebones IDS
This looks great! I've been searching for something like this for ever.
Some feedback of what I found on my network, as compared to some other scanners I've used.
I've never seen anything that can beat Advanced IP Scanner at finding hostnames. I've never even found a way to get arp or nmap to get close to Advanced IP Scanner; I've tried dozens of suggested commands of each, all with no luck. Here's the results of my scans:
It would be great it it could show the reverse lookup of the IPs as on my LAN everything has a name and if it hasn't then it is probably an interloper!
I am not a golang user. If I install as recommended via `go` command on Linux how do I make sure it is updated when new versions are released? I wish it has a .deb package..
Generally speaking, the Debian package management system is really not a place I would look for prompt updates when new versions of software are released.
No, it really doesn't have anything TUI focused in stdlib. I get the reason why but it would be cool if they had something foundational in golang.org/x/
This project appears to be using github.com/rivo/tview which is is really solid.
The charmbracelet folk are quite, um, charming, but when I tried to work with bubble tea on a multi pane project I found it unwieldy -- tview seemed much more straightforward.
I started off using tview/tcell, and only later found out about bubbletea and the charmbracelet ecosystem. Then I didn't really find a solid reason to switch over to bubbletea. So far I really enjoyed the experience building the app with tview, the only real limitation I ran into was switching the theme at runtime, for which I had to build a custom mechanism.
Overall good work. I'd request an `-i` command-line parameter to specify the interface to scan (and I'd prefer ALL params being able to be read from command line params). I think it just performs a full scan initially on my laptop, following scans either didn't success or didn't involve TCP connect scan (I don't see ARP requests after the initial scan).
That's correct. To avoid overloading the local network, the initial scan has a built-in safeguard:
1. It only scans the subnet of the configured network interface.
2. The scan is limited to a maximum size of a /16 subnet.
3. It runs just once every 5 minutes (this interval should be made configurable, currently still hardcoded).
If a subnet larger than /16 is configured, whosthere will log a warning and only scan the first /16 portion of that subnet. As of now the network interface itself is configured via the YAML file. I agree it would be a good idea to add command-line flags for more of these settings to make them easier to adjust.
I love the resurgence of TUI apps, but I wonder what the definition of "modern TUI" means in these cases. Does it basically mean just not using curses?
Yikes, so it's a "TUI" app... that still requires a display server? So I can't run this TUI over SSH or a virtual terminal. Wondering what the point of a tui is that still requires a gui environment to run?
Sorry, I was unhelpfully flippant. You totally can, and I don't want to distract from the great app that has been shared. This bug was just a compile time issue, which needed X libs to bake in clipboard support which is optional at runtime.
That has nothing to do with the UI framework. The X11 dependency comes as part of the clipboard integration (which I'd argue should be optional or even removed). Still, I wouldn't call it modern if Wayland is outright not supported.
I hesitated a bit bringing in this feature. On one hand, I really like to have clipboard support, on the other hand, I don't like that it requires you to change from static to dynamic linking (and have the x11 dependency).
Maybe I could write an install.sh script for installation that detects the OS and fetches the correct version/tarball from the Github release.
Thanks a lot for your contribution, this is something I will look into in the upcoming days. I totally agree that CGO isn't ideal, I had to make the build/release process also a lot more complicated purely for that clipboard requirement (see GHAs and the different goreleaser files).
On the other hand, I also don't want whosthere to be depended on a fork that isn't maintained anymore. I will think about this trade-off, but I am also interested how others look at this problem.
It doesn't start port scanning by default, maybe this is a feature I can build in the future. When you are on the `detail` view of a device, you can press `p` and that will open a pop-up to perform the port scan. Also the list of ports that will be scanned is a default list of common ports, and can be configured via the configuration yaml.
this is great!
i had to tweak the config file on macos because it was using some weird interface (utun4) instead of en0. otherwise awesome tool, i am definitely going to be using this more often.
Thanks, I am glad you like it! I couldn't find a Go API that just returns the OS "default" network interface, so struggled a bit with a correct implementation for that part.
When reading some blog posts, I found often a solution where it sends out an UDP dial to for example 8.8.8.8:53 because you can then get the network interface back from the connection it's local address. As fallback I implemented to pick the first non-loopback interface that is up.
Would be open to suggestions to do this in a better way!
this prints my Ethernet interface as expected. It doesn't make any requests, it just figures out where to route a packet. I guess it interfaces with the OS routing table.
Thanks for sharing! This is definitely something I will look into, I am all in favor to simplify the current implementation of finding the "default" OS network interface.
Would it help to get it on the "official" homebrew, instead of a custom tap/cask? Might try to do an application for that somewhere in the upcoming weeks.
It works by combining several discovery methods:
- mDNS and SSDP scanning
- ARP cache reading (after triggering ARP resolution via TCP/UDP sweeps)
- OUI lookups to identify device manufacturers
It also includes:
- A fast, keyboard-driven TUI (powered by tview)
- An optional built-in port scanner
- Daemon mode with a simple HTTP API to fetch devices
- Configurable theming and behavior via a YAML config file
Why I built it:
Mainly to learn, I've been programming in Go for about a year now and wanted to combine learning Go with learning more about networking in one single project. I've always been a big fan of TUI applications like lazygit, k9s, and dive. And then the idea came to build a TUI application that shows devices on your LAN. I am by no means a networking expert, but it was fun to figure out how ARP works, and discovery protocols such as mDNS and SSDP.
Example usage:
---
# install via HomeBrew brew tap ramonvermeulen/whosthere brew install whosthere
# or with go install go install github.com/ramonvermeulen/whosthere@latest
# run as TUI whosthere
# run as daemon whosthere daemon --port 8080
---
I'd love to hear your feedback, if you have ideas for additional features or improvements that is highly appreciated! Current platform support is Linux and MacOS.
Some feedback of what I found on my network, as compared to some other scanners I've used.
I've never seen anything that can beat Advanced IP Scanner at finding hostnames. I've never even found a way to get arp or nmap to get close to Advanced IP Scanner; I've tried dozens of suggested commands of each, all with no luck. Here's the results of my scans:
Alive hosts: 309
Unkown: 201
With hostnames: 80
https://www.advanced-ip-scanner.com/
####################################
I also tried a program called Angry IP Scanner:
Hosts scanned: 510
Hosts alive: 315
With hostnames: 75
https://angryip.org/
####################################
whosthere
Devices: 318
With hostnames: 54
It would be great it it could show the reverse lookup of the IPs as on my LAN everything has a name and if it hasn't then it is probably an interloper!
Generally speaking, the Debian package management system is really not a place I would look for prompt updates when new versions of software are released.
This project appears to be using github.com/rivo/tview which is is really solid.
1. It only scans the subnet of the configured network interface.
2. The scan is limited to a maximum size of a /16 subnet.
3. It runs just once every 5 minutes (this interval should be made configurable, currently still hardcoded).
If a subnet larger than /16 is configured, whosthere will log a warning and only scan the first /16 portion of that subnet. As of now the network interface itself is configured via the YAML file. I agree it would be a good idea to add command-line flags for more of these settings to make them easier to adjust.
Specifically it needs to pull additional detail out of proxmox servers and opnsense plus deduce where things are physically based on latency.
Thats a whole lot easier if it doesn’t need to work universally & you can hardcode some assumptions
There should be a build tag to disable clipboard, that'd be the easiest way around this.
I hesitated a bit bringing in this feature. On one hand, I really like to have clipboard support, on the other hand, I don't like that it requires you to change from static to dynamic linking (and have the x11 dependency).
Maybe I could write an install.sh script for installation that detects the OS and fetches the correct version/tarball from the Github release.
How about this PR? https://github.com/ramonvermeulen/whosthere/pull/29
It switches to using github.com/dece2183/go-clipboard, which supports Mac, Windows, Linux (X11 + Wayland) and Android.
On the other hand, I also don't want whosthere to be depended on a fork that isn't maintained anymore. I will think about this trade-off, but I am also interested how others look at this problem.
When reading some blog posts, I found often a solution where it sends out an UDP dial to for example 8.8.8.8:53 because you can then get the network interface back from the connection it's local address. As fallback I implemented to pick the first non-loopback interface that is up.
Would be open to suggestions to do this in a better way!
Couldn't run it on macOS Tahoe. I believe this requires me lowering the security to allow it, which is something I would rather not doing.
xattr -c `which whosthere`