summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/netlink/ovpn.py85
1 files changed, 84 insertions, 1 deletions
diff --git a/python/vyos/netlink/ovpn.py b/python/vyos/netlink/ovpn.py
index fe325bd60..232c57810 100644
--- a/python/vyos/netlink/ovpn.py
+++ b/python/vyos/netlink/ovpn.py
@@ -14,7 +14,7 @@
# along with this library. If not, see <http://www.gnu.org/licenses/>.
"""
-Creation of "ovpn" interfaces for OpenVPN data channel offload.
+Creation and inspection of "ovpn" interfaces for OpenVPN data channel offload.
The Kernel module takes the operating mode as a link attribute at creation
time and it can not be changed afterwards. iproute2 has no support for the
@@ -23,14 +23,24 @@ mode - which a server then adopts and rejects every client on. Go through
netlink directly instead.
"""
+from socket import if_nametoindex
+
from pyroute2 import IPRoute
+from pyroute2.netlink import NLM_F_DUMP
+from pyroute2.netlink import NLM_F_REQUEST
+from pyroute2.netlink import genlmsg
from pyroute2.netlink import nla
+from pyroute2.netlink.generic import GenericNetlinkSocket
from pyroute2.netlink.rtnl.ifinfmsg import ifinfmsg
# uapi: enum ovpn_mode
OVPN_MODE_P2P = 0
OVPN_MODE_MP = 1
+# uapi: OVPN_FAMILY_NAME and enum ovpn_nl_commands
+OVPN_FAMILY_NAME = 'ovpn'
+OVPN_CMD_PEER_GET = 3
+
class ovpn_data(nla):
prefix = 'IFLA_'
@@ -44,6 +54,79 @@ class ovpn_data(nla):
ifinfmsg.ifinfo.data_map.setdefault('ovpn', ovpn_data)
+class ovpnmsg(genlmsg):
+ """OVPN_A_* of the "ovpn" generic netlink family. Only the attributes read
+ below are decoded; the rest stay opaque so a Kernel that grows new ones
+ does not break the parser."""
+
+ prefix = 'OVPN_A_'
+ nla_map = (
+ ('OVPN_A_UNSPEC', 'none'),
+ ('OVPN_A_IFINDEX', 'uint32'),
+ ('OVPN_A_PEER', 'peer'),
+ ('OVPN_A_KEYCONF', 'hex'),
+ )
+
+ class peer(nla):
+ prefix = 'OVPN_A_PEER_'
+ nla_map = (
+ ('OVPN_A_PEER_UNSPEC', 'none'),
+ ('OVPN_A_PEER_ID', 'uint32'),
+ ('OVPN_A_PEER_REMOTE_IPV4', 'hex'),
+ ('OVPN_A_PEER_REMOTE_IPV6', 'hex'),
+ ('OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID', 'hex'),
+ ('OVPN_A_PEER_REMOTE_PORT', 'hex'),
+ ('OVPN_A_PEER_SOCKET', 'hex'),
+ ('OVPN_A_PEER_SOCKET_NETNSID', 'hex'),
+ ('OVPN_A_PEER_VPN_IPV4', 'hex'),
+ ('OVPN_A_PEER_VPN_IPV6', 'hex'),
+ ('OVPN_A_PEER_LOCAL_IPV4', 'hex'),
+ ('OVPN_A_PEER_LOCAL_IPV6', 'hex'),
+ ('OVPN_A_PEER_LOCAL_PORT', 'hex'),
+ ('OVPN_A_PEER_KEEPALIVE_INTERVAL', 'uint32'),
+ ('OVPN_A_PEER_KEEPALIVE_TIMEOUT', 'uint32'),
+ ('OVPN_A_PEER_DEL_REASON', 'hex'),
+ ('OVPN_A_PEER_VPN_RX_BYTES', 'uint64'),
+ ('OVPN_A_PEER_VPN_TX_BYTES', 'uint64'),
+ ('OVPN_A_PEER_VPN_RX_PACKETS', 'uint64'),
+ ('OVPN_A_PEER_VPN_TX_PACKETS', 'uint64'),
+ ('OVPN_A_PEER_LINK_RX_BYTES', 'uint64'),
+ ('OVPN_A_PEER_LINK_TX_BYTES', 'uint64'),
+ ('OVPN_A_PEER_LINK_RX_PACKETS', 'uint64'),
+ ('OVPN_A_PEER_LINK_TX_PACKETS', 'uint64'),
+ )
+
+
+def get_ovpn_peers(ifname: str) -> list:
+ """The peers the Kernel holds for an "ovpn" interface, as dictionaries of
+ the decoded OVPN_A_PEER_* attributes.
+
+ A peer only exists here once OpenVPN's OVPN_CMD_PEER_NEW succeeded, so
+ this is the one way to tell an offload that carries traffic from a device
+ that merely looks right. The byte counters are the Kernel's own, unlike
+ the ones OpenVPN reports, which add its userspace socket accounting.
+
+ OVPN_CMD_PEER_GET carries GENL_ADMIN_PERM, so this needs CAP_NET_ADMIN.
+ """
+ peers = []
+ with GenericNetlinkSocket() as sock:
+ sock.bind(OVPN_FAMILY_NAME, ovpnmsg)
+
+ msg = ovpnmsg()
+ msg['cmd'] = OVPN_CMD_PEER_GET
+ msg['version'] = 1
+ msg['attrs'] = [('OVPN_A_IFINDEX', if_nametoindex(ifname))]
+
+ for reply in sock.nlm_request(
+ msg, msg_type=sock.prid, msg_flags=NLM_F_REQUEST | NLM_F_DUMP
+ ):
+ peer = reply.get_attr('OVPN_A_PEER')
+ if peer is not None:
+ peers.append(dict(peer['attrs']))
+
+ return peers
+
+
def get_ovpn_mode(ifname: str):
"""Operating mode of an "ovpn" interface, or None when the interface is
missing or of any other type. iproute2 has no decoder for the attribute,