From 257b5762215fde31919b883441a1cae66db2606a Mon Sep 17 00:00:00 2001 From: jimmylomro Date: Tue, 15 Mar 2022 21:35:11 +0000 Subject: [PATCH 1/4] add python bindings --- .gitignore | 2 ++ pyproject.toml | 8 ++++++ setup.py | 62 +++++++++++++++++++++++++++++++++++++++++++ src/bindings.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ src/process.cpp | 3 +-- 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 src/bindings.cpp diff --git a/.gitignore b/.gitignore index 517c40f..b2a98ec 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ nethogs.project libnethogs.so.* libnethogs.a results/ +build/ +nethogs.egg-info diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bc40523 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel", + "pybind11>=2.8.0", +] + +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0ed8487 --- /dev/null +++ b/setup.py @@ -0,0 +1,62 @@ +import glob +import subprocess +import sys + +from pybind11 import get_cmake_dir +# Available at setup time due to pyproject.toml +from pybind11.setup_helpers import Pybind11Extension, build_ext +from setuptools import setup + +_version_info = subprocess.run(['bash', "./determineVersion.sh"], stdout=subprocess.PIPE) +__version__ = _version_info.stdout.decode("utf-8").rstrip("\n").split("-")[0] if _version_info else "0.0.0" + +OBJS = [ + "src/bindings.cpp", + "src/libnethogs.cpp", + "src/packet.cpp", + "src/connection.cpp", + "src/process.cpp", + "src/decpcap.c", + "src/inode2prog.cpp", + "src/conninode.cpp", + "src/devices.cpp" +] + +FLAGS = [ + "-Wall", + "-Wextra", + "-Wno-missing-field-initializers", + "--std=c++0x", + "-O3", + "-fPIC", + '-DVERSION="{}"'.format(__version__) +] +# The main interface is through Pybind11Extension. +# * You can add cxx_std=11/14/17, and then build_ext can be removed. +# * You can set include_pybind11=false to add the include directory yourself, +# say from a submodule. +# +# Note: +# Sort input source files if you glob sources to ensure bit-for-bit +# reproducible builds (https://github.com/pybind/python_example/pull/53) + +ext_modules = [ + Pybind11Extension( + "nethogs", + sources = OBJS, + extra_compile_args = FLAGS, + libraries = ["pcap"] + ), +] + +setup( + name="nethogs", + version=__version__, + author="raboof", + url="https://github.com/raboof/nethogs", + description="Nethogs python bindings", + ext_modules=ext_modules, + cmdclass={"build_ext": build_ext}, + zip_safe=False, + python_requires=">=3.6", +) diff --git a/src/bindings.cpp b/src/bindings.cpp new file mode 100644 index 0000000..8c20ca6 --- /dev/null +++ b/src/bindings.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include "libnethogs.h" + +namespace py = pybind11; + +//--- for some reason this is a global defined in main.cpp +std::set pidsToWatch; + +//--- hacky way to get callbacks working and handle signals +std::function empty_callback; +std::function loop_callback; +void loop_callback_wrapper(int arg1, NethogsMonitorRecord const *arg2){ + py::gil_scoped_acquire acquire; + if (PyErr_CheckSignals() != 0) { + nethogsmonitor_breakloop(); + PyErr_Clear(); + } + else if (loop_callback) { + loop_callback(arg1, arg2); + } +} + +int nethogsmonitor_loop_py( + std::function &cb, + char *filter, + int to_ms) +{ + int retval; + loop_callback = cb; + { + py::gil_scoped_release release; + retval = nethogsmonitor_loop(loop_callback_wrapper, filter, to_ms); + } + loop_callback = empty_callback; + return retval; +} + +//--- python module binding +PYBIND11_MODULE(nethogs, m) { + py::class_(m, "NethogsMonitorRecord") + .def_readwrite("record_id", &NethogsMonitorRecord::record_id) + .def_readwrite("name", &NethogsMonitorRecord::name) + .def_readwrite("pid", &NethogsMonitorRecord::pid) + .def_readwrite("uid", &NethogsMonitorRecord::uid) + .def_readwrite("device_name", &NethogsMonitorRecord::device_name) + .def_readwrite("sent_bytes", &NethogsMonitorRecord::sent_bytes) + .def_readwrite("recv_bytes", &NethogsMonitorRecord::recv_bytes) + .def_readwrite("sent_kbs", &NethogsMonitorRecord::sent_kbs) + .def_readwrite("recv_kbs", &NethogsMonitorRecord::recv_kbs); + + m.def("nethogsmonitor_loop", &nethogsmonitor_loop_py, R"pbdoc( + Nethogs monitor loop + )pbdoc"); + m.def("nethogsmonitor_breakloop", &nethogsmonitor_breakloop, R"pbdoc( + Nethogs monitor loop break + )pbdoc"); + +#ifdef VERSION + m.attr("__version__") = VERSION; +#else + m.attr("__version__") = "unknown"; +#endif + +} diff --git a/src/process.cpp b/src/process.cpp index fcfb1f9..321f1f7 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -268,7 +268,7 @@ Process *getProcess(unsigned long inode, const char *devicename) { if (proc != NULL) return proc; - if ( !(pidsToWatch.empty()) && pidsToWatch.find(node->pid) == pidsToWatch.end() ) { + if ( !(pidsToWatch.empty()) && pidsToWatch.find(node->pid) == pidsToWatch.end() ) { return NULL; } @@ -440,4 +440,3 @@ void remove_timed_out_processes() { } void garbage_collect_processes() { garbage_collect_inodeproc(); } - From fcacd7efdb241c5b8712c3c940a38ac445494017 Mon Sep 17 00:00:00 2001 From: jimmylomro Date: Wed, 16 Mar 2022 18:58:28 +0000 Subject: [PATCH 2/4] added nethogsmonitor_loop_devices and moved code to python dir --- {contrib => python}/README.md | 0 {src => python}/bindings.cpp | 36 ++++++++++++++++++++++++--- {contrib => python}/python-wrapper.py | 0 setup.py | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) rename {contrib => python}/README.md (100%) rename {src => python}/bindings.cpp (66%) rename {contrib => python}/python-wrapper.py (100%) diff --git a/contrib/README.md b/python/README.md similarity index 100% rename from contrib/README.md rename to python/README.md diff --git a/src/bindings.cpp b/python/bindings.cpp similarity index 66% rename from src/bindings.cpp rename to python/bindings.cpp index 8c20ca6..0413798 100644 --- a/src/bindings.cpp +++ b/python/bindings.cpp @@ -1,5 +1,6 @@ -#include #include +#include +#include #include #include @@ -13,14 +14,17 @@ std::set pidsToWatch; //--- hacky way to get callbacks working and handle signals std::function empty_callback; std::function loop_callback; -void loop_callback_wrapper(int arg1, NethogsMonitorRecord const *arg2){ +void loop_callback_wrapper( + int action, + NethogsMonitorRecord const *record) +{ py::gil_scoped_acquire acquire; if (PyErr_CheckSignals() != 0) { nethogsmonitor_breakloop(); PyErr_Clear(); } else if (loop_callback) { - loop_callback(arg1, arg2); + loop_callback(action, record); } } @@ -39,6 +43,29 @@ int nethogsmonitor_loop_py( return retval; } +int nethogsmonitor_loop_devices_py( + std::function &cb, + char *filter, + std::vector __devicenames, + bool all, + int to_ms) +{ + // this is ok because we only use the vector here + std::vector _devicenames; + for (auto _dn : __devicenames) _devicenames.push_back(const_cast(_dn.c_str())); + int devc = _devicenames.size(); + char **devicenames = (_devicenames.empty()) ? NULL : _devicenames.data(); + + int retval; + loop_callback = cb; + { + py::gil_scoped_release release; + retval = nethogsmonitor_loop_devices(loop_callback_wrapper, filter, devc, devicenames, all, to_ms); + } + loop_callback = empty_callback; + return retval; +} + //--- python module binding PYBIND11_MODULE(nethogs, m) { py::class_(m, "NethogsMonitorRecord") @@ -55,6 +82,9 @@ PYBIND11_MODULE(nethogs, m) { m.def("nethogsmonitor_loop", &nethogsmonitor_loop_py, R"pbdoc( Nethogs monitor loop )pbdoc"); + m.def("nethogsmonitor_loop_devices", &nethogsmonitor_loop_devices_py, R"pbdoc( + Nethogs monitor loop + )pbdoc"); m.def("nethogsmonitor_breakloop", &nethogsmonitor_breakloop, R"pbdoc( Nethogs monitor loop break )pbdoc"); diff --git a/contrib/python-wrapper.py b/python/python-wrapper.py similarity index 100% rename from contrib/python-wrapper.py rename to python/python-wrapper.py diff --git a/setup.py b/setup.py index 0ed8487..8b134b8 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ _version_info = subprocess.run(['bash', "./determineVersion.sh"], stdout=subproc __version__ = _version_info.stdout.decode("utf-8").rstrip("\n").split("-")[0] if _version_info else "0.0.0" OBJS = [ - "src/bindings.cpp", + "python/bindings.cpp", "src/libnethogs.cpp", "src/packet.cpp", "src/connection.cpp", From 6be888892e787d0345972770441fc889f597ba2b Mon Sep 17 00:00:00 2001 From: Jaime Lomeli-R Date: Thu, 17 Mar 2022 13:09:18 +0000 Subject: [PATCH 3/4] Update README.md --- python/README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/python/README.md b/python/README.md index 2e6f80f..6097507 100644 --- a/python/README.md +++ b/python/README.md @@ -1,4 +1,49 @@ +## Python stuff +This directory contains the code that wraps `libnethogs` into a python module. + +### To build + install +Install dependencies: +``` bash +apt-get install build-essential libncurses5-dev libpcap-dev pybind11-dev +``` +All the code is compiled through the [pybind11 setuptools building system](https://pybind11.readthedocs.io/en/stable/compiling.html). So to build and install you just need to do: +``` bash +### if you have the code cloned locally +pip install . +``` +Or: +``` bash +### if you can't bother to clone the repo yourself +pip install git+https://github.com/raboof/nethogs.git +``` + +### To use +``` python +import nethogs +import threading + +def callback(action: int, record: nethogs.NethogsMonitorRecord) -> None: + do_whatever_with_record(record) + return + +th = threading.Thread(target=nethogs.nethogsmonitor_loop, args(callback, filter, to_ms)) +th.start() +do_whatever_you_need_to_do() +nethogs.nethogsmonitor_breakloop() +th.join() +``` + +### Caveats +- **Ideally this should not be run in the main thread**, stopping the nethogsmonitor_loop with a SIGINT or SIGTERM is a bit hacky and could break things. +- Only one instance of the loop can be run at a time, callbacks would get completely messed up if more than one is run. I believe nethogs only allows one at a time anyways. +- There is no way of setting pidsToWatch, but it is not difficult to implement if anyone wants to do it :) +- The package version is set in setup.py using the script determineVersion.sh, but it is very hacky as a PEP compatible string is needed. Something stronger should be implemented, ideally using the regex module. + +## Extra stuff +The script `python-wrapper.py` used to be in a contrib directory. This script loads the libnethogs library directly, which you shouldn't really do anyways. The way of using nethogs in python by the `python-wrapper.py` is now deprecated. I'm just leaving it here to not destroy the previous contribution. This is the original comment that **raboof** made about the `python-wrapper.py` and the `contrib` directory: +``` This folder contains user-contributed scripts. The Nethogs project does not make claims about the quality of the scripts, maintains them in any way, or necessarily think they're a good idea in the first place :). +``` From f4f85997b101653692c208a6c196fbd8fb78ada8 Mon Sep 17 00:00:00 2001 From: Jaime Lomeli Date: Thu, 17 Mar 2022 13:11:41 +0000 Subject: [PATCH 4/4] add include dirs --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 8b134b8..3898ee5 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ ext_modules = [ Pybind11Extension( "nethogs", sources = OBJS, + include_dirs = ["src/"], extra_compile_args = FLAGS, libraries = ["pcap"] ),