See the Dockerfile and issues.
You have to expose your host network to the container to see what's going on, but at least you don't have to run sudo
as Docker itself is privileged.
docker run -it --rm --network=host deanturpin/shh
shh
is passive, but you might like to generate some traffic on your network.
nmap -A 192.168.0.0/24
The source is deployed with the container, so you can run it interactively and rebuild if you like.
docker run -it --rm --network=host deanturpin/shh bash
touch src/main.cpp
make run
And all the usual git
commands work, so you could push straight back to my repo (if I have let you); or just dump a patch of the diffs to /tmp
and copy it out of the container.
git diff > /tmp/diff.patch
For an even sweeter development experience you can connect to the running container with Visual Studio Code.
Run executing bash
(as above) and then connect your IDE to the running container. Open /root/shh
and install extensions "CMake Tools" and "C++" to build and debug.
Run the following in the web IDE terminal, it will build and run when a file changes.
docker run --rm -it --network=host -v .:/root/shh deanturpin/dev make --directory /root/shh entr
You could even build without running in a container... imagine! Simply clone the repo and run make run
to invokes all the usual CMake commands and also executes the binary. But you will need all the latest compilers installed, of course.
The main thread starts a pcap logging thread for each interface; each thread then reads packets from a single interface and pushes captured packets to a shared container. Peridoically the main thread processes and empties the shared resource and prints a summary of devices encountered: basically the MAC address and associated IP/flags. The main thread runs for a fixed duration and then signals all the threads to stop before joining them to ensure no resources are leaked.
Something that drove me mad for a while was not setting the pcap reads to non-blocking; whilst it worked fine on my macOS laptop, on Linux the threads would occasionally block and leave the main thread hanging.
pcap_setnonblock(pcap, 1, errbuf);
This project is my current testbed for the latest C++ features. I do run a nightly latest GCC build from source but GCC 14 is readily available on Linux and macOS so that will suffice.
Firstly GCC 14 now accepts the -std=c++26
flag; it has also introduced -Wnrvo
to warn when the compiler is unable to perform return value optimisation.
I've been enjoying std::format
for a while but now we have std::print
! And also the variant with the implicit newline.
std::println("https://github.com/deanturpin/shh/commit/{}\n", GIT_HASH);
constexpr auto logging_cycles = 60uz;
auto zipped = std::views::zip(interfaces, counts);
for (const auto& [interface, count] : zipped) {
std::println("{}: {}", interface, count);
}
static_assert(is_print('~'));
static_assert(not is_print('\n'));
static_assert(not std::has_virtual_destructor_v<packet_t>);
Name the members you want to initialise.
return {
.interface = interface,
.source = {.mac = source_mac, .ip = source_ip},
.destination = {.mac = destination_mac, .ip = destination_ip},
.type = std::byteswap(eth.packet_type),
.length = header.len,
};
if (not std::ranges::any_of(
excludes, [name](auto excluded) { return name == excluded; }))
names.emplace_back(name);
I've used std::async
to create each logging thread; which also provides a natural synchronisation point when you wait for the return value from each thread.
for (auto name : interfaces)
counts.emplace_back(std::async(std::launch::async, func, name));