Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add netstat sample application #40

Merged
merged 11 commits into from
Jan 4, 2024
Merged
74 changes: 35 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
![pfs](./img/pfs.png "Logo")

Very easy to use, procfs parsing library in C++.
Production grade, very easy to use, procfs parsing library in C++.
Used in production by S&P 500 tech companies and startups!

**NEW** Basic parsing of sysfs (Additional `sysfs` feature requests are welcome!)

## Build

Expand Down Expand Up @@ -53,6 +56,7 @@ CMake generates an `install_manifest.txt` file to track all the created files, t
- Parsing system-wide information from files directly under `/procfs`. See `procfs.hpp` for all the supported files.
- Parsing per-task (processes and threads) information from files under `/procfs/[task-id]/`. See `task.hpp` for all the supported files.
- Parsing network information from files under `/procfs/net` (which is an alias to `/procfs/self/net` nowadays)
- **NEW** Parsing of basic disk information from `sysfs/block` (Additional `sysfs` feature requests are welcome!)

## Requirements

Expand All @@ -72,7 +76,7 @@ CMake generates an `install_manifest.txt` file to track all the created files, t

If you call `procfs().get_task(<id>)` and that task doesn't really exist, the constructor will succeed.

Since tasks can die any time, instead of adding some extra validation during construction, that might be confusing, the current design assumes the first call after the tasks died will fail.
Since tasks can die any time, instead of adding extra validation during construction, which might be confusing, the current design assumes the first call after the tasks died will fail.

### Collecting thread information

Expand All @@ -91,22 +95,43 @@ How does that affect `pfs`?

The directory `sample` contains a full blown application that calls all(!) the supported APIs and prints all the information gathered. When compiling the library, the sample applications is compiled as well.

You can find a basic implementations of `netstat` (see `sample/tool_netstat.cpp`) and `lsmod` (see `sample/tool_lsmod.cpp`) that you can easily reuse in your projects.

Anyway, here are some cool (and concise) examples:

**Example 1:** Iterater over all the loaded unsigned or out-of-tree kernel modules
**Example 1:** Find all the process that hold an open file descriptor to a specific file:
```
auto file = "/path/to/file";
auto pfs = pfs::procfs();
auto modules = pfs.get_modules();
for (const auto& module : modules)
for (const auto& process : pfs.get_processes())
{
if (module.is_out_of_tree || module.is_unsigned)
for (const auto& thread : process.get_tasks())
{
... do your work ...
for (const auto& fd : thread.get_fds())
{
if (fd.get_target() == file)
{
... do something ...
}
}
}
}
```
_Note: This is pedantic implementation that takes into account the fact that a threads might not share the file descriptor with the process, see CLONE_FILES in [clone(2)](https://man7.org/linux/man-pages/man2/clone.2.html)_

**Example 2:** Find all the memory maps for task 1234 (This can be both a process or a thread) that start with an ELFs header
**Example 2:** Iterate over all the IPv4 TCP sockets currently in listening state (in my current network namespace) and print their local port:
```
auto filter_listening = [](const net_socket& socket){
return socket.socket_net_state == pfs::net_socket::net_state::listen;
}

for (auto& socket : pfs::procfs().get_net().get_tcp(filter_listening))
{
std::cout << socket.local_port << std::endl;
}
```

**Example 3:** Find all the memory maps for task 1234 (This can be both a process or a thread) that start with an ELFs header
```
auto task = pfs::procfs().get_task(1234);
auto mem = task.get_mem();
Expand All @@ -120,38 +145,9 @@ for (auto& map : task.get_maps())
static const std::vector<uint8_t> ELF_HEADER = { 0x7F, 0x45, 0x4C, 0x46 };
if (mem.read(map.start_address, ELF_HEADER.size()) == ELF_HEADER)
{
... do your work ...
... do something ...
}
}
```
_(You can either create `pfs` every time or once and keep it, the overhead is really small)_
_(You can either create `pfs::procfs()` every time or once and keep it, the overhead is really small)_

**Example 3:** Iterate over all the IPv4 TCP sockets currently in listening state (in my current network namespace):
```
// Same as pfs::procfs().get_task().get_net().get_tcp()
for (auto& socket : pfs::procfs().get_net().get_tcp())
{
if (socket.socket_net_state == pfs::net_socket::net_state::listen)
{
... do your work ...
}
}
```
_(API behaves similar to the `procfs`, where `/proc/net` is a soft link to `/proc/self/net`)_

**Example 4:** Get all the IPv6 UDP sockets in the root network namespace belonging to a specific user ID:
```
for (auto& socket : pfs::procfs().get_task(1).get_net().get_udp6())
{
if (socket.uid == <some-uid-value>)
{
... do your work ...
}
}
```

**Example 5:** Check if the process catches SIGSTOP signals
```
auto status = pfs::procfs().get_task(1234).get_status();
bool handles_sigstop = status.sig_cgt.is_set(pfs::signal::sigstop);
```
11 changes: 11 additions & 0 deletions docker/Dockerfile-debian12
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM debian:bookworm

WORKDIR /pfs

RUN apt-get update && \
apt-get install -y cmake g++ build-essential

ENV CXX=g++
ENV CC=gcc

CMD /bin/bash
File renamed without changes.
File renamed without changes.
31 changes: 31 additions & 0 deletions include/pfs/filter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2020-present Daniel Trugman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef PFS_FILTER_HPP
#define PFS_FILTER_HPP

namespace pfs {
namespace filter {

enum class action {
drop,
keep,
};

} // namespace filter
} // namespace pfs

#endif // PFS_FILTER_HPP
50 changes: 31 additions & 19 deletions include/pfs/net.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
#ifndef PFS_NET_HPP
#define PFS_NET_HPP

#include <functional>
#include <string>
#include <vector>

#include "types.hpp"
#include "filter.hpp"

namespace pfs {

Expand All @@ -41,36 +43,46 @@ class net final
net& operator=(net&&) = delete;

public:
std::vector<net_device> get_dev() const;

std::vector<net_socket> get_icmp() const;
std::vector<net_socket> get_icmp6() const;
std::vector<net_socket> get_raw() const;
std::vector<net_socket> get_raw6() const;
std::vector<net_socket> get_tcp() const;
std::vector<net_socket> get_tcp6() const;
std::vector<net_socket> get_udp() const;
std::vector<net_socket> get_udp6() const;
std::vector<net_socket> get_udplite() const;
std::vector<net_socket> get_udplite6() const;
using net_device_filter = std::function<filter::action(const net_device&)>;
using net_socket_filter = std::function<filter::action(const net_socket&)>;
using netlink_socket_filter = std::function<filter::action(const netlink_socket&)>;
using unix_socket_filter = std::function<filter::action(const unix_socket&)>;
using net_route_filter = std::function<filter::action(const net_route&)>;

std::vector<netlink_socket> get_netlink() const;
public:
std::vector<net_device> get_dev(net_device_filter filter = nullptr) const;

std::vector<net_socket> get_icmp(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_icmp6(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_raw(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_raw6(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_tcp(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_tcp6(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_udp(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_udp6(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_udplite(net_socket_filter filter = nullptr) const;
std::vector<net_socket> get_udplite6(net_socket_filter filter = nullptr) const;

std::vector<netlink_socket> get_netlink(netlink_socket_filter filter = nullptr) const;

std::vector<unix_socket> get_unix() const;
std::vector<unix_socket> get_unix(unix_socket_filter filter = nullptr) const;

std::vector<net_route> get_route() const;
std::vector<net_route> get_route(net_route_filter filter = nullptr) const;

private:
friend class task;
net(const std::string& procfs_root);
net(const std::string& parent_root);

private:
std::vector<net_socket> get_net_sockets(const std::string& file) const;
std::vector<net_socket> get_net_sockets(const std::string& file,
net_socket_filter filter = nullptr) const;

static std::string build_net_root(const std::string& procfs_root);
static std::string build_net_root(const std::string& parent_root);

private:
const std::string _procfs_root;
// Net has a "parent root", and not a "procfs root", because we could
// be looking at a net namespace of a specific process.
const std::string _parent_root;
const std::string _net_root;
};

Expand Down
128 changes: 0 additions & 128 deletions include/pfs/parsers/generic.hpp

This file was deleted.

Loading
Loading