diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/netlink/ovpn.py | 85 |
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, |
