-
Notifications
You must be signed in to change notification settings - Fork 0
/
messenger.py
executable file
·208 lines (170 loc) · 7.77 KB
/
messenger.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#!/usr/bin/python3
"""
* (C) Copyright 2018 Hewlett Packard Enterprise Development LP”.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
# Get on the Generic bus. Uses the llamas test module.
# https://docs.pyroute2.org/ for info, but the source is best, especially
# /usr/lib/python3/dist-packages/pyroute2/netlink/generic/__init__.py
import errno
import os
import socket
import sys
import time
import uuid
import logging
import traceback
from pdb import set_trace
from pprint import pprint
from types import SimpleNamespace
from pyroute2.common import map_namespace
from pyroute2.netlink import NETLINK_GENERIC, NetlinkError
from pyroute2.netlink import NLM_F_REQUEST, NLM_F_DUMP, NLM_F_ACK, NLA_F_NESTED
from pyroute2.netlink import nla, nla_base, genlmsg, nlmsg
from pyroute2.netlink.generic import GenericNetlinkSocket
from pyroute2.netlink.nlsocket import Marshal
from pyroute2.netlink import nla
from base64 import b64encode, b64decode
from socket import inet_ntoa, inet_aton, inet_pton, AF_INET, AF_INET6
from struct import pack, unpack
from .configurator import Configurator
# Used in kernel module: genl_register_family(struct genl_family.name).
# After insmod, run "genl -d ctrl list" and eventually see
# Name: genz_cmd
# ID: 0x18 Version: 0x1 header size: 0 max attribs: 3
# commands supported:
# #1: ID-0x0
# #2: ID-0x1
# #3: ID-0x2
#
# ID == 0x18 (== 24) is dynamic and only valid in this scenario, and I've
# seen the 24 in embedded structures. Version in kernel == 1, no header,
# max 3 attribs == fields in 'MsgProps': gcid, cclass, uuid (user_send.c).
# GENZ_GENL_FAMILY_NAME = 'genz_cmd'
# GENZ_GENL_VERSION = 1
# Commands are matched from kern_recv.c::struct genl_ops genz_gnl_ops.
# Kernel convention is not to use zero as an index or base value.
GENZ_C_PREFIX = 'GENZ_C_'
GENZ_C_ADD_COMPONENT = 1 # from genz_genl.h "enum"
GENZ_C_REMOVE_COMPONENT = 2
GENZ_C_SYMLINK_COMPONENT = 3
# Coalesce the commands into a forward and reverse map.
# From https://www.open-mesh.org/attachments/857/neighbor_extend_dump.py
(GENZ_C_name2num, GENZ_C_num2name) = map_namespace(GENZ_C_PREFIX, globals())
# These mixins are given to pyroute2 to build packed structs for sending to
# C routines (libnl). They're just linked lookup tables whose attributes are
# proscribed by internals. The choices for encoding (uint32, asciiz, etc)
# are each a class in netlink/__init.__.py "class nlmsg_atoms". If you
# don't like those choices, add a class here then add it as a new class
# attribute to nlmsg. Only override __init__ for a set_trace, nothing else.
# See also netlink/__init__.py::register_nlas() for optional fields.
class DefaultMessageModel(genlmsg):
"""
The set of all Netlink Attributes (NLAs) that could be passed.
This is the analog of the kernel "struct nla_policy".
"""
# Zero-based arrays are sort-of needed here, but also somewhat frowned
# upon. This needs further research, maybe in pyroute2 itself.
nla_map = None
class Commandment(Marshal):
"""
The set of all command numbers and their associated message structures.
This is the analog of the kernel 'struct genl_ops'.
"""
msg_map = {}
class Messenger(GenericNetlinkSocket):
"""
Constructs a netlink message in the right format from the config file
(default one should be in the root of the project) and sends the message to
the destination.
"""
def __init__(self, *args, **kwargs):
"""
@param 'config' <str>: Path to a config file. Default: ./config
@param 'msg_flags' <int>: Default: NLM_F_REQUEST|NLM_F_ACK. A pyrouter2
flag used to send a message.
@param 'dont_bind' <bool>: False or None - self.bind is called.
True - user has to call self.bind manually.
"""
super().__init__() #anything you pass here, will be used against you!
cfg_path = kwargs.get('config', None)
if cfg_path is None:
cfg_path = os.path.abspath(os.path.realpath(__file__))
cfg_path = os.path.dirname(cfg_path)
cfg_path = os.path.join(cfg_path, 'config')
logging.warning('ZooKeeper received no config. Using default at: %s' % cfg_path)
# raise RuntimeError('Missing "config" argument!')
assert(os.path.exists(cfg_path)), 'Config at %s not found!' % cfg_path
self.msg_flags = kwargs.get('msg_flags', NLM_F_REQUEST|NLM_F_ACK)
self.cmd = Commandment() # Now pyroute2 methods see it.
self.cfg = Configurator(cfg_path)
@property
def runtime_id(self):
""" ID for this instance that will be used when binding to the kernel. """
return os.getpid()
def _clean_kwargs(self, keys: list, kwargs: dict) -> dict:
"""
GenericNetlinkSocket __init__ doesn't like any
"""
for k in keys:
if k in kwargs:
del kwargs[k]
return kwargs
def _assign_msg_to_cmd(self, cfg_model: dict):
"""
Set the Message Model per each Command type when it is None.
"""
model = cfg_model
for key in model.keys():
if model[key] is None:
model[key] = self.msg_model
return model
def bind(self, **kwargs):
"""Exposed here instead of just automatically doing it."""
try:
super().bind(
self.cfg.family_name,
self.msg_model,
groups=0,
pid=self.runtime_id,
**kwargs)
return # self.prid is now set, BTW
except NetlinkError as exc:
self.close()
if exc.code == errno.ENOENT:
raise RuntimeError(
'No kernel response for "%s"' % self.cfg.family_name)
raise(exc)
except Exception as exc:
self.close()
raise RuntimeError('bind() failed: %s' % str(exc))
def build_msg(self, cmd, **kwargs):
# pyroute2 expacts a certain class and format for the "message" protocols.
# This sets everything needed from the config file.
self.msg_model = kwargs.get('model', DefaultMessageModel)
self.msg_model.nla_map = self.cfg.get_msg_model(cmd)
# self.cmd.msg_map = self.cfg.cmd_model
# construct a Command Model for the netlink communication
# self.cfg.cmd_model = self._assign_msg_to_cmd(self.cfg.cmd_model)
if not kwargs.get('dont_bind', False):
self.bind()
return self.msg_model.nla_map
def sendmsg(self, msg):
return self.nlm_request(msg,
msg_type=self.prid,
msg_flags=NLM_F_REQUEST|NLM_F_ACK|NLA_F_NESTED)