diff -pruN 0.7.7-4/.github/workflows/codeql.yaml 0.7.11-0ubuntu3/.github/workflows/codeql.yaml
--- 0.7.7-4/.github/workflows/codeql.yaml	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/.github/workflows/codeql.yaml	2024-01-12 18:27:09.000000000 +0000
@@ -15,11 +15,11 @@ jobs:
 
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v2
+        uses: github/codeql-action/init@v3
         # Override language selection by uncommenting this and choosing your languages
         # with:
         #   languages: go, javascript, csharp, python, cpp, java
@@ -27,7 +27,7 @@ jobs:
       # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
       # If this step fails, then you should remove it and run the build manually (see below).
       - name: Autobuild
-        uses: github/codeql-action/autobuild@v2
+        uses: github/codeql-action/autobuild@v3
 
       # ℹ️ Command-line programs to run using the OS shell.
       # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -41,4 +41,4 @@ jobs:
       #     make release
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v2
+        uses: github/codeql-action/analyze@v3
diff -pruN 0.7.7-4/.github/workflows/depsreview.yaml 0.7.11-0ubuntu3/.github/workflows/depsreview.yaml
--- 0.7.7-4/.github/workflows/depsreview.yaml	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/.github/workflows/depsreview.yaml	2024-01-12 18:27:09.000000000 +0000
@@ -9,6 +9,6 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: 'Checkout Repository'
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: 'Dependency Review'
         uses: actions/dependency-review-action@v3
diff -pruN 0.7.7-4/.github/workflows/pull_request.yml 0.7.11-0ubuntu3/.github/workflows/pull_request.yml
--- 0.7.7-4/.github/workflows/pull_request.yml	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/.github/workflows/pull_request.yml	2024-01-12 18:27:09.000000000 +0000
@@ -13,67 +13,69 @@ jobs:
     runs-on: code
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=linter
   repo:
     runs-on: Python3.8
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=repo
   unit:
     runs-on: Linux
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=unit
   lab:
     runs-on: Linux
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=lab
   docs:
     runs-on: Linux
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=docs
   neutron:
     runs-on: Python3.8
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=neutron
   integration:
     runs-on: Linux
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=integration
   minimal:
     runs-on: Linux
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: sudo make nox session=minimal
-  linux-fedora-35:
-    runs-on: linux-fedora-35
+  linux-fedora-38:
+    runs-on: linux-fedora-38
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: sudo make nox session=linux-3.6
+      - run: sudo make nox session=linux-3.8
       - run: sudo make nox session=linux-3.10
-  linux-ubuntu-18:
-    runs-on: linux-ubuntu-18
+      - run: sudo make nox session=linux-3.12
+  linux-ubuntu-22:
+    runs-on: linux-ubuntu-22
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: sudo make nox session=linux-3.6
       - run: sudo make nox session=linux-3.10
   openbsd:
     runs-on: OpenBSD
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: /home/github/openbsd_runner.sh
diff -pruN 0.7.7-4/.github/workflows/push.yml 0.7.11-0ubuntu3/.github/workflows/push.yml
--- 0.7.7-4/.github/workflows/push.yml	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/.github/workflows/push.yml	2024-01-12 18:27:09.000000000 +0000
@@ -13,5 +13,5 @@ jobs:
     runs-on: code
     steps:
       - run: sudo chown -R $USER:$USER $GITHUB_WORKSPACE
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: make nox session=linter
diff -pruN 0.7.7-4/CHANGELOG.rst 0.7.11-0ubuntu3/CHANGELOG.rst
--- 0.7.7-4/CHANGELOG.rst	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/CHANGELOG.rst	2024-01-12 18:27:09.000000000 +0000
@@ -1,6 +1,31 @@
 Changelog
 =========
 
+* 0.7.11
+    * ethtool: ring support <https://github.com/svinota/pyroute2/pull/1152>
+    * ndb: fix FDB records index <https://github.com/svinota/pyroute2/pull/1158>
+    * ndb: fix sources objects counting <https://github.com/svinota/pyroute2/pull/1156>
+* 0.7.10
+    * ss2: fix classful flow data <https://github.com/svinota/pyroute2/pull/1143>
+    * ci: add Python versions 3.8 and 3.12, update platform versions
+    * l2tp: fix get_tunnel/get_session <https://github.com/svinota/pyroute2/pull/1134>
+    * ndb: compat fix <https://github.com/svinota/pyroute2/pull/1133>
+    * ndb: recordset pipes <https://github.com/svinota/pyroute2/pull/1108>
+    * netns: RISCV64 fix <https://github.com/svinota/pyroute2/pull/1128>
+    * netns: loongarch support <https://github.com/svinota/pyroute2/pull/1104>
+    * ethtool: statistics support <https://github.com/svinota/pyroute2/pull/1126>
+    * ethtool: dynamic ioctl gstrings <https://github.com/svinota/pyroute2/issues/1112>
+    * NetNS: set_netnsid fix <https://github.com/svinota/pyroute2/issues/1123>
+    * iproute: nsid allocation <https://github.com/svinota/pyroute2/pull/1121>
+    * iproute: dump mpls routes <https://github.com/svinota/pyroute2/pull/1115>
+    * rtnl: CAN support <https://github.com/svinota/pyroute2/pull/1109>
+* 0.7.9
+    * minimal: fix for embedded envs <https://github.com/svinota/pyroute2/pull/1096>
+    * diag: support CGROUP_ID <https://github.com/svinota/pyroute2/pull/1092>
+    * iwutil: get/set interface (by ifindex) type <https://github.com/svinota/pyroute2/pull/1093>
+    * tc: 'duplicate' parameter fix <https://github.com/svinota/pyroute2/pull/1098>
+* 0.7.8
+    * ss2: more fixes <https://github.com/svinota/pyroute2/pull/1088>
 * 0.7.7
     * ss2: user context patch <https://github.com/svinota/pyroute2/pull/1087>
     * ndb: basic altname support
diff -pruN 0.7.7-4/Makefile 0.7.11-0ubuntu3/Makefile
--- 0.7.7-4/Makefile	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/Makefile	2024-01-12 18:27:09.000000000 +0000
@@ -6,11 +6,7 @@
 python ?= $(shell util/find_python.sh)
 
 define nox
-	[ -d .venv ] || ${python} -m venv .venv
-	bash -c "source .venv/bin/activate; \
-		python -m pip install --upgrade pip; \
-		python -m pip install nox; \
-		nox $(1) -- '$(subst ",\",${noxconfig})'"
+	nox $(1) -- '$(subst ",\",${noxconfig})'
 endef
 
 .PHONY: all
@@ -72,7 +68,7 @@ test-platform:
 
 .PHONY: upload
 upload: dist
-	${python} -m twine upload dist/*
+	$(call nox,-e upload)
 
 .PHONY: setup
 setup:
diff -pruN 0.7.7-4/README.rst 0.7.11-0ubuntu3/README.rst
--- 0.7.7-4/README.rst	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/README.rst	2024-01-12 18:27:09.000000000 +0000
@@ -62,7 +62,7 @@ A "Hello world" example:
     from pyroute2 import NDB
 
     with NDB() as ndb:
-        with ndb.interfaces['eth0'] as eth0
+        with ndb.interfaces['eth0'] as eth0:
             # set one parameter
             eth0.set(state='down')
             eth0.commit()  # make sure that the interface is down
@@ -88,7 +88,7 @@ More examples:
     for line in if_dump.format('json'):
         print(line)
 
-    addr_summary = nsb.addresses.summary()
+    addr_summary = ndb.addresses.summary()
     addr_summary.select_records(ifname='eth0')
     for line in addr_summary.format('csv'):
         print(line)
diff -pruN 0.7.7-4/VERSION 0.7.11-0ubuntu3/VERSION
--- 0.7.7-4/VERSION	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/VERSION	2024-01-12 18:27:09.000000000 +0000
@@ -1 +1 @@
-0.7.7
+0.7.11
diff -pruN 0.7.7-4/debian/changelog 0.7.11-0ubuntu3/debian/changelog
--- 0.7.7-4/debian/changelog	2024-02-22 17:25:30.000000000 +0000
+++ 0.7.11-0ubuntu3/debian/changelog	2025-12-01 14:08:03.000000000 +0000
@@ -1,16 +1,24 @@
-pyroute2 (0.7.7-4) unstable; urgency=medium
+pyroute2 (0.7.11-0ubuntu3) resolute; urgency=medium
 
-  [ Nicolas Schier ]
-  * Add test against unknown version
-  * Fix pyroute2.__version__ to hold real version number (Closes: #1052606)
+  * No-change mass rebuild for Ubuntu 26.04 (LP: #2132257)
 
- -- Thomas Goirand <zigo@debian.org>  Thu, 22 Feb 2024 18:25:30 +0100
+ -- Sebastien Bacher <seb128@ubuntu.com>  Mon, 01 Dec 2025 15:08:03 +0100
 
-pyroute2 (0.7.7-3) unstable; urgency=medium
+pyroute2 (0.7.11-0ubuntu2) noble; urgency=medium
 
-  * Fix VCS URL (Closes: #1052605).
+  * debian/control: drop Build-Depends-Indep: python3-mitogen
+    This allows us to temporarily remove python-mitogen from 
+    the archive (LP: #2053266)
 
- -- Thomas Goirand <zigo@debian.org>  Thu, 15 Feb 2024 21:48:42 +0100
+ -- Nick Rosbrook <enr0n@ubuntu.com>  Thu, 15 Feb 2024 12:14:40 -0500
+
+pyroute2 (0.7.11-0ubuntu1) noble; urgency=medium
+
+  * d/watch: Update to use github+tags for new releases otherwise doc
+    build fails.
+  * New upstream point release.
+
+ -- James Page <james.page@ubuntu.com>  Fri, 09 Feb 2024 09:54:26 +0000
 
 pyroute2 (0.7.7-2) unstable; urgency=medium
 
diff -pruN 0.7.7-4/debian/control 0.7.11-0ubuntu3/debian/control
--- 0.7.7-4/debian/control	2024-02-22 17:25:30.000000000 +0000
+++ 0.7.11-0ubuntu3/debian/control	2024-02-15 17:14:40.000000000 +0000
@@ -1,7 +1,8 @@
 Source: pyroute2
 Section: python
 Priority: optional
-Maintainer: Debian OpenStack <team+openstack@tracker.debian.org>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Debian OpenStack <team+openstack@tracker.debian.org>
 Uploaders:
  Thomas Goirand <zigo@debian.org>,
  Florian Pelgrim <florian.pelgrim@craneworks.de>,
@@ -20,7 +21,6 @@ Build-Depends-Indep:
  python3-bottle,
  python3-coverage,
  python3-eventlet,
- python3-mitogen,
  python3-netaddr,
  python3-nox,
  python3-psutil,
@@ -29,8 +29,8 @@ Build-Depends-Indep:
  python3-pytest-cov,
  python3-sphinx,
 Standards-Version: 4.5.0
-VCS-Browser: https://salsa.debian.org/openstack-team/third-party/pyroute2
-VCS-Git: https://salsa.debian.org/openstack-team/third-party/pyroute2.git
+VCS-Browser: https://salsa.debian.org/openstack/third-party/pyroute2
+VCS-Git: https://salsa.debian.org/openstack/third-party/pyroute2.git
 Homepage: https://github.com/svinota/pyroute2
 
 Package: python-pyroute2-doc
diff -pruN 0.7.7-4/debian/rules 0.7.11-0ubuntu3/debian/rules
--- 0.7.7-4/debian/rules	2024-02-22 17:25:30.000000000 +0000
+++ 0.7.11-0ubuntu3/debian/rules	2024-02-15 16:50:18.000000000 +0000
@@ -13,7 +13,6 @@ override_dh_auto_clean:
 	rm -rf build .stestr *.egg-info .pybuild
 	find . -iname '*.pyc' -delete
 	for i in $$(find . -type d -iname __pycache__) ; do rm -rf $$i ; done
-	rm -f pyroute2/config/version.py
 
 override_dh_auto_test:
 ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS)))
@@ -26,7 +25,6 @@ endif
 
 override_dh_auto_build:
 	echo $$OSLO_PACKAGE_VERSION > VERSION
-	$(MAKE) VERSION
 	dh_auto_build
 
 override_dh_installchangelogs:
diff -pruN 0.7.7-4/debian/tests/control 0.7.11-0ubuntu3/debian/tests/control
--- 0.7.7-4/debian/tests/control	2024-02-22 17:25:30.000000000 +0000
+++ 0.7.11-0ubuntu3/debian/tests/control	1970-01-01 00:00:00.000000000 +0000
@@ -1,4 +0,0 @@
-Tests:
- version-is-not-unknown
-Restrictions:
- superficial
diff -pruN 0.7.7-4/debian/tests/version-is-not-unknown 0.7.11-0ubuntu3/debian/tests/version-is-not-unknown
--- 0.7.7-4/debian/tests/version-is-not-unknown	2024-02-22 17:25:30.000000000 +0000
+++ 0.7.11-0ubuntu3/debian/tests/version-is-not-unknown	1970-01-01 00:00:00.000000000 +0000
@@ -1,9 +0,0 @@
-#!/usr/bin/python3
-
-from pyroute2 import __version__
-from sys import exit
-
-print(f"pyroute2.__version__ is '{__version__}'")
-
-if __version__ == "unknown":
-    exit(1)
diff -pruN 0.7.7-4/debian/watch 0.7.11-0ubuntu3/debian/watch
--- 0.7.7-4/debian/watch	2024-02-22 17:25:30.000000000 +0000
+++ 0.7.11-0ubuntu3/debian/watch	2024-02-15 16:50:18.000000000 +0000
@@ -1,3 +1,3 @@
 version=3
-opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
-https://pypi.debian.net/pyroute2/pyroute2-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
+opts="uversionmangle=s/\.(b|rc)/~$1/" \
+https://github.com/svinota/pyroute2/tags .*/(\d[\d\.]+)\.tar\.gz
diff -pruN 0.7.7-4/docs/arch.rst 0.7.11-0ubuntu3/docs/arch.rst
--- 0.7.7-4/docs/arch.rst	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/docs/arch.rst	2024-01-12 18:27:09.000000000 +0000
@@ -74,7 +74,7 @@ match responses, and the pair `put()/get
 cache thread
 ------------
 
-Sometimes it is preferrable to get incoming messages asap
+Sometimes it is preferable to get incoming messages asap
 and parse them only when there is time for that. For that
 case the `NetlinkSocketBase` provides a possibility to start a
 dedicated cache thread, that will collect and queue incoming
diff -pruN 0.7.7-4/docs/ndb_init.rst 0.7.11-0ubuntu3/docs/ndb_init.rst
--- 0.7.7-4/docs/ndb_init.rst	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/docs/ndb_init.rst	2024-01-12 18:27:09.000000000 +0000
@@ -81,7 +81,7 @@ sources
     2020-03-24 18:01:48,537    DEBUG pyroute2.ndb.139900805197264.sources.test02: running
 
 
-The RTNL sources documenation: :ref:`ndbsources`
+The RTNL sources documentation: :ref:`ndbsources`
 
 db_provider, db_spec
 ~~~~~~~~~~~~~~~~~~~~
diff -pruN 0.7.7-4/docs/parser.rst 0.7.11-0ubuntu3/docs/parser.rst
--- 0.7.7-4/docs/parser.rst	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/docs/parser.rst	2024-01-12 18:27:09.000000000 +0000
@@ -198,8 +198,8 @@ Mandatory message fields, expected by Ne
 Per-request parsers
 -------------------
 
-Sometimes it may be reasonable to handle a particular response with a
-spcific parser, rather than a generic one. An example is
+Sometimes, it may be reasonable to handle a particular response with a
+specific parser rather than a generic one. An example is
 `IPRoute.get_default_routes()`, which could be slow on systems with
 huge amounts of routes.
 
diff -pruN 0.7.7-4/examples/ethtool/ethtool-ioctl_get_infos.py 0.7.11-0ubuntu3/examples/ethtool/ethtool-ioctl_get_infos.py
--- 0.7.7-4/examples/ethtool/ethtool-ioctl_get_infos.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/ethtool/ethtool-ioctl_get_infos.py	2024-01-12 18:27:09.000000000 +0000
@@ -26,3 +26,7 @@ for name, value, not_fixed, _, _ in dev.
 print("\n=== Device coalesce: ===")
 for name, value in dev.get_coalesce().items():
     print("\t{}: {}".format(name, value))
+
+print("\n=== Device statistics: ===")
+for name, value in dev.get_statistics():
+    print("\t{}: {}".format(name, value))
diff -pruN 0.7.7-4/examples/generic/netl.py 0.7.11-0ubuntu3/examples/generic/netl.py
--- 0.7.7-4/examples/generic/netl.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/generic/netl.py	2024-01-12 18:27:09.000000000 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import traceback
 from pyroute2.netlink import NLM_F_REQUEST
diff -pruN 0.7.7-4/examples/iproute/socketcan.py 0.7.11-0ubuntu3/examples/iproute/socketcan.py
--- 0.7.7-4/examples/iproute/socketcan.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/iproute/socketcan.py	2024-01-12 18:27:09.000000000 +0000
@@ -0,0 +1,21 @@
+'''
+Simplest example to set CAN bitrate.
+'''
+
+from pyroute2 import IPRoute
+
+with IPRoute() as ip_route:
+    # loolkup can0 interface
+    idx = ip_route.link_lookup(ifname='can0')[0]
+    link = ip_route.link('get', index=idx)
+
+    # bring can0 interface down. CAN settings can be set only
+    # if the interface is down
+    if 'state' in link[0] and link[0]['state'] == 'up':
+        ip_route.link('set', index=idx, state='down')
+
+    # set CAN birate
+    ip_route.link('set', index=idx, kind='can', can_bittiming={'bitrate': 250000 })
+
+    # bring can0 interface up
+    ip_route.link('set', index=idx, state='up')
diff -pruN 0.7.7-4/examples/ndb/keystone_auth.py 0.7.11-0ubuntu3/examples/ndb/keystone_auth.py
--- 0.7.7-4/examples/ndb/keystone_auth.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/ndb/keystone_auth.py	2024-01-12 18:27:09.000000000 +0000
@@ -40,7 +40,7 @@ run::
 
     $ . openstack.rc  # <-- your OpenStack APIv3 RC file
     $ export PYTHONPATH=`pwd`
-    $ python examples/ndb/keystone_auth.py 14080769fe05e1f8b837fb43ca0f0ba4
+    $ python3 examples/ndb/keystone_auth.py 14080769fe05e1f8b837fb43ca0f0ba4
 
 Using this example you can implement services that export NDB via any RPC,
 e.g. HTTP, and use Keystone integration. Same scheme may be used for any
diff -pruN 0.7.7-4/examples/ndb/radius_auth.py 0.7.11-0ubuntu3/examples/ndb/radius_auth.py
--- 0.7.7-4/examples/ndb/radius_auth.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/ndb/radius_auth.py	2024-01-12 18:27:09.000000000 +0000
@@ -32,7 +32,7 @@ Then setup your client::
     export PYTHONPATH=`pwd`
 
     $ . radius.rc
-    $ python examples/ndb/radius_auth.py testing secret
+    $ python3 examples/ndb/radius_auth.py testing secret
 
 '''
 
diff -pruN 0.7.7-4/examples/policy/policy.py 0.7.11-0ubuntu3/examples/policy/policy.py
--- 0.7.7-4/examples/policy/policy.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/policy/policy.py	2024-01-12 18:27:09.000000000 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import traceback
 from pprint import pprint
diff -pruN 0.7.7-4/examples/wifi/nl80211_interfaces.py 0.7.11-0ubuntu3/examples/wifi/nl80211_interfaces.py
--- 0.7.7-4/examples/wifi/nl80211_interfaces.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/wifi/nl80211_interfaces.py	2024-01-12 18:27:09.000000000 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 #
 from pyroute2.iwutil import IW
diff -pruN 0.7.7-4/examples/wifi/nl80211_set_type.py 0.7.11-0ubuntu3/examples/wifi/nl80211_set_type.py
--- 0.7.7-4/examples/wifi/nl80211_set_type.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.7.11-0ubuntu3/examples/wifi/nl80211_set_type.py	2024-01-12 18:27:09.000000000 +0000
@@ -0,0 +1,22 @@
+import errno
+from pyroute2 import IW
+from pyroute2 import IPRoute
+from pyroute2.netlink.exceptions import NetlinkError
+from pyroute2.netlink.nl80211 import IFTYPE_NAMES
+
+# interface name to check
+ifname = 'wlx2'
+iftype = 'monitor'
+
+iw = IW()
+ip = IPRoute()
+index = ip.link_lookup(ifname=ifname)[0]
+try:
+    print(f"Original type: '{iw.get_interface_type(index)}'")
+    iw.set_interface_type(index, iftype)
+    print(f"New state: '{iw.get_interface_type(index)}'")
+except NetlinkError as e:
+    print(f"Exception : {e}")
+finally:
+    iw.close()
+    ip.close()
diff -pruN 0.7.7-4/noxfile.py 0.7.11-0ubuntu3/noxfile.py
--- 0.7.7-4/noxfile.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/noxfile.py	2024-01-12 18:27:09.000000000 +0000
@@ -16,7 +16,9 @@ nox.options.sessions = [
     'neutron',
     'integration',
     'linux-3.6',
+    'linux-3.8',
     'linux-3.10',
+    'linux-3.12',
     'minimal',
 ]
 
@@ -75,6 +77,9 @@ def options(module, config):
         '--verbose',
         '--junitxml=junit.xml',
     ]
+    if config.get('fail_on_warnings'):
+        ret.insert(1, 'error')
+        ret.insert(1, '-W')
     if config.get('pdb'):
         ret.append('--pdb')
     if config.get('coverage'):
@@ -240,7 +245,7 @@ def integration(session, config):
     session.run(*options('test_integration', config))
 
 
-@nox.session(python=['3.6', '3.10'])
+@nox.session(python=['3.6', '3.8', '3.10', '3.12'])
 @add_session_config
 def linux(session, config):
     '''Run Linux functional tests. Requires root to run all the tests.'''
@@ -328,3 +333,11 @@ def build(session):
 def build_minimal(session, config):
     '''Build the minimal package'''
     setup_venv_minimal(session, config)
+
+
+@nox.session
+@add_session_config
+def upload(session, config):
+    '''Upload built packages'''
+    session.install('twine')
+    session.run('python', '-m', 'twine', 'upload', 'dist/*')
diff -pruN 0.7.7-4/pyroute2/cli/console.py 0.7.11-0ubuntu3/pyroute2/cli/console.py
--- 0.7.7-4/pyroute2/cli/console.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/cli/console.py	2024-01-12 18:27:09.000000000 +0000
@@ -18,7 +18,9 @@ class Console(code.InteractiveConsole):
     def __init__(self, stdout=None, log=None, sources=None):
         global HAS_READLINE
         self.db = NDB(log=log, sources=sources)
-        self.db.config = {'show_format': 'json'}
+        self.db.config.update(
+            {'show_format': 'json', 'recordset_pipe': 'true'}
+        )
         self.stdout = stdout or sys.stdout
         self.session = Session(self.db, self.stdout, self.set_prompt)
         self.matches = []
diff -pruN 0.7.7-4/pyroute2/cli/server.py 0.7.11-0ubuntu3/pyroute2/cli/server.py
--- 0.7.7-4/pyroute2/cli/server.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/cli/server.py	2024-01-12 18:27:09.000000000 +0000
@@ -102,5 +102,7 @@ class Server(HTTPServer):
             self.ndb = ndb
         else:
             self.ndb = NDB(sources=sources, log=log)
-        self.ndb.config = {'show_format': 'json'}
+        self.ndb.config.update(
+            {'show_format': 'json', 'recordset_pipe': 'true'}
+        )
         HTTPServer.__init__(self, (address, port), Handler)
diff -pruN 0.7.7-4/pyroute2/ethtool/ethtool.py 0.7.11-0ubuntu3/pyroute2/ethtool/ethtool.py
--- 0.7.7-4/pyroute2/ethtool/ethtool.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ethtool/ethtool.py	2024-01-12 18:27:09.000000000 +0000
@@ -13,7 +13,7 @@ from pyroute2.ethtool.common import (
 )
 from pyroute2.ethtool.ioctl import WAKE_NAMES, IoctlEthtool
 from pyroute2.netlink.exceptions import NetlinkError
-from pyroute2.netlink.generic.ethtool import NlEthtool
+from pyroute2.netlink.generic.ethtool import NlEthtool, ethtool_rings_msg
 
 INT32MINUS_UINT32 = c_uint32(-1).value
 INT16MINUS_UINT16 = c_uint16(-1).value
@@ -257,6 +257,108 @@ class EthtoolLinkMode(
         )
 
 
+class EthtoolRings(
+    namedtuple(
+        'EthtoolRings',
+        (
+            "rx_max",
+            "rx_mini_max",
+            "rx_jumbo_max",
+            "tx_max",
+            "rx",
+            "rx_mini",
+            "rx_jumbo",
+            "tx",
+            "rx_buf_len",
+            "tcp_data_split",
+            "cqe_size",
+            "tx_push",
+            "rx_push",
+            "tx_push_buf_len",
+            "tx_push_buf_len_max",
+        ),
+    )
+):
+    nl_attributs_dict = {
+        "rx_max": 'ETHTOOL_A_RINGS_RX_MAX',
+        "rx_mini_max": 'ETHTOOL_A_RINGS_RX_MINI_MAX',
+        "rx_jumbo_max": 'ETHTOOL_A_RINGS_RX_JUMBO_MAX',
+        "tx_max": 'ETHTOOL_A_RINGS_TX_MAX',
+        "rx": 'ETHTOOL_A_RINGS_RX',
+        "rx_mini": 'ETHTOOL_A_RINGS_RX_MINI',
+        "rx_jumbo": 'ETHTOOL_A_RINGS_RX_JUMBO',
+        "tx": 'ETHTOOL_A_RINGS_TX',
+        "rx_buf_len": 'ETHTOOL_A_RINGS_RX_BUF_LEN',
+        "tcp_data_split": 'ETHTOOL_A_RINGS_TCP_DATA_SPLIT',
+        "cqe_size": 'ETHTOOL_A_RINGS_CQE_SIZE',
+        "tx_push": 'ETHTOOL_A_RINGS_TX_PUSH',
+        "rx_push": 'ETHTOOL_A_RINGS_RX_PUSH',
+        "tx_push_buf_len": 'ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN',
+        "tx_push_buf_len_max": 'ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX',
+    }
+
+    def __new__(
+        cls,
+        rx_max=None,
+        rx_mini_max=None,
+        rx_jumbo_max=None,
+        tx_max=None,
+        rx=None,
+        rx_mini=None,
+        rx_jumbo=None,
+        tx=None,
+        rx_buf_len=None,
+        tcp_data_split=None,
+        cqe_size=None,
+        tx_push=None,
+        rx_push=None,
+        tx_push_buf_len=None,
+        tx_push_buf_len_max=None,
+    ):
+        return super(EthtoolRings, cls).__new__(
+            cls,
+            rx_max,
+            rx_mini_max,
+            rx_jumbo_max,
+            tx_max,
+            rx,
+            rx_mini,
+            rx_jumbo,
+            tx,
+            rx_buf_len,
+            tcp_data_split,
+            cqe_size,
+            tx_push,
+            rx_push,
+            tx_push_buf_len,
+            tx_push_buf_len_max,
+        )
+
+    @classmethod
+    def from_netlink(cls, nl_rings):
+        nl_rings = nl_rings[0]
+        return cls(
+            **{
+                cls_attr: nl_rings.get_attr(netlink_attr)
+                for cls_attr, netlink_attr in cls.nl_attributs_dict.items()
+            }
+        )
+
+    def to_netlink(self):
+        nl_rings_attrs = ethtool_rings_msg()
+        for cls_attr, netlink_attr in self.nl_attributs_dict.items():
+            attr = getattr(self, cls_attr)
+            if attr is not None:
+                nl_rings_attrs["attrs"].append((netlink_attr, attr))
+        return nl_rings_attrs
+
+    @classmethod
+    def from_ioctl(cls, ioctl_rings):
+        ioctl_rings = dict(ioctl_rings)
+        ioctl_rings.pop("cmd")
+        return cls(**ioctl_rings)
+
+
 class Ethtool:
     def __init__(self):
         self._with_ioctl = IoctlEthtool()
@@ -330,6 +432,31 @@ class Ethtool:
             wol_mode = self._with_ioctl.get_wol()
             return EthtoolWakeOnLan.from_ioctl(wol_mode)
 
+    def get_rings(self, ifname, with_netlink=None):
+        try:
+            rings = self._nl_exec(
+                self._with_nl.get_rings, with_netlink, ifname
+            )
+            rings = EthtoolRings.from_netlink(rings)
+        except UseIoctl:
+            self._with_ioctl.change_ifname(ifname)
+            rings_info = self._with_ioctl.get_rings()
+            rings = EthtoolRings.from_ioctl(rings_info)
+        return rings
+
+    def set_rings(self, ifname, with_netlink=None, **kwargs):
+        try:
+            rings = EthtoolRings(**kwargs).to_netlink()
+            self._nl_exec(self._with_nl.set_rings, with_netlink, rings, ifname)
+        except UseIoctl:
+            self._with_ioctl.change_ifname(ifname)
+            ioctl_rings = self._with_ioctl.get_rings()
+            for name, value in kwargs.items():
+                if name in ioctl_rings.keys() and ioctl_rings[name] != value:
+                    ioctl_rings[name] = value
+
+            self._with_ioctl.set_rings(ioctl_rings)
+
     def get_features(self, ifname):
         self._with_ioctl.change_ifname(ifname)
         return EthtoolFeatures.from_ioctl(self._with_ioctl.get_features())
diff -pruN 0.7.7-4/pyroute2/ethtool/ioctl.py 0.7.11-0ubuntu3/pyroute2/ethtool/ioctl.py
--- 0.7.7-4/pyroute2/ethtool/ioctl.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ethtool/ioctl.py	2024-01-12 18:27:09.000000000 +0000
@@ -20,8 +20,12 @@ ETHTOOL_SFEATURES = 0x0000003B
 ETHTOOL_GLINKSETTINGS = 0x0000004C
 
 ETHTOOL_GSTRINGS = 0x0000001B
+ETHTOOL_GSTATS = 0x0000001D
 ETH_GSTRING_LEN = 32
 
+ETHTOOL_GRINGPARAM = 0x00000010
+ETHTOOL_SRINGPARAM = 0x00000011
+
 ETHTOOL_GRXCSUM = 0x00000014
 ETHTOOL_SRXCSUM = 0x00000015
 ETHTOOL_GTXCSUM = 0x00000016
@@ -39,6 +43,7 @@ ETHTOOL_SGRO = 0x0000002C
 
 SOPASS_MAX = 6
 
+ETH_SS_STATS = 1
 ETH_SS_FEATURES = 4
 
 ETH_FLAG_RXCSUM = 1 << 0
@@ -274,15 +279,16 @@ class EthtoolSsetInfo(ctypes.Structure):
     ]
 
 
-class EthtoolGstrings(ctypes.Structure):
-    _fields_ = [
-        ("cmd", ctypes.c_uint32),
-        ("string_set", ctypes.c_uint32),
-        ("len", ctypes.c_uint32),
-        # If you have more than 256 features on your NIC
-        # they will not be seen by it
-        ("strings", ctypes.c_ubyte * ETH_GSTRING_LEN * 256),
-    ]
+def generate_EthtoolGstrings(gstrings_length):
+    class EthtoolGstrings(ctypes.Structure):
+        _fields_ = [
+            ("cmd", ctypes.c_uint32),
+            ("string_set", ctypes.c_uint32),
+            ("len", ctypes.c_uint32),
+            ("strings", ctypes.c_ubyte * ETH_GSTRING_LEN * gstrings_length),
+        ]
+
+    return EthtoolGstrings
 
 
 class EthtoolGetFeaturesBlock(ctypes.Structure):
@@ -298,6 +304,17 @@ class EthtoolSetFeaturesBlock(ctypes.Str
     _fields_ = [("changed", ctypes.c_uint32), ("active", ctypes.c_uint32)]
 
 
+def generate_EthtoolGStats(stats_length):
+    class EthtoolGStats(ctypes.Structure):
+        _fields_ = [
+            ("cmd", ctypes.c_uint32),
+            ("size", ctypes.c_uint32),
+            ("data", ctypes.c_uint64 * stats_length),
+        ]
+
+    return EthtoolGStats
+
+
 def div_round_up(n, d):
     return int(((n) + (d) - 1) / (d))
 
@@ -326,17 +343,35 @@ class FeatureState(ctypes.Structure):
     _fields_ = [("off_flags", ctypes.c_uint32), ("features", EthtoolGfeatures)]
 
 
+class EthtoolRingParam(DictStruct):
+    _pack_ = 1
+    _fields_ = [
+        ("cmd", ctypes.c_uint32),
+        ("rx_max", ctypes.c_uint32),
+        ("rx_mini_max", ctypes.c_uint32),
+        ("rx_jumbo_max", ctypes.c_uint32),
+        ("tx_max", ctypes.c_uint32),
+        ("rx", ctypes.c_uint32),
+        ("rx_mini", ctypes.c_uint32),
+        ("rx_jumbo", ctypes.c_uint32),
+        ("tx", ctypes.c_uint32),
+    ]
+
+
 class IfReqData(ctypes.Union):
+    dummy = generate_EthtoolGstrings(0)
     _fields_ = [
         ("ifr_data", ctypes.POINTER(EthtoolCmd)),
         ("coalesce", ctypes.POINTER(EthtoolCoalesce)),
         ("value", ctypes.POINTER(EthtoolValue)),
         ("sset_info", ctypes.POINTER(EthtoolSsetInfo)),
-        ("gstrings", ctypes.POINTER(EthtoolGstrings)),
+        ("gstrings", ctypes.POINTER(None)),
+        ("gstats", ctypes.POINTER(None)),
         ("gfeatures", ctypes.POINTER(EthtoolGfeatures)),
         ("sfeatures", ctypes.POINTER(EthtoolSfeatures)),
         ("glinksettings", ctypes.POINTER(IoctlEthtoolLinkSettings)),
         ("wolinfo", ctypes.POINTER(EthtoolWolInfo)),
+        ("rings", ctypes.POINTER(EthtoolRingParam)),
     ]
 
 
@@ -346,6 +381,14 @@ class IfReq(ctypes.Structure):
     _fields_ = [("ifr_name", ctypes.c_uint8 * IFNAMSIZ), ("u", IfReqData)]
 
 
+class IfReqSsetInfo(ctypes.Structure):
+    _pack_ = 1
+    _fields_ = [
+        ("ifr_name", ctypes.c_uint8 * IFNAMSIZ),
+        ("info", ctypes.POINTER(EthtoolSsetInfo)),
+    ]
+
+
 class EthtoolFeaturesList:
     def __init__(self, cmd, stringsset):
         self._offsets = {}
@@ -429,6 +472,7 @@ class IoctlEthtool:
         self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         self.ifname = None
         self.ifreq = None
+        self.stat_names = None
 
         if ifname is not None:
             self.change_ifname(ifname)
@@ -441,6 +485,7 @@ class IoctlEthtool:
         self.ifname.extend(b"\0" * (IFNAMSIZ - len(self.ifname)))
         self.ifreq = IfReq()
         self.ifreq.ifr_name = (ctypes.c_uint8 * IFNAMSIZ)(*self.ifname)
+        self.stat_names = None
 
     def ioctl(self):
         try:
@@ -453,27 +498,48 @@ class IoctlEthtool:
                 raise NoSuchDevice(self.ifname.decode("utf-8"))
             raise
 
-    def get_stringset(
-        self, set_id=ETH_SS_FEATURES, drvinfo_offset=0, null_terminate=1
-    ):
+    def get_statistics(self):
+        """Statistics in raw format, without names"""
+        if not self.stat_names:
+            self.stat_names = self.get_stringset(set_id=ETH_SS_STATS)
+        gstats = generate_EthtoolGStats(len(self.stat_names))(
+            cmd=ETHTOOL_GSTATS
+        )
+        self.ifreq.gstats = ctypes.cast(
+            ctypes.pointer(gstats), ctypes.POINTER(None)
+        )
+        self.ioctl()
+        assert len(self.stat_names) == len(gstats.data)
+        return list(zip(self.stat_names, gstats.data))
+
+    def get_stringset_length(self, set_id):
         sset_info = EthtoolSsetInfo(
             cmd=ETHTOOL_GSSET_INFO, reserved=0, sset_mask=1 << set_id
         )
-        self.ifreq.sset_info = ctypes.pointer(sset_info)
-        fcntl.ioctl(self.sock, SIOCETHTOOL, self.ifreq)
-        if sset_info.sset_mask:
-            length = sset_info.data
-        else:
-            length = 0
+        ifreq_sset = IfReqSsetInfo()
+        ifreq_sset.ifr_name = (ctypes.c_uint8 * IFNAMSIZ)(*self.ifname)
+        ifreq_sset.info = ctypes.pointer(sset_info)
+        fcntl.ioctl(self.sock, SIOCETHTOOL, ifreq_sset)
+        assert sset_info.sset_mask
+        return sset_info.data
 
-        strings_found = []
-        gstrings = EthtoolGstrings(
-            cmd=ETHTOOL_GSTRINGS, string_set=set_id, len=length
+    def get_stringset(
+        self, set_id=ETH_SS_FEATURES, drvinfo_offset=0, null_terminate=1
+    ):
+        # different sets have potentially different lengthts,
+        # obtain size dynamically
+        gstrings_length = self.get_stringset_length(set_id)
+        EthtoolGstringsType = generate_EthtoolGstrings(gstrings_length)
+        gstrings = EthtoolGstringsType(
+            cmd=ETHTOOL_GSTRINGS, string_set=set_id, len=gstrings_length
+        )
+        self.ifreq.gstrings = ctypes.cast(
+            ctypes.pointer(gstrings), ctypes.POINTER(None)
         )
-        self.ifreq.gstrings = ctypes.pointer(gstrings)
         self.ioctl()
 
-        for i in range(length):
+        strings_found = []
+        for i in range(gstrings_length):
             buf = ''
             for j in range(ETH_GSTRING_LEN):
                 code = gstrings.strings[i][j]
@@ -484,7 +550,7 @@ class IoctlEthtool:
         return strings_found
 
     def get_features(self):
-        stringsset = self.get_stringset()
+        stringsset = self.get_stringset(set_id=ETH_SS_FEATURES)
         cmd = EthtoolGfeatures()
         cmd.cmd = ETHTOOL_GFEATURES
         cmd.size = feature_bits_to_blocks(len(stringsset))
@@ -580,3 +646,14 @@ class IoctlEthtool:
         self.ifreq.wolinfo = ctypes.pointer(cmd)
         self.ioctl()
         return cmd
+
+    def get_rings(self):
+        cmd = EthtoolRingParam(cmd=ETHTOOL_GRINGPARAM)
+        self.ifreq.rings = ctypes.pointer(cmd)
+        self.ioctl()
+        return cmd
+
+    def set_rings(self, rings):
+        rings.cmd = ETHTOOL_SRINGPARAM
+        self.ifreq.rings = ctypes.pointer(rings)
+        self.ioctl()
diff -pruN 0.7.7-4/pyroute2/iproute/linux.py 0.7.11-0ubuntu3/pyroute2/iproute/linux.py
--- 0.7.7-4/pyroute2/iproute/linux.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/iproute/linux.py	2024-01-12 18:27:09.000000000 +0000
@@ -55,6 +55,7 @@ from pyroute2.netlink.rtnl import (
     RTM_NEWLINKPROP,
     RTM_NEWNEIGH,
     RTM_NEWNETNS,
+    RTM_NEWNSID,
     RTM_NEWQDISC,
     RTM_NEWROUTE,
     RTM_NEWRULE,
@@ -68,6 +69,7 @@ from pyroute2.netlink.rtnl import (
     RTMGRP_IPV6_ROUTE,
     RTMGRP_IPV6_RULE,
     RTMGRP_LINK,
+    RTMGRP_MPLS_ROUTE,
     RTMGRP_NEIGH,
     TC_H_ROOT,
     ndmsg,
@@ -328,6 +330,7 @@ class RTNL_API:
                 RTMGRP_NEIGH: [self.get_neighbours],
                 RTMGRP_IPV4_ROUTE: [partial(self.get_routes, family=AF_INET)],
                 RTMGRP_IPV6_ROUTE: [partial(self.get_routes, family=AF_INET6)],
+                RTMGRP_MPLS_ROUTE: [partial(self.get_routes, family=AF_MPLS)],
                 RTMGRP_IPV4_RULE: [partial(self.get_rules, family=AF_INET)],
                 RTMGRP_IPV6_RULE: [partial(self.get_rules, family=AF_INET6)],
             }
@@ -739,6 +742,27 @@ class RTNL_API:
             except OSError:
                 pass
 
+    def set_netnsid(self, nsid=None, pid=None, fd=None):
+        '''Assigns an id to a peer netns using RTM_NEWNSID query.
+        The kernel chooses an unique id if nsid is omitted.
+        This corresponds to the "ip netns set" command.
+        '''
+        msg = nsidmsg()
+
+        if nsid is None or nsid < 0:
+            # kernel auto select
+            msg['attrs'].append(('NETNSA_NSID', 4294967295))
+        else:
+            msg['attrs'].append(('NETNSA_NSID', nsid))
+
+        if pid is not None:
+            msg['attrs'].append(('NETNSA_PID', pid))
+
+        if fd is not None:
+            msg['attrs'].append(('NETNSA_FD', fd))
+
+        return self.nlm_request(msg, RTM_NEWNSID, NLM_F_REQUEST | NLM_F_ACK)
+
     # 8<---------------------------------------------------------------
 
     # 8<---------------------------------------------------------------
diff -pruN 0.7.7-4/pyroute2/iwutil.py 0.7.11-0ubuntu3/pyroute2/iwutil.py
--- 0.7.7-4/pyroute2/iwutil.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/iwutil.py	2024-01-12 18:27:09.000000000 +0000
@@ -683,3 +683,57 @@ class IW(NL80211):
         self.nlm_request(
             msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
         )
+
+    def set_interface_type(self, ifindex, iftype):
+        '''
+        Set interface type
+            - ifindex — device index
+            - iftype — interface type
+
+        `iftype` can be integer or string:
+        1. adhoc
+        2. station
+        3. ap
+        4. ap_vlan
+        5. wds
+        6. monitor
+        7. mesh_point
+        8. p2p_client
+        9. p2p_go
+        10. p2p_device
+        11. ocb
+        '''
+
+        iftype = IFTYPE_NAMES.get(iftype, iftype)
+        if not isinstance(iftype, int):
+            raise TypeError('iftype must be int')
+
+        msg = nl80211cmd()
+        msg['cmd'] = NL80211_NAMES['NL80211_CMD_SET_INTERFACE']
+
+        msg['attrs'] = [
+            ['NL80211_ATTR_IFINDEX', ifindex],
+            ['NL80211_ATTR_IFTYPE', iftype],
+        ]
+
+        self.nlm_request(
+            msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
+        )
+
+    def get_interface_type(self, ifindex) -> str:
+        '''
+        return interface type name
+        '''
+        dump = self.get_interface_by_ifindex(ifindex)
+        type = None
+        for d in dump:
+            type = d.get_attr('NL80211_ATTR_IFTYPE')
+
+        if type is not None:
+            for key, value in IFTYPE_NAMES.items():
+                if value == type:
+                    res = key
+        else:
+            res = 'Not Found Type'
+
+        return res
diff -pruN 0.7.7-4/pyroute2/lab.py 0.7.11-0ubuntu3/pyroute2/lab.py
--- 0.7.7-4/pyroute2/lab.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/lab.py	2024-01-12 18:27:09.000000000 +0000
@@ -1,5 +1,9 @@
 import inspect
-from unittest import mock
+
+try:
+    from unittest import mock
+except ImportError:
+    mock = None
 
 registry = []
 use_mock = False
@@ -9,6 +13,16 @@ class LAB_API:
     def __init__(self, *argv, **kwarg):
         super().__init__(*argv, **kwarg)
         if use_mock:
+            if mock is None:
+                # postpone ImportError
+                #
+                # unittest may not be available on embedded platforms,
+                # but it is still used by IPRoute class; it is safe
+                # to leave it in the minimal for now, just raise an
+                # exception when being used
+                #
+                # Bug-Url: https://github.com/svinota/pyroute2/pull/1096
+                raise ImportError('unittest.mock not available')
             registry.append(self)
             for name, method in inspect.getmembers(
                 self, predicate=inspect.ismethod
diff -pruN 0.7.7-4/pyroute2/ndb/compat.py 0.7.11-0ubuntu3/pyroute2/ndb/compat.py
--- 0.7.7-4/pyroute2/ndb/compat.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ndb/compat.py	2024-01-12 18:27:09.000000000 +0000
@@ -18,30 +18,51 @@ def ipdb_interfaces_view(ndb):
         interface = record._as_dict()
         interface['ipdb_scope'] = 'system'
         interface['ipdb_priority'] = 0
-        interface['ipaddr'] = tuple(
-            (
-                (x.address, x.prefixlen)
-                for x in (
-                    ndb.addresses.dump().select_records(index=record.index)
+        try:
+            interface['ipaddr'] = tuple(
+                (
+                    (x.address, x.prefixlen)
+                    for x in (
+                        ndb.addresses.dump().select_records(index=record.index)
+                    )
                 )
             )
-        )
-        interface['ports'] = tuple(
-            (
-                x.index
-                for x in (
-                    ndb.interfaces.dump().select_records(master=record.index)
+        except:
+            with ndb.addresses.summary() as report:
+                report.select_records(ifname=f"{record.ifname}")
+                interface['ipaddr'] = tuple(
+                    ((x.address, x.prefixlen) for x in report)
+                )
+        try:
+            interface['ports'] = tuple(
+                (
+                    x.index
+                    for x in (
+                        ndb.interfaces.dump().select_records(
+                            master=record.index
+                        )
+                    )
                 )
             )
-        )
-        interface['neighbours'] = tuple(
-            (
-                x.dst
-                for x in (
-                    ndb.neighbours.dump().select_records(ifindex=record.index)
+        except:
+            with ndb.interfaces.dump() as report:
+                report.select_records(ifname=f"{record.ifname}")
+                interface['ports'] = tuple((x.index for x in report))
+        try:
+            interface['neighbours'] = tuple(
+                (
+                    x.dst
+                    for x in (
+                        ndb.neighbours.dump().select_records(
+                            ifindex=record.index
+                        )
+                    )
                 )
             )
-        )
+        except:
+            with ndb.neighbours.dump() as report:
+                report.select_records(ifindex=record.index)
+                interface['neighbours'] = tuple((x.dst for x in report))
         ret[record.ifname] = interface
 
     return ret
diff -pruN 0.7.7-4/pyroute2/ndb/main.py 0.7.11-0ubuntu3/pyroute2/ndb/main.py
--- 0.7.7-4/pyroute2/ndb/main.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ndb/main.py	2024-01-12 18:27:09.000000000 +0000
@@ -537,6 +537,7 @@ class NDB:
             'rtnl_debug': rtnl_debug,
             'db_cleanup': db_cleanup,
             'auto_netns': auto_netns,
+            'recordset_pipe': 'false',
         }
         self.task_manager = TaskManager(self)
         self._dbm_thread = threading.Thread(
diff -pruN 0.7.7-4/pyroute2/ndb/objects/neighbour.py 0.7.11-0ubuntu3/pyroute2/ndb/objects/neighbour.py
--- 0.7.7-4/pyroute2/ndb/objects/neighbour.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ndb/objects/neighbour.py	2024-01-12 18:27:09.000000000 +0000
@@ -48,8 +48,9 @@ ndmsg_schema = (
 
 brmsg_schema = (
     ndmsg.sql_schema()
-    .unique_index('ifindex', 'NDA_LLADDR', 'NDA_VLAN')
+    .unique_index('ifindex', 'NDA_LLADDR', 'NDA_DST', 'NDA_VLAN')
     .constraint('NDA_LLADDR', "NOT NULL DEFAULT ''")
+    .constraint('NDA_DST', "NOT NULL DEFAULT ''")
     .constraint('NDA_VLAN', "NOT NULL DEFAULT 0")
     .foreign_key(
         'interfaces',
diff -pruN 0.7.7-4/pyroute2/ndb/report.py 0.7.11-0ubuntu3/pyroute2/ndb/report.py
--- 0.7.7-4/pyroute2/ndb/report.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ndb/report.py	2024-01-12 18:27:09.000000000 +0000
@@ -211,6 +211,20 @@ class BaseRecordSet(object):
         return ''.join(ret)
 
 
+class RecordSetConfig(dict):
+    def __init__(self, prime):
+        if isinstance(prime, dict):
+            for key, value in prime.items():
+                self[key] = value
+        else:
+            raise ValueError('only dict allowed')
+
+    def __setitem__(self, key, value):
+        if isinstance(value, str):
+            value = json.loads(value)
+        return super().__setitem__(key, value)
+
+
 class RecordSet(BaseRecordSet):
     '''
     NDB views return objects of this class with `summary()` and `dump()`
@@ -221,9 +235,10 @@ class RecordSet(BaseRecordSet):
     to make chains of filters.
     '''
 
-    def __init__(self, generator, ellipsis=True):
+    def __init__(self, generator, config=None, ellipsis=True):
         super().__init__(generator, ellipsis)
         self.filters = []
+        self.config = RecordSetConfig(config) if config is not None else {}
 
     def __next__(self):
         while True:
@@ -235,6 +250,7 @@ class RecordSet(BaseRecordSet):
             else:
                 return record
 
+    @cli.show_result
     def select_fields(self, *fields):
         '''
         Select only chosen fields for every record:
@@ -253,7 +269,10 @@ class RecordSet(BaseRecordSet):
             2,'eth0'
         '''
         self.filters.append(lambda x: x._select_fields(*fields))
+        if self.config.get('recordset_pipe'):
+            return RecordSet(self, config=self.config)
 
+    @cli.show_result
     def select_records(self, f=None, **spec):
         '''
         Select records based on a function f() or a spec match. A spec
@@ -272,7 +291,10 @@ class RecordSet(BaseRecordSet):
             'localhost',0,'eth0','192.168.122.28',24
         '''
         self.filters.append(lambda x: x if x._match(f, **spec) else None)
+        if self.config.get('recordset_pipe'):
+            return RecordSet(self, config=self.config)
 
+    @cli.show_result
     def transform_fields(self, **kwarg):
         '''
         Transform fields with a function. Function must accept
@@ -295,6 +317,8 @@ class RecordSet(BaseRecordSet):
             'eth0','192.168.122.28/24'
         '''
         self.filters.append(lambda x: x._transform_fields(**kwarg))
+        if self.config.get('recordset_pipe'):
+            return RecordSet(self, config=self.config)
 
     @cli.show_result
     def transform(self, **kwarg):
diff -pruN 0.7.7-4/pyroute2/ndb/source.py 0.7.11-0ubuntu3/pyroute2/ndb/source.py
--- 0.7.7-4/pyroute2/ndb/source.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ndb/source.py	2024-01-12 18:27:09.000000000 +0000
@@ -7,10 +7,21 @@ Local RTNL source is a simple `IPRoute`
 starts with one local RTNL source names `localhost`::
 
     >>> ndb = NDB()
-    >>> ndb.sources.details()
-    {'kind': u'local', u'nlm_generator': 1, 'target': u'localhost'}
+    >>> ndb.sources.summary().format("json")
+    [
+        {
+            "name": "localhost",
+            "spec": "{'target': 'localhost', 'nlm_generator': 1}",
+            "state": "running"
+        },
+        {
+            "name": "localhost/nsmanager",
+            "spec": "{'target': 'localhost/nsmanager'}",
+            "state": "running"
+        }
+    ]
     >>> ndb.sources['localhost']
-    [running] <IPRoute {'nlm_generator': 1}>
+    [running] <IPRoute {'target: 'localhost', 'nlm_generator': 1}>
 
 The `localhost` RTNL source starts an additional async cache thread.
 The `nlm_generator` option means that instead of collections the
@@ -18,7 +29,7 @@ The `nlm_generator` option means that in
 consume memory regardless of the RTNL objects number::
 
     >>> ndb.sources['localhost'].nl.link('dump')
-    <generator object _match at 0x7fa444961e10>
+    <generator object RTNL_API.filter_messages at 0x7f61a99a34a0>
 
 See also: :ref:`iproute`
 
@@ -180,6 +191,12 @@ class Source(dict):
         self.ndb.task_manager.db_add_nl_source(self.target, self.kind, spec)
         self.load_sql()
 
+    @classmethod
+    def _count(cls, view):
+        return view.ndb.task_manager.db_fetchone(
+            "SELECT count(*) FROM %s" % view.table
+        )
+
     @property
     def must_restart(self):
         if self.max_errors < 0 or self.errors_counter <= self.max_errors:
diff -pruN 0.7.7-4/pyroute2/ndb/view.py 0.7.11-0ubuntu3/pyroute2/ndb/view.py
--- 0.7.7-4/pyroute2/ndb/view.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/ndb/view.py	2024-01-12 18:27:09.000000000 +0000
@@ -424,13 +424,27 @@ class View(dict):
     @check_auth('obj:list')
     def dump(self):
         iclass = self.classes[self.table]
-        return RecordSet(self._native(iclass.dump(self)))
+        return RecordSet(
+            self._native(iclass.dump(self)),
+            config={
+                'recordset_pipe': self.ndb.config.get(
+                    'recordset_pipe', 'false'
+                )
+            },
+        )
 
     @cli.show_result
     @check_auth('obj:list')
     def summary(self):
         iclass = self.classes[self.table]
-        return RecordSet(self._native(iclass.summary(self)))
+        return RecordSet(
+            self._native(iclass.summary(self)),
+            config={
+                'recordset_pipe': self.ndb.config.get(
+                    'recordset_pipe', 'false'
+                )
+            },
+        )
 
     def __repr__(self):
         if self.chain and 'ifname' in self.chain:
diff -pruN 0.7.7-4/pyroute2/netlink/devlink/__init__.py 0.7.11-0ubuntu3/pyroute2/netlink/devlink/__init__.py
--- 0.7.7-4/pyroute2/netlink/devlink/__init__.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/devlink/__init__.py	2024-01-12 18:27:09.000000000 +0000
@@ -492,8 +492,8 @@ class MarshalDevlink(Marshal):
 
 
 class DevlinkSocket(GenericNetlinkSocket):
-    def __init__(self):
-        GenericNetlinkSocket.__init__(self)
+    def __init__(self, *args, **kwargs):
+        GenericNetlinkSocket.__init__(self, *args, **kwargs)
         self.marshal = MarshalDevlink()
 
     def bind(self, groups=0, **kwarg):
diff -pruN 0.7.7-4/pyroute2/netlink/diag/__init__.py 0.7.11-0ubuntu3/pyroute2/netlink/diag/__init__.py
--- 0.7.7-4/pyroute2/netlink/diag/__init__.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/diag/__init__.py	2024-01-12 18:27:09.000000000 +0000
@@ -141,8 +141,11 @@ class inet_diag_msg(inet_addr_codec):
         ('INET_DIAG_PAD', 'hex'),
         ('INET_DIAG_MARK', 'hex'),
         ('INET_DIAG_BBRINFO', 'tcp_bbr_info'),
-        ('INET_DIAG_CLASS_ID', 'hex'),
+        ('INET_DIAG_CLASS_ID', 'uint32'),
         ('INET_DIAG_MD5SIG', 'hex'),
+        ('INET_DIAG_ULP_INFO', 'hex'),
+        ('INET_DIAG_SK_BPF_STORAGES', 'hex'),
+        ('INET_DIAG_CGROUP_ID', 'uint64'),
     )
 
     class inet_diag_meminfo(nla):
diff -pruN 0.7.7-4/pyroute2/netlink/diag/ss2.py 0.7.11-0ubuntu3/pyroute2/netlink/diag/ss2.py
--- 0.7.7-4/pyroute2/netlink/diag/ss2.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/diag/ss2.py	2024-01-12 18:27:09.000000000 +0000
@@ -56,10 +56,10 @@ except ImportError:
 # UDIAG_SHOW_RQLEN,
 # UDIAG_SHOW_MEMINFO
 
+RUN_AS_MODULE = False
 
-class UserCtxtMap(Mapping):
-    _data = {}
 
+class UserCtxtMap(Mapping):
     _sk_inode_re = re.compile(r"socket:\[(?P<ino>\d+)\]")
 
     _proc_sk_fd_cast = "/proc/%d/fd/%d"
@@ -71,7 +71,7 @@ class UserCtxtMap(Mapping):
         inode = None
 
         sk_inode_raw = os.readlink(sk_path)
-        inode = self._sk_inode_re.search(sk_inode_raw).group('ino')
+        inode = self._sk_inode_re.search(sk_inode_raw).group("ino")
 
         if not inode:
             raise RuntimeError("Unexpected kernel sk inode outline")
@@ -141,14 +141,18 @@ class UserCtxtMap(Mapping):
 
     def _build(self):
         for flow in psutil.net_connections(kind="all"):
-            proc = psutil.Process(flow.pid)
-            usr = proc.username()
+            try:
+                proc = psutil.Process(flow.pid)
+                usr = proc.username()
 
-            ctxt = {"cmd": proc.exe(), "full_cmd": proc.cmdline(), "fds": []}
+                ctxt = {
+                    "cmd": proc.exe(),
+                    "full_cmd": proc.cmdline(),
+                    "fds": [],
+                }
 
-            try:
                 self._enter_item(usr, flow, ctxt)
-            except FileNotFoundError:
+            except (FileNotFoundError, AttributeError, psutil.NoSuchProcess):
                 # Handling edge case of race condition between build and parse
                 # time. That's for very volatile, shortlived flows that can
                 # exist during build but are gone once we want to parse the
@@ -156,6 +160,7 @@ class UserCtxtMap(Mapping):
                 pass
 
     def __init__(self):
+        self._data = {}
         self._build()
 
     def __getitem__(self, key):
@@ -183,21 +188,21 @@ class Protocol(Callable):
                 # gracefully
                 return None
 
-    def __init__(self, sk_states, fmt='json'):
+    def __init__(self, sk_states, fmt="json"):
         self._states = sk_states
 
         fmter = "_fmt_%s" % fmt
         self._fmt = getattr(self, fmter, None)
 
         def __call__(self, nl_diag_sk, args, usr_ctxt):
-            raise RuntimeError('not implemented')
+            raise RuntimeError("not implemented")
 
     def _fmt_json(self, refined_stats):
         return json.dumps(refined_stats, indent=4)
 
 
 class UNIX(Protocol):
-    def __init__(self, sk_states=SS_CONN, _fmt='json'):
+    def __init__(self, sk_states=SS_CONN, _fmt="json"):
         super(UNIX, self).__init__(sk_states, fmt=_fmt)
 
     def __call__(self, nl_diag_sk, args, usr_ctxt):
@@ -207,17 +212,16 @@ class UNIX(Protocol):
             show=(UDIAG_SHOW_NAME | UDIAG_SHOW_VFS | UDIAG_SHOW_PEER),
         )
         refined_stats = self._refine_diag_raw(sstats, usr_ctxt)
-        printable = self._fmt(refined_stats)
 
-        print(printable)
+        return refined_stats
 
     def _refine_diag_raw(self, raw_stats, usr_ctxt):
-        refined = {'UNIX': {'flows': []}}
+        refined = {"UNIX": {"flows": []}}
 
         def vfs_cb(raw_val):
             out = {}
-            out['inode'] = raw_val['udiag_vfs_ino']
-            out['dev'] = raw_val['udiag_vfs_dev']
+            out["inode"] = raw_val["udiag_vfs_ino"]
+            out["dev"] = raw_val["udiag_vfs_dev"]
 
             return out
 
@@ -226,17 +230,17 @@ class UNIX(Protocol):
         cb_idx = 1
 
         idiag_attr_refine_map = {
-            'UNIX_DIAG_NAME': ('path_name', None),
-            'UNIX_DIAG_VFS': ('vfs', vfs_cb),
-            'UNIX_DIAG_PEER': ('peer_inode', None),
-            'UNIX_DIAG_SHUTDOWN': ('shutdown', None),
+            "UNIX_DIAG_NAME": ("path_name", None),
+            "UNIX_DIAG_VFS": ("vfs", vfs_cb),
+            "UNIX_DIAG_PEER": ("peer_inode", None),
+            "UNIX_DIAG_SHUTDOWN": ("shutdown", None),
         }
 
         for raw_flow in raw_stats:
             vessel = {}
-            vessel['inode'] = raw_flow['udiag_ino']
+            vessel["inode"] = raw_flow["udiag_ino"]
 
-            for attr in raw_flow['attrs']:
+            for attr in raw_flow["attrs"]:
                 attr_k = attr[k_idx]
                 attr_val = attr[val_idx]
                 k = idiag_attr_refine_map[attr_k][k_idx]
@@ -247,13 +251,13 @@ class UNIX(Protocol):
 
                 vessel[k] = attr_val
 
-            refined['UNIX']['flows'].append(vessel)
+            refined["UNIX"]["flows"].append(vessel)
 
         if usr_ctxt:
-            for flow in refined['UNIX']['flows']:
+            for flow in refined["UNIX"]["flows"]:
                 try:
-                    sk_inode = flow['inode']
-                    flow['usr_ctxt'] = usr_ctxt[sk_inode]
+                    sk_inode = flow["inode"]
+                    flow["usr_ctxt"] = usr_ctxt[sk_inode]
                 except KeyError:
                     # might define sentinel val
                     pass
@@ -267,7 +271,7 @@ class TCP(Protocol):
     INET_DIAG_VEGASINFO = 3
     INET_DIAG_CONG = 4
 
-    def __init__(self, sk_states=SS_CONN, _fmt='json'):
+    def __init__(self, sk_states=SS_CONN, _fmt="json"):
         super(TCP, self).__init__(sk_states, fmt=_fmt)
 
         IDIAG_EXT_FLAGS = [
@@ -286,21 +290,20 @@ class TCP(Protocol):
             states=self._states, family=AF_INET, extensions=self.ext_f
         )
         refined_stats = self._refine_diag_raw(sstats, args.resolve, usr_ctxt)
-        printable = self._fmt(refined_stats)
 
-        print(printable)
+        return refined_stats
 
     def _refine_diag_raw(self, raw_stats, do_resolve, usr_ctxt):
-        refined = {'TCP': {'flows': []}}
+        refined = {"TCP": {"flows": []}}
 
         idiag_refine_map = {
-            'src': 'idiag_src',
-            'dst': 'idiag_dst',
-            'src_port': 'idiag_sport',
-            'dst_port': 'idiag_dport',
-            'inode': 'idiag_inode',
-            'iface_idx': 'idiag_if',
-            'retrans': 'idiag_retrans',
+            "src": "idiag_src",
+            "dst": "idiag_dst",
+            "src_port": "idiag_sport",
+            "dst_port": "idiag_dport",
+            "inode": "idiag_inode",
+            "iface_idx": "idiag_if",
+            "retrans": "idiag_retrans",
         }
 
         for raw_flow in raw_stats:
@@ -308,56 +311,56 @@ class TCP(Protocol):
             for k1, k2 in idiag_refine_map.items():
                 vessel[k1] = raw_flow[k2]
 
-            for ext_bundle in raw_flow['attrs']:
+            for ext_bundle in raw_flow["attrs"]:
                 vessel = self._refine_extension(vessel, ext_bundle)
 
-            refined['TCP']['flows'].append(vessel)
+            refined["TCP"]["flows"].append(vessel)
 
         if usr_ctxt:
-            for flow in refined['TCP']['flows']:
+            for flow in refined["TCP"]["flows"]:
                 try:
-                    sk_inode = flow['inode']
-                    flow['usr_ctxt'] = usr_ctxt[sk_inode]
+                    sk_inode = flow["inode"]
+                    flow["usr_ctxt"] = usr_ctxt[sk_inode]
                 except KeyError:
                     # might define sentinel val
                     pass
 
         if do_resolve:
-            for flow in refined['TCP']['flows']:
-                src_host = Protocol.Resolver.getHost(flow['src'])
+            for flow in refined["TCP"]["flows"]:
+                src_host = Protocol.Resolver.getHost(flow["src"])
                 if src_host:
-                    flow['src_host'] = src_host
+                    flow["src_host"] = src_host
 
-                dst_host = Protocol.Resolver.getHost(flow['dst'])
+                dst_host = Protocol.Resolver.getHost(flow["dst"])
                 if dst_host:
-                    flow['dst_host'] = dst_host
+                    flow["dst_host"] = dst_host
 
         return refined
 
     def _refine_extension(self, vessel, raw_ext):
         k, content = raw_ext
         ext_refine_map = {
-            'meminfo': {
-                'r': 'idiag_rmem',
-                'w': 'idiag_wmem',
-                'f': 'idiag_fmem',
-                't': 'idiag_tmem',
+            "meminfo": {
+                "r": "idiag_rmem",
+                "w": "idiag_wmem",
+                "f": "idiag_fmem",
+                "t": "idiag_tmem",
             }
         }
 
-        if k == 'INET_DIAG_MEMINFO':
-            mem_k = 'meminfo'
+        if k == "INET_DIAG_MEMINFO":
+            mem_k = "meminfo"
             vessel[mem_k] = {}
             for k1, k2 in ext_refine_map[mem_k].items():
                 vessel[mem_k][k1] = content[k2]
 
-        elif k == 'INET_DIAG_CONG':
-            vessel['cong_algo'] = content
+        elif k == "INET_DIAG_CONG":
+            vessel["cong_algo"] = content
 
-        elif k == 'INET_DIAG_INFO':
+        elif k == "INET_DIAG_INFO":
             vessel = self._refine_tcp_info(vessel, content)
 
-        elif k == 'INET_DIAG_SHUTDOWN':
+        elif k == "INET_DIAG_SHUTDOWN":
             pass
 
         return vessel
@@ -388,19 +391,19 @@ class TCP(Protocol):
 
         @staticmethod
         def rtt_p_cb(key, value, **ctx):
-            tcp_info_raw = ctx['raw']
+            tcp_info_raw = ctx["raw"]
 
             try:
                 if (
-                    tcp_info_raw['tcpv_enabled'] != 0
-                    and tcp_info_raw['tcpv_rtt'] != 0x7FFFFFFF
+                    tcp_info_raw["tcpv_enabled"] != 0
+                    and tcp_info_raw["tcpv_rtt"] != 0x7FFFFFFF
                 ):
-                    return tcp_info_raw['tcpv_rtt']
+                    return tcp_info_raw["tcpv_rtt"]
             except KeyError:
                 # ill practice, yet except quicker path
                 pass
 
-            return tcp_info_raw['tcpi_rtt'] / 1000.0
+            return tcp_info_raw["tcpi_rtt"] / 1000.0
 
         # converter
         @staticmethod
@@ -423,7 +426,7 @@ class TCP(Protocol):
 
         @staticmethod
         def opts_c_cb(key, value, **ctx):
-            tcp_info_raw = ctx['raw']
+            tcp_info_raw = ctx["raw"]
 
             # tcp_info opt flags
             TCPI_OPT_TIMESTAMPS = 1
@@ -432,7 +435,7 @@ class TCP(Protocol):
 
             out = []
 
-            opts = tcp_info_raw['tcpi_options']
+            opts = tcp_info_raw["tcpi_options"]
             if opts & TCPI_OPT_TIMESTAMPS:
                 out.append("ts")
             if opts & TCPI_OPT_SACK:
@@ -446,82 +449,82 @@ class TCP(Protocol):
         ti = TCP.InfoCbCore
 
         info_refine_tabl = {
-            'tcpi_state': ('state', ti.state_c_cb),
-            'tcpi_pmtu': ('pmtu', None),
-            'tcpi_retrans': ('retrans', None),
-            'tcpi_ato': ('ato', ti.generic_1k_n_cb),
-            'tcpi_rto': ('rto', ti.rto_n_cb),
+            "tcpi_state": ("state", ti.state_c_cb),
+            "tcpi_pmtu": ("pmtu", None),
+            "tcpi_retrans": ("retrans", None),
+            "tcpi_ato": ("ato", ti.generic_1k_n_cb),
+            "tcpi_rto": ("rto", ti.rto_n_cb),
             # TODO consider wscale baking
-            'tcpi_snd_wscale': ('snd_wscale', None),
-            'tcpi_rcv_wscale': ('rcv_wscale', None),
+            "tcpi_snd_wscale": ("snd_wscale", None),
+            "tcpi_rcv_wscale": ("rcv_wscale", None),
             # TODO bps baking
-            'tcpi_snd_mss': ('snd_mss', None),
-            'tcpi_snd_cwnd': ('snd_cwnd', None),
-            'tcpi_snd_ssthresh': ('snd_ssthresh', ti.snd_thresh_p_cb),
+            "tcpi_snd_mss": ("snd_mss", None),
+            "tcpi_snd_cwnd": ("snd_cwnd", None),
+            "tcpi_snd_ssthresh": ("snd_ssthresh", ti.snd_thresh_p_cb),
             # TODO consider rtt agglomeration - needs nesting
-            'tcpi_rtt': ('rtt', ti.rtt_p_cb),
-            'tcpi_rttvar': ('rttvar', ti.generic_1k_n_cb),
-            'tcpi_rcv_rtt': ('rcv_rtt', ti.generic_1k_n_cb),
-            'tcpi_rcv_space': ('rcv_space', None),
-            'tcpi_options': ('opts', ti.opts_c_cb),
+            "tcpi_rtt": ("rtt", ti.rtt_p_cb),
+            "tcpi_rttvar": ("rttvar", ti.generic_1k_n_cb),
+            "tcpi_rcv_rtt": ("rcv_rtt", ti.generic_1k_n_cb),
+            "tcpi_rcv_space": ("rcv_space", None),
+            "tcpi_options": ("opts", ti.opts_c_cb),
             # unclear, NB not in use by iproute2 ss latest
-            'tcpi_last_data_sent': ('last_data_sent', None),
-            'tcpi_rcv_ssthresh': ('rcv_ssthresh', None),
-            'tcpi_rcv_ssthresh': ('rcv_ssthresh', None),
-            'tcpi_segs_in': ('segs_in', None),
-            'tcpi_segs_out': ('segs_out', None),
-            'tcpi_data_segs_in': ('data_segs_in', None),
-            'tcpi_data_segs_out': ('data_segs_out', None),
-            'tcpi_lost': ('lost', None),
-            'tcpi_notsent_bytes': ('notsent_bytes', None),
-            'tcpi_rcv_mss': ('rcv_mss', None),
-            'tcpi_pacing_rate': ('pacing_rate', None),
-            'tcpi_retransmits': ('retransmits', None),
-            'tcpi_min_rtt': ('min_rtt', None),
-            'tcpi_rwnd_limited': ('rwnd_limited', None),
-            'tcpi_max_pacing_rate': ('max_pacing_rate', None),
-            'tcpi_probes': ('probes', None),
-            'tcpi_reordering': ('reordering', None),
-            'tcpi_last_data_recv': ('last_data_recv', None),
-            'tcpi_bytes_received': ('bytes_received', None),
-            'tcpi_fackets': ('fackets', None),
-            'tcpi_last_ack_recv': ('last_ack_recv', None),
-            'tcpi_last_ack_sent': ('last_ack_sent', None),
-            'tcpi_unacked': ('unacked', None),
-            'tcpi_sacked': ('sacked', None),
-            'tcpi_bytes_acked': ('bytes_acked', None),
-            'tcpi_delivery_rate_app_limited': (
-                'delivery_rate_app_limited',
+            "tcpi_last_data_sent": ("last_data_sent", None),
+            "tcpi_rcv_ssthresh": ("rcv_ssthresh", None),
+            "tcpi_rcv_ssthresh": ("rcv_ssthresh", None),
+            "tcpi_segs_in": ("segs_in", None),
+            "tcpi_segs_out": ("segs_out", None),
+            "tcpi_data_segs_in": ("data_segs_in", None),
+            "tcpi_data_segs_out": ("data_segs_out", None),
+            "tcpi_lost": ("lost", None),
+            "tcpi_notsent_bytes": ("notsent_bytes", None),
+            "tcpi_rcv_mss": ("rcv_mss", None),
+            "tcpi_pacing_rate": ("pacing_rate", None),
+            "tcpi_retransmits": ("retransmits", None),
+            "tcpi_min_rtt": ("min_rtt", None),
+            "tcpi_rwnd_limited": ("rwnd_limited", None),
+            "tcpi_max_pacing_rate": ("max_pacing_rate", None),
+            "tcpi_probes": ("probes", None),
+            "tcpi_reordering": ("reordering", None),
+            "tcpi_last_data_recv": ("last_data_recv", None),
+            "tcpi_bytes_received": ("bytes_received", None),
+            "tcpi_fackets": ("fackets", None),
+            "tcpi_last_ack_recv": ("last_ack_recv", None),
+            "tcpi_last_ack_sent": ("last_ack_sent", None),
+            "tcpi_unacked": ("unacked", None),
+            "tcpi_sacked": ("sacked", None),
+            "tcpi_bytes_acked": ("bytes_acked", None),
+            "tcpi_delivery_rate_app_limited": (
+                "delivery_rate_app_limited",
                 None,
             ),
-            'tcpi_delivery_rate': ('delivery_rate', None),
-            'tcpi_sndbuf_limited': ('sndbuf_limited', None),
-            'tcpi_ca_state': ('ca_state', None),
-            'tcpi_busy_time': ('busy_time', None),
-            'tcpi_total_retrans': ('total_retrans', None),
-            'tcpi_advmss': ('advmss', None),
-            'tcpi_backoff': (None, None),
-            'tcpv_enabled': (None, 'skip'),
-            'tcpv_rttcnt': (None, 'skip'),
-            'tcpv_rtt': (None, 'skip'),
-            'tcpv_minrtt': (None, 'skip'),
+            "tcpi_delivery_rate": ("delivery_rate", None),
+            "tcpi_sndbuf_limited": ("sndbuf_limited", None),
+            "tcpi_ca_state": ("ca_state", None),
+            "tcpi_busy_time": ("busy_time", None),
+            "tcpi_total_retrans": ("total_retrans", None),
+            "tcpi_advmss": ("advmss", None),
+            "tcpi_backoff": (None, None),
+            "tcpv_enabled": (None, "skip"),
+            "tcpv_rttcnt": (None, "skip"),
+            "tcpv_rtt": (None, "skip"),
+            "tcpv_minrtt": (None, "skip"),
             # BBR
-            'bbr_bw_lo': ('bbr_bw_lo', None),
-            'bbr_bw_hi': ('bbr_bw_hi', None),
-            'bbr_min_rtt': ('bbr_min_rtt', None),
-            'bbr_pacing_gain': ('bbr_pacing_gain', None),
-            'bbr_cwnd_gain': ('bbr_cwnd_gain', None),
+            "bbr_bw_lo": ("bbr_bw_lo", None),
+            "bbr_bw_hi": ("bbr_bw_hi", None),
+            "bbr_min_rtt": ("bbr_min_rtt", None),
+            "bbr_pacing_gain": ("bbr_pacing_gain", None),
+            "bbr_cwnd_gain": ("bbr_cwnd_gain", None),
             # DCTCP
-            'dctcp_enabled': ('dctcp_enabled', None),
-            'dctcp_ce_state': ('dctcp_ce_state', None),
-            'dctcp_alpha': ('dctcp_alpha', None),
-            'dctcp_ab_ecn': ('dctcp_ab_ecn', None),
-            'dctcp_ab_tot': ('dctcp_ab_tot', None),
+            "dctcp_enabled": ("dctcp_enabled", None),
+            "dctcp_ce_state": ("dctcp_ce_state", None),
+            "dctcp_alpha": ("dctcp_alpha", None),
+            "dctcp_ab_ecn": ("dctcp_ab_ecn", None),
+            "dctcp_ab_tot": ("dctcp_ab_tot", None),
         }
         k_idx = 0
         cb_idx = 1
 
-        info_k = 'tcp_info'
+        info_k = "tcp_info"
         vessel[info_k] = {}
 
         # BUG - pyroute2 diag - seems always last info instance from kernel
@@ -532,10 +535,10 @@ class TCP(Protocol):
                 refined_k = info_refine_tabl[k][k_idx]
                 cb = info_refine_tabl[k][cb_idx]
                 refined_v = v
-                if cb and cb == 'skip':
+                if cb and cb == "skip":
                     continue
                 elif cb:
-                    ctx = {'raw': tcp_info_raw}
+                    ctx = {"raw": tcp_info_raw}
                     refined_v = cb(k, v, **ctx)
 
                 vessel[info_k][refined_k] = refined_v
@@ -551,34 +554,34 @@ def prepare_args():
                                      iproute2/misc/ss2"""
     )
     parser.add_argument(
-        '-x',
-        '--unix',
-        help='Display Unix domain sockets.',
-        action='store_true',
+        "-x",
+        "--unix",
+        help="Display Unix domain sockets.",
+        action="store_true",
     )
     parser.add_argument(
-        '-t', '--tcp', help='Display TCP sockets.', action='store_true'
+        "-t", "--tcp", help="Display TCP sockets.", action="store_true"
     )
     parser.add_argument(
-        '-l',
-        '--listen',
-        help='Display listening sockets.',
-        action='store_true',
+        "-l",
+        "--listen",
+        help="Display listening sockets.",
+        action="store_true",
     )
     parser.add_argument(
-        '-a', '--all', help='Display all sockets.', action='store_true'
+        "-a", "--all", help="Display all sockets.", action="store_true"
     )
     parser.add_argument(
-        '-p',
-        '--process',
-        help='show socket holding context',
-        action='store_true',
+        "-p",
+        "--process",
+        help="show socket holding context",
+        action="store_true",
     )
     parser.add_argument(
-        '-r',
-        '--resolve',
-        help='resolve host names in addition',
-        action='store_true',
+        "-r",
+        "--resolve",
+        help="resolve host names in addition",
+        action="store_true",
     )
 
     args = parser.parse_args()
@@ -588,7 +591,7 @@ def prepare_args():
 
 def run(args=None):
     if psutil is None:
-        raise RuntimeError('ss2 requires python-psutil >= 5.0 to run')
+        raise RuntimeError("ss2 requires python-psutil >= 5.0 to run")
 
     if not args:
         args = prepare_args()
@@ -607,16 +610,24 @@ def run(args=None):
         protocols.append(UNIX(sk_states=_states))
 
     if not protocols:
-        raise RuntimeError('not implemented - ss2 in fledging mode')
+        raise RuntimeError("not implemented - ss2 in fledging mode")
 
     _user_ctxt_map = None
     if args.process:
         _user_ctxt_map = UserCtxtMap()
 
+    result = list()
+
     with DiagSocket() as ds:
         ds.bind()
         for p in protocols:
-            p(ds, args, _user_ctxt_map)
+            sub_statistics = p(ds, args, _user_ctxt_map)
+            result.append(sub_statistics)
+
+    if RUN_AS_MODULE:
+        return result
+    else:
+        print(json.dumps(result, indent=4))
 
 
 if __name__ == "__main__":
diff -pruN 0.7.7-4/pyroute2/netlink/event/__init__.py 0.7.11-0ubuntu3/pyroute2/netlink/event/__init__.py
--- 0.7.7-4/pyroute2/netlink/event/__init__.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/event/__init__.py	2024-01-12 18:27:09.000000000 +0000
@@ -6,8 +6,8 @@ class EventSocket(GenericNetlinkSocket):
     marshal_class = None
     genl_family = None
 
-    def __init__(self):
-        GenericNetlinkSocket.__init__(self)
+    def __init__(self, *args, **kwargs):
+        GenericNetlinkSocket.__init__(self, *args, **kwargs)
         self.marshal = self.marshal_class()
         if kernel[0] <= 2:
             self.bind(groups=0xFFFFFF)
diff -pruN 0.7.7-4/pyroute2/netlink/generic/ethtool.py 0.7.11-0ubuntu3/pyroute2/netlink/generic/ethtool.py
--- 0.7.7-4/pyroute2/netlink/generic/ethtool.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/generic/ethtool.py	2024-01-12 18:27:09.000000000 +0000
@@ -1,4 +1,10 @@
-from pyroute2.netlink import NLA_F_NESTED, NLM_F_REQUEST, genlmsg, nla
+from pyroute2.netlink import (
+    NLA_F_NESTED,
+    NLM_F_ACK,
+    NLM_F_REQUEST,
+    genlmsg,
+    nla,
+)
 from pyroute2.netlink.exceptions import NetlinkError
 from pyroute2.netlink.generic import GenericNetlinkSocket
 
@@ -16,6 +22,12 @@ ETHTOOL_MSG_DEBUG_GET = 7
 ETHTOOL_MSG_DEBUG_SET = 8
 ETHTOOL_MSG_WOL_GET = 9
 ETHTOOL_MSG_WOL_SET = 10
+ETHTOOL_MSG_FEATURES_GET = 11
+ETHTOOL_MSG_FEATURES_SET = 12
+ETHTOOL_MSG_PRIVFLAGS_GET = 13
+ETHTOOL_MSG_PRIVFLAGS_SET = 14
+ETHTOOL_MSG_RINGS_GET = 15
+ETHTOOL_MSG_RINGS_SET = 16
 
 
 class ethtoolheader(nla):
@@ -149,6 +161,30 @@ class ethtool_wol_msg(genlmsg):
     ethtoolbitset = ethtoolbitset
 
 
+class ethtool_rings_msg(genlmsg):
+    nla_map = (
+        ('ETHTOOL_A_RINGS_UNSPEC', 'none'),
+        ('ETHTOOL_A_RINGS_HEADER', 'ethtoolheader'),
+        ('ETHTOOL_A_RINGS_RX_MAX', 'uint32'),
+        ('ETHTOOL_A_RINGS_RX_MINI_MAX', 'uint32'),
+        ('ETHTOOL_A_RINGS_RX_JUMBO_MAX', 'uint32'),
+        ('ETHTOOL_A_RINGS_TX_MAX', 'uint32'),
+        ('ETHTOOL_A_RINGS_RX', 'uint32'),
+        ('ETHTOOL_A_RINGS_RX_MINI', 'uint32'),
+        ('ETHTOOL_A_RINGS_RX_JUMBO', 'uint32'),
+        ('ETHTOOL_A_RINGS_TX', 'uint32'),
+        ('ETHTOOL_A_RINGS_RX_BUF_LEN', 'uint32'),
+        ('ETHTOOL_A_RINGS_TCP_DATA_SPLIT', 'uint8'),
+        ('ETHTOOL_A_RINGS_CQE_SIZE', 'uint32'),
+        ('ETHTOOL_A_RINGS_TX_PUSH', 'uint8'),
+        ('ETHTOOL_A_RINGS_RX_PUSH', 'uint8'),
+        ('ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN', 'uint32'),
+        ('ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX', 'uint32'),
+    )
+
+    ethtoolheader = ethtoolheader
+
+
 class NlEthtool(GenericNetlinkSocket):
     def _do_request(self, msg, msg_flags=NLM_F_REQUEST):
         return self.nlm_request(msg, msg_type=self.prid, msg_flags=msg_flags)
@@ -231,3 +267,24 @@ class NlEthtool(GenericNetlinkSocket):
 
         self.bind(ETHTOOL_GENL_NAME, ethtool_wol_msg)
         return self._do_request(msg)
+
+    def get_rings(self, ifname=None, ifindex=None):
+        msg = ethtool_rings_msg()
+        msg["cmd"] = ETHTOOL_MSG_RINGS_GET
+        msg["version"] = ETHTOOL_GENL_VERSION
+        msg["attrs"].append(
+            ('ETHTOOL_A_RINGS_HEADER', self._get_dev_header(ifname, ifindex))
+        )
+
+        self.bind(ETHTOOL_GENL_NAME, ethtool_rings_msg)
+        return self._do_request(msg)
+
+    def set_rings(self, rings, ifname=None, ifindex=None):
+        rings["cmd"] = ETHTOOL_MSG_RINGS_SET
+        rings["version"] = ETHTOOL_GENL_VERSION
+        rings["attrs"].append(
+            ('ETHTOOL_A_RINGS_HEADER', self._get_dev_header(ifname, ifindex))
+        )
+
+        self.bind(ETHTOOL_GENL_NAME, ethtool_rings_msg)
+        return self._do_request(rings, msg_flags=NLM_F_REQUEST | NLM_F_ACK)
diff -pruN 0.7.7-4/pyroute2/netlink/generic/l2tp.py 0.7.11-0ubuntu3/pyroute2/netlink/generic/l2tp.py
--- 0.7.7-4/pyroute2/netlink/generic/l2tp.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/generic/l2tp.py	2024-01-12 18:27:09.000000000 +0000
@@ -145,8 +145,8 @@ class l2tpmsg(genlmsg):
 
 
 class L2tp(GenericNetlinkSocket):
-    def __init__(self):
-        GenericNetlinkSocket.__init__(self)
+    def __init__(self, *args, **kwargs):
+        GenericNetlinkSocket.__init__(self, *args, **kwargs)
         self.bind(L2TP_GENL_NAME, l2tpmsg)
 
     def _do_request(self, msg, msg_flags=NLM_F_REQUEST | NLM_F_ACK):
@@ -537,35 +537,50 @@ class L2tp(GenericNetlinkSocket):
 
         return self._do_request(msg)
 
-    def get_tunnel(self, tunnel_id=None):
+    def get_tunnel(self, tunnel_id):
         """
-        Get one or more tunnels
-        :param tunnel_id: tunnel id of the tunnel to show, if not set all
-                          tunnels will be returned
+        Get one tunnel
+        :param tunnel_id: tunnel id of the tunnel to show
+        :return: netlink response
+        """
+        msg = l2tpmsg()
+        msg["cmd"] = L2TP_CMD_TUNNEL_GET
+        msg["version"] = L2TP_GENL_VERSION
+        msg["attrs"].append(["L2TP_ATTR_CONN_ID", tunnel_id])
+        return self._do_request(msg, msg_flags=NLM_F_REQUEST)[0]
+
+    def dump_tunnels(self, tunnel_id):
+        """
+        Dump all tunnels
         :return: netlink response
         """
         msg = l2tpmsg()
         msg["cmd"] = L2TP_CMD_TUNNEL_GET
         msg["version"] = L2TP_GENL_VERSION
-        if tunnel_id:
-            msg["attrs"].append(["L2TP_ATTR_CONN_ID", tunnel_id])
         return self._do_request(msg, msg_flags=NLM_F_REQUEST | NLM_F_DUMP)
 
-    def get_session(self, tunnel_id=None, session_id=None):
+    def get_session(self, tunnel_id, session_id):
+        """
+        Get one session
+        :param tunnel_id: tunnel id of the session
+        :param session_id: session id of the session
+        :return: netlink response
+        """
+        msg = l2tpmsg()
+        msg["cmd"] = L2TP_CMD_SESSION_GET
+        msg["version"] = L2TP_GENL_VERSION
+        msg["attrs"].append(["L2TP_ATTR_CONN_ID", tunnel_id])
+        msg["attrs"].append(["L2TP_ATTR_SESSION_ID", session_id])
+
+        return self._do_request(msg, msg_flags=NLM_F_REQUEST)[0]
+
+    def dump_sessions(self):
         """
-        Get one or more sessions
-        :param tunnel_id: tunnel id of the tunnel for which to show the
-                          session(s)
-        :param session_id: session id of the session to show, if not set all
-                           sessions will be returned
+        Dump all sessions
         :return: netlink response
         """
         msg = l2tpmsg()
         msg["cmd"] = L2TP_CMD_SESSION_GET
         msg["version"] = L2TP_GENL_VERSION
-        if tunnel_id:
-            msg["attrs"].append(["L2TP_ATTR_CONN_ID", tunnel_id])
-        if session_id:
-            msg["attrs"].append(["L2TP_ATTR_SESSION_ID", session_id])
 
         return self._do_request(msg, msg_flags=NLM_F_REQUEST | NLM_F_DUMP)
diff -pruN 0.7.7-4/pyroute2/netlink/generic/wireguard.py 0.7.11-0ubuntu3/pyroute2/netlink/generic/wireguard.py
--- 0.7.7-4/pyroute2/netlink/generic/wireguard.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/generic/wireguard.py	2024-01-12 18:27:09.000000000 +0000
@@ -263,8 +263,8 @@ class wgmsg(genlmsg):
 
 
 class WireGuard(GenericNetlinkSocket):
-    def __init__(self):
-        GenericNetlinkSocket.__init__(self)
+    def __init__(self, *args, **kwargs):
+        GenericNetlinkSocket.__init__(self, *args, **kwargs)
         self.bind(WG_GENL_NAME, wgmsg)
 
     def info(self, interface):
diff -pruN 0.7.7-4/pyroute2/netlink/nl80211/__init__.py 0.7.11-0ubuntu3/pyroute2/netlink/nl80211/__init__.py
--- 0.7.7-4/pyroute2/netlink/nl80211/__init__.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/nl80211/__init__.py	2024-01-12 18:27:09.000000000 +0000
@@ -1533,8 +1533,8 @@ class MarshalNl80211(Marshal):
 
 
 class NL80211(GenericNetlinkSocket):
-    def __init__(self):
-        GenericNetlinkSocket.__init__(self)
+    def __init__(self, *args, **kwargs):
+        GenericNetlinkSocket.__init__(self, *args, **kwargs)
         self.marshal = MarshalNl80211()
 
     def bind(self, groups=0, **kwarg):
diff -pruN 0.7.7-4/pyroute2/netlink/rtnl/ifinfmsg/__init__.py 0.7.11-0ubuntu3/pyroute2/netlink/rtnl/ifinfmsg/__init__.py
--- 0.7.7-4/pyroute2/netlink/rtnl/ifinfmsg/__init__.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/rtnl/ifinfmsg/__init__.py	2024-01-12 18:27:09.000000000 +0000
@@ -12,6 +12,7 @@ from pyroute2.config import AF_BRIDGE
 from pyroute2.netlink import NLA_F_NESTED, nla, nlmsg, nlmsg_atoms
 from pyroute2.netlink.rtnl.ifinfmsg.plugins import (
     bond,
+    can,
     geneve,
     gtp,
     ipoib,
@@ -257,6 +258,7 @@ data_plugins = {}
 
 for module in (
     bond,
+    can,
     geneve,
     gtp,
     ipvlan,
diff -pruN 0.7.7-4/pyroute2/netlink/rtnl/ifinfmsg/plugins/can.py 0.7.11-0ubuntu3/pyroute2/netlink/rtnl/ifinfmsg/plugins/can.py
--- 0.7.7-4/pyroute2/netlink/rtnl/ifinfmsg/plugins/can.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/rtnl/ifinfmsg/plugins/can.py	2024-01-12 18:27:09.000000000 +0000
@@ -0,0 +1,154 @@
+from pyroute2.netlink import nla, nlmsg_atoms
+
+CAN_CTRLMODE_NAMES = {
+    'CAN_CTRLMODE_LOOPBACK': 0x01,
+    'CAN_CTRLMODE_LISTENONLY': 0x02,
+    'CAN_CTRLMODE_3_SAMPLES': 0x04,
+    'CAN_CTRLMODE_ONE_SHOT': 0x08,
+    'CAN_CTRLMODE_BERR_REPORTING': 0x10,
+    'CAN_CTRLMODE_FD': 0x20,
+    'CAN_CTRLMODE_PRESUME_ACK': 0x40,
+    'CAN_CTRLMODE_FD_NON_ISO': 0x80,
+    'CAN_CTRLMODE_CC_LEN8_DLC': 0x100,
+    'CAN_CTRLMODE_TDC_AUTO': 0x200,
+    'CAN_CTRLMODE_TDC_MANUAL': 0x400,
+}
+
+CAN_CTRLMODE_VALUES = {
+    0x001: 'CAN_CTRLMODE_LOOPBACK',
+    0x002: 'CAN_CTRLMODE_LISTENONLY',
+    0x004: 'CAN_CTRLMODE_3_SAMPLES',
+    0x008: 'CAN_CTRLMODE_ONE_SHOT',
+    0x010: 'CAN_CTRLMODE_BERR_REPORTING',
+    0x020: 'CAN_CTRLMODE_FD',
+    0x040: 'CAN_CTRLMODE_PRESUME_ACK',
+    0x080: 'CAN_CTRLMODE_FD_NON_ISO',
+    0x100: 'CAN_CTRLMODE_CC_LEN8_DLC',
+    0x200: 'CAN_CTRLMODE_TDC_AUTO',
+    0x400: 'CAN_CTRLMODE_TDC_MANUAL',
+}
+
+
+class can(nla):
+    prefix = 'IFLA_'
+    nla_map = (
+        ('IFLA_CAN_UNSPEC', 'none'),
+        ('IFLA_CAN_BITTIMING', 'can_bittiming'),
+        ('IFLA_CAN_BITTIMING_CONST', 'can_bittiming_const'),
+        # NOTE:
+        # This is actually a struct of one member, but that doesn't parse:
+        ('IFLA_CAN_CLOCK', 'uint32'),
+        ('IFLA_CAN_STATE', 'can_state'),
+        ('IFLA_CAN_CTRLMODE', 'can_ctrlmode'),
+        ('IFLA_CAN_RESTART_MS', 'uint32'),
+        ('IFLA_CAN_RESTART', 'flag'),
+        ('IFLA_CAN_BERR_COUNTER', 'can_berr_counter'),
+        ('IFLA_CAN_DATA_BITTIMING', 'can_bittiming'),
+        ('IFLA_CAN_DATA_BITTIMING_CONST', 'can_bittiming_const'),
+        ('IFLA_CAN_TERMINATION', 'uint16'),
+        ('IFLA_CAN_TERMINATION_CONST', 'array(uint16)'),
+        ('IFLA_CAN_BITRATE_CONST', 'array(uint32)'),
+        ('IFLA_CAN_DATA_BITRATE_CONST', 'array(uint32)'),
+        ('IFLA_CAN_BITRATE_MAX', 'uint32'),
+        ('IFLA_CAN_TDC', 'can_tdc'),
+        ('IFLA_CAN_CTRLMODE_EXT', 'can_ctrlmode_ext'),
+    )
+
+    class can_bittiming(nla):
+        fields = (
+            ('bitrate', 'I'),
+            ('sample_point', 'I'),
+            ('tq', 'I'),
+            ('prop_seg', 'I'),
+            ('phase_seg1', 'I'),
+            ('phase_seg2', 'I'),
+            ('sjw', 'I'),
+            ('brp', 'I'),
+        )
+
+    class can_bittiming_const(nla):
+        fields = (
+            ('name', '=16s'),
+            ('tseg1_min', 'I'),
+            ('tseg1_max', 'I'),
+            ('tseg2_min', 'I'),
+            ('tseg2_max', 'I'),
+            ('sjw_max', 'I'),
+            ('brp_min', 'I'),
+            ('brp_max', 'I'),
+            ('brp_inc', 'I'),
+        )
+
+    class can_state(nlmsg_atoms.uint32):
+        value_map = {
+            0: 'ERROR_ACTIVE',
+            1: 'ERROR_WARNING',
+            2: 'ERROR_PASSIVE',
+            3: 'BUS_OFF',
+            4: 'STOPPED',
+            5: 'SLEEPING',
+            6: 'MAX',
+        }
+
+    class can_ctrlmode(nla):
+        fields = (('mask', 'I'), ('flags', 'I'))
+
+        def decode(self):
+            super(nla, self).decode()
+            flags = self["flags"]
+            for value, mode in CAN_CTRLMODE_VALUES.items():
+                self[mode[len('CAN_CTRLMODE_') :].lower()] = (
+                    "on" if flags & value else "off"
+                )
+            del self["flags"]
+            del self["mask"]
+
+        def encode(self):
+            mask = 0
+            flags = 0
+            for mode, value in CAN_CTRLMODE_NAMES.items():
+                m = mode[len('CAN_CTRLMODE_') :].lower()
+                try:
+                    v = self[m]
+                except KeyError:
+                    continue
+                mask |= value
+                if v == "on":
+                    flags |= value
+            self['mask'] = mask
+            self['flags'] = flags
+            return super(nla, self).encode()
+
+    class can_berr_counter(nla):
+        fields = (('txerr', 'H'), ('rxerr', 'H'))
+
+    class can_tdc(nla):
+        prefix = "IFLA_"
+        nla_map = (
+            ('IFLA_CAN_TDC_UNSPEC', 'none'),
+            ('IFLA_CAN_TDC_TDCV_MIN', 'uint32'),
+            ('IFLA_CAN_TDC_TDCV_MAX', 'uint32'),
+            ('IFLA_CAN_TDC_TDCO_MIN', 'uint32'),
+            ('IFLA_CAN_TDC_TDCO_MAX', 'uint32'),
+            ('IFLA_CAN_TDC_TDCF_MIN', 'uint32'),
+            ('IFLA_CAN_TDC_TDCF_MAX', 'uint32'),
+            ('IFLA_CAN_TDC_TDCV', 'uint32'),
+            ('IFLA_CAN_TDC_TDCO', 'uint32'),
+            ('IFLA_CAN_TDC_TDCF', 'uint32'),
+        )
+
+    class can_ctrlmode_ext(nla):
+        prefix = "IFLA_"
+        nla_map = (
+            ('IFLA_CAN_CTRLMODE_UNSPEC', 'none'),
+            ('IFLA_CAN_CTRLMODE_SUPPORTED', 'can_ctrlmode_supported'),
+        )
+
+        class can_ctrlmode_supported(nlmsg_atoms.uint32):
+            def decode(self):
+                super(nlmsg_atoms.uint32, self).decode()
+                for value, mode in CAN_CTRLMODE_VALUES.items():
+                    self[mode[len('CAN_CTRLMODE_') :].lower()] = (
+                        'yes' if value & self["value"] else 'no'
+                    )
+                del self["value"]
diff -pruN 0.7.7-4/pyroute2/netlink/rtnl/tcmsg/sched_netem.py 0.7.11-0ubuntu3/pyroute2/netlink/rtnl/tcmsg/sched_netem.py
--- 0.7.7-4/pyroute2/netlink/rtnl/tcmsg/sched_netem.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/rtnl/tcmsg/sched_netem.py	2024-01-12 18:27:09.000000000 +0000
@@ -10,7 +10,7 @@ def get_parameters(kwarg):
     limit = kwarg.get('limit', 1000)  # fifo limit (packets) see netem.c:230
     loss = percent2u32(kwarg.get('loss', 0))  # int percentage
     gap = kwarg.get('gap', 0)
-    duplicate = kwarg.get('duplicate', 0)
+    duplicate = percent2u32(kwarg.get('duplicate', 0))  # int percentage
     jitter = time2tick(kwarg.get('jitter', 0))  # in microsecond
 
     opts = {
diff -pruN 0.7.7-4/pyroute2/netlink/taskstats/__init__.py 0.7.11-0ubuntu3/pyroute2/netlink/taskstats/__init__.py
--- 0.7.7-4/pyroute2/netlink/taskstats/__init__.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netlink/taskstats/__init__.py	2024-01-12 18:27:09.000000000 +0000
@@ -119,9 +119,6 @@ class taskstatsmsg(genlmsg):
 
 
 class TaskStats(GenericNetlinkSocket):
-    def __init__(self):
-        GenericNetlinkSocket.__init__(self)
-
     def bind(self):
         GenericNetlinkSocket.bind(self, 'TASKSTATS', taskstatsmsg)
 
diff -pruN 0.7.7-4/pyroute2/netns/__init__.py 0.7.11-0ubuntu3/pyroute2/netns/__init__.py
--- 0.7.7-4/pyroute2/netns/__init__.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/netns/__init__.py	2024-01-12 18:27:09.000000000 +0000
@@ -108,6 +108,8 @@ __NR = {
     'aarc': {'32bit': 375, '64bit': 268},  # FIXME: EABI vs. OABI?
     'ppc6': {'64bit': 350},
     's390': {'64bit': 339},
+    'loongarch64': {'64bit': 268},
+    'risc': {'64bit': 268},
 }
 __NR_setns = __NR.get(config.machine[:4], {}).get(config.arch, 308)
 
diff -pruN 0.7.7-4/pyroute2/requests/neighbour.py 0.7.11-0ubuntu3/pyroute2/requests/neighbour.py
--- 0.7.7-4/pyroute2/requests/neighbour.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/pyroute2/requests/neighbour.py	2024-01-12 18:27:09.000000000 +0000
@@ -28,6 +28,12 @@ class NeighbourFieldFilter(Index):
     def set_state(self, context, value):
         return self._state(value)
 
+    def set_dst(self, context, value):
+        if value:
+            return {'dst': value}
+        else:
+            return {}
+
 
 class NeighbourIPRouteFilter(IPRouteFilter):
     def set_dst(self, context, value):
diff -pruN 0.7.7-4/setup.cfg 0.7.11-0ubuntu3/setup.cfg
--- 0.7.7-4/setup.cfg	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/setup.cfg	2024-01-12 18:27:09.000000000 +0000
@@ -26,6 +26,7 @@ classifiers =
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
+    Programming Language :: Python :: 3.12
     Development Status :: 4 - Beta
 
 [options]
diff -pruN 0.7.7-4/setup.minimal.cfg 0.7.11-0ubuntu3/setup.minimal.cfg
--- 0.7.7-4/setup.minimal.cfg	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/setup.minimal.cfg	2024-01-12 18:27:09.000000000 +0000
@@ -26,6 +26,7 @@ classifiers =
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
+    Programming Language :: Python :: 3.12
     Development Status :: 4 - Beta
 
 [options]
diff -pruN 0.7.7-4/tests/test_linux/test_generic/test_l2tp.py 0.7.11-0ubuntu3/tests/test_linux/test_generic/test_l2tp.py
--- 0.7.7-4/tests/test_linux/test_generic/test_l2tp.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/tests/test_linux/test_generic/test_l2tp.py	2024-01-12 18:27:09.000000000 +0000
@@ -39,6 +39,7 @@ def l2ctx(context):
     context.l2tp.close()
 
 
+@pytest.mark.xfail(reason='flaky test, only to collect failure logs')
 def test_complete(l2ctx):
     # 1. create tunnel
     l2ctx.l2tp.create_tunnel(
@@ -51,14 +52,14 @@ def test_complete(l2ctx):
         encap="udp",
     )
     tunnel = l2ctx.l2tp.get_tunnel(tunnel_id=2324)
-    assert tunnel[0].get_attr("L2TP_ATTR_CONN_ID") == 2324
-    assert tunnel[0].get_attr("L2TP_ATTR_PEER_CONN_ID") == 2425
-    assert tunnel[0].get_attr("L2TP_ATTR_IP_DADDR") == l2ctx.remote_ip
-    assert tunnel[0].get_attr("L2TP_ATTR_IP_SADDR") == l2ctx.local_ip
-    assert tunnel[0].get_attr("L2TP_ATTR_UDP_DPORT") == 32000
-    assert tunnel[0].get_attr("L2TP_ATTR_UDP_SPORT") == 32000
-    assert tunnel[0].get_attr("L2TP_ATTR_ENCAP_TYPE") == 0  # 0 == UDP
-    assert tunnel[0].get_attr("L2TP_ATTR_DEBUG") == 0
+    assert tunnel.get_attr("L2TP_ATTR_CONN_ID") == 2324
+    assert tunnel.get_attr("L2TP_ATTR_PEER_CONN_ID") == 2425
+    assert tunnel.get_attr("L2TP_ATTR_IP_DADDR") == l2ctx.remote_ip
+    assert tunnel.get_attr("L2TP_ATTR_IP_SADDR") == l2ctx.local_ip
+    assert tunnel.get_attr("L2TP_ATTR_UDP_DPORT") == 32000
+    assert tunnel.get_attr("L2TP_ATTR_UDP_SPORT") == 32000
+    assert tunnel.get_attr("L2TP_ATTR_ENCAP_TYPE") == 0  # 0 == UDP
+    assert tunnel.get_attr("L2TP_ATTR_DEBUG") == 0
 
     # 2. create session
     l2ctx.l2tp.create_session(
@@ -68,9 +69,9 @@ def test_complete(l2ctx):
         ifname=l2ctx.l2tpeth0,
     )
     session = l2ctx.l2tp.get_session(tunnel_id=2324, session_id=3435)
-    assert session[0].get_attr("L2TP_ATTR_SESSION_ID") == 3435
-    assert session[0].get_attr("L2TP_ATTR_PEER_SESSION_ID") == 3536
-    assert session[0].get_attr("L2TP_ATTR_DEBUG") == 0
+    assert session.get_attr("L2TP_ATTR_SESSION_ID") == 3435
+    assert session.get_attr("L2TP_ATTR_PEER_SESSION_ID") == 3536
+    assert session.get_attr("L2TP_ATTR_DEBUG") == 0
 
     # setting up DEBUG -> 95, operation not supported; review the test
     # # 3. modify session
@@ -88,8 +89,8 @@ def test_complete(l2ctx):
     l2ctx.l2tp.delete_session(tunnel_id=2324, session_id=3435)
     for _ in range(5):
         try:
-            assert not l2ctx.l2tp.get_session(tunnel_id=2324, session_id=3435)
-        except AssertionError:
+            l2ctx.l2tp.get_session(tunnel_id=2324, session_id=3435)
+        except NetlinkError:
             time.sleep(0.1)
             continue
         break
@@ -100,8 +101,8 @@ def test_complete(l2ctx):
     l2ctx.l2tp.delete_tunnel(tunnel_id=2324)
     for _ in range(5):
         try:
-            assert not l2ctx.l2tp.get_tunnel(tunnel_id=2324)
-        except AssertionError:
+            l2ctx.l2tp.get_tunnel(tunnel_id=2324)
+        except NetlinkError:
             time.sleep(0.1)
             continue
         break
diff -pruN 0.7.7-4/tests/test_linux/test_ndb/test_chaotic.py 0.7.11-0ubuntu3/tests/test_linux/test_ndb/test_chaotic.py
--- 0.7.7-4/tests/test_linux/test_ndb/test_chaotic.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/tests/test_linux/test_ndb/test_chaotic.py	2024-01-12 18:27:09.000000000 +0000
@@ -8,7 +8,7 @@ pytestmark = [require_root()]
 
 
 @pytest.mark.xfail(reason='flaky test, only to collect failure logs')
-def test_add_del_ip_dict(context):
+def __test_add_del_ip_dict(context):
     ifname = context.new_ifname
     ifaddr1 = context.new_ipaddr
     ifaddr2 = context.new_ipaddr
diff -pruN 0.7.7-4/tests/test_linux/test_ndb/test_sources.py 0.7.11-0ubuntu3/tests/test_linux/test_ndb/test_sources.py
--- 0.7.7-4/tests/test_linux/test_ndb/test_sources.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/tests/test_linux/test_ndb/test_sources.py	2024-01-12 18:27:09.000000000 +0000
@@ -38,6 +38,7 @@ def test_multiple_sources(context):
         assert len(list(ndb.neighbours.dump()))
         assert len(list(ndb.addresses.dump()))
         assert len(list(ndb.routes.dump()))
+        assert len(ndb.sources) == len(sources)
     # here NDB() gets closed
     #
 
diff -pruN 0.7.7-4/util/update_version.py 0.7.11-0ubuntu3/util/update_version.py
--- 0.7.7-4/util/update_version.py	2023-04-14 19:07:33.000000000 +0000
+++ 0.7.11-0ubuntu3/util/update_version.py	2024-01-12 18:27:09.000000000 +0000
@@ -1,5 +1,6 @@
 #!/usr/bin/env python
 import subprocess
+from pathlib import Path
 
 version_module = "pyroute2/config/version.py"
 version_output_file = "VERSION"
@@ -13,11 +14,27 @@ def get_project_version():
     1. fetch version from git
     2. if not available, fallback to the version file in the repo
     """
+    version = None
+
     try:
-        version = subprocess.check_output(
-            ("git", "describe"), stderr=subprocess.DEVNULL
-        ).decode("utf-8")
+        git_top_level = Path(
+            subprocess.check_output(
+                ("git", "rev-parse", "--show-toplevel"),
+                stderr=subprocess.DEVNULL,
+            )
+            .decode("utf-8")
+            .strip()
+        )
+        pyroute2_top_level = Path(__file__).parent.parent.absolute()
+        # Only retrieve the git description from the pyroute2 directory
+        if git_top_level == pyroute2_top_level:
+            version = subprocess.check_output(
+                ("git", "describe"), stderr=subprocess.DEVNULL
+            ).decode("utf-8")
     except (FileNotFoundError, subprocess.CalledProcessError):
+        pass
+
+    if version is None:
         with open(version_input_file, "r") as f:
             version = f.read()
 
