opensipscli 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env python
2
+ ##
3
+ ## This file is part of OpenSIPS CLI
4
+ ## (see https://github.com/OpenSIPS/opensips-cli).
5
+ ##
6
+ ## This program is free software: you can redistribute it and/or modify
7
+ ## it under the terms of the GNU General Public License as published by
8
+ ## the Free Software Foundation, either version 3 of the License, or
9
+ ## (at your option) any later version.
10
+ ##
11
+ ## This program is distributed in the hope that it will be useful,
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ ## GNU General Public License for more details.
15
+ ##
16
+ ## You should have received a copy of the GNU General Public License
17
+ ## along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ ##
19
+
20
+ from datetime import datetime
21
+ from time import time
22
+ import random
23
+ import socket
24
+ from opensipscli import comm
25
+ from opensipscli.config import cfg
26
+ from opensipscli.logger import logger
27
+ from opensipscli.module import Module
28
+
29
+ TRACE_BUFFER_SIZE = 65535
30
+
31
+ '''
32
+ find out more information here:
33
+ * https://github.com/sipcapture/HEP/blob/master/docs/HEP3NetworkProtocolSpecification_REV26.pdf
34
+ '''
35
+
36
+ protocol_types = {
37
+ 0x00: "UNKNOWN",
38
+ 0x01: "SIP",
39
+ 0x02: "XMPP",
40
+ 0x03: "SDP",
41
+ 0x04: "RTP",
42
+ 0x05: "RTCP JSON",
43
+ 0x56: "LOG",
44
+ 0x57: "MI",
45
+ 0x58: "REST",
46
+ 0x59: "NET",
47
+ 0x60: "CONTROL",
48
+ }
49
+
50
+ protocol_ids = {
51
+ num:name[8:] for name,num in vars(socket).items() if name.startswith("IPPROTO")
52
+ }
53
+
54
+ class HEPpacketException(Exception):
55
+ pass
56
+
57
+ class HEPpacket(object):
58
+
59
+ def __init__(self, payloads):
60
+ self.payloads = payloads
61
+ self.family = socket.AF_INET
62
+ self.protocol = "UNKNOWN"
63
+ self.src_addr = None
64
+ self.dst_addr = None
65
+ self.src_port = None
66
+ self.dst_port = None
67
+ self.data = None
68
+ self.correlation = None
69
+ self.ts = time()
70
+ self.tms = datetime.now().microsecond
71
+
72
+ def __str__(self):
73
+ time_str = "{}.{}".format(
74
+ self.ts,
75
+ self.tms)
76
+ protocol_str = " {}/{}".format(
77
+ self.protocol,
78
+ self.type)
79
+
80
+ if self.type == "SIP":
81
+ ip_str = " {}:{} -> {}:{}".format(
82
+ socket.inet_ntop(self.family, self.src_addr),
83
+ self.src_port,
84
+ socket.inet_ntop(self.family, self.dst_addr),
85
+ self.dst_port)
86
+ else:
87
+ ip_str = ""
88
+ if self.data:
89
+ data_str = self.data.decode()
90
+ else:
91
+ data_str = ""
92
+
93
+ return logger.color(logger.BLUE, time_str) + \
94
+ logger.color(logger.CYAN, protocol_str + ip_str) + \
95
+ "\n" + data_str
96
+
97
+ def parse(self):
98
+ length = len(self.payloads)
99
+ payloads = self.payloads
100
+ while length > 0:
101
+ if length < 6:
102
+ logger.error("payload too small {}".format(length))
103
+ return None
104
+ chunk_vendor_id = int.from_bytes(payloads[0:2],
105
+ byteorder="big", signed=False)
106
+ chunk_type_id = int.from_bytes(payloads[2:4],
107
+ byteorder="big", signed=False)
108
+ chunk_len = int.from_bytes(payloads[4:6],
109
+ byteorder="big", signed=False)
110
+ if chunk_len < 6:
111
+ logger.error("chunk too small {}".format(chunk_len))
112
+ return None
113
+ payload = payloads[6:chunk_len]
114
+ payloads = payloads[chunk_len:]
115
+ length = length - chunk_len
116
+ self.push_chunk(chunk_vendor_id, chunk_type_id, payload)
117
+
118
+ def push_chunk(self, vendor_id, type_id, payload):
119
+
120
+ if vendor_id != 0:
121
+ logger.warning("Unknown vendor id {}".format(vendor_id))
122
+ raise HEPpacketException
123
+ if type_id == 0x0001:
124
+ if len(payload) != 1:
125
+ raise HEPpacketException
126
+ self.family = payload[0]
127
+ elif type_id == 0x0002:
128
+ if len(payload) != 1:
129
+ raise HEPpacketException
130
+ if not payload[0] in protocol_ids:
131
+ self.protocol = str(payload[0])
132
+ else:
133
+ self.protocol = protocol_ids[payload[0]]
134
+ elif type_id >= 0x0003 and type_id <= 0x0006:
135
+ expected_payload_len = 4 if type_id <= 0x0004 else 16
136
+ if len(payload) != expected_payload_len:
137
+ raise HEPpacketException
138
+ if type_id == 0x0003 or type_id == 0x0005:
139
+ self.src_addr = payload
140
+ else:
141
+ self.dst_addr = payload
142
+ elif type_id == 0x0007 or type_id == 0x0008:
143
+ if len(payload) != 2:
144
+ raise HEPpacketException
145
+ port = int.from_bytes(payload,
146
+ byteorder="big", signed=False)
147
+ if type_id == 7:
148
+ self.src_port = port
149
+ else:
150
+ self.dst_port = port
151
+ elif type_id == 0x0009 or type_id == 0x000a:
152
+ if len(payload) != 4:
153
+ raise HEPpacketException
154
+ timespec = int.from_bytes(payload,
155
+ byteorder="big", signed=False)
156
+ if type_id == 0x0009:
157
+ self.ts = timespec
158
+ else:
159
+ self.tms = timespec
160
+ elif type_id == 0x000b:
161
+ if len(payload) != 1:
162
+ raise HEPpacketException
163
+ if not payload[0] in protocol_types:
164
+ self.type = str(payload[0])
165
+ else:
166
+ self.type = protocol_types[payload[0]]
167
+ elif type_id == 0x000c:
168
+ pass # capture id not used now
169
+ elif type_id == 0x000f:
170
+ self.data = payload
171
+ elif type_id == 0x0011:
172
+ self.correlation = payload
173
+ else:
174
+ logger.warning("unhandled payload type {}".format(type_id))
175
+
176
+ class trace(Module):
177
+
178
+ def __print_hep(self, packet):
179
+ # this works as a HEP parser
180
+ logger.debug("initial packet size is {}".format(len(packet)))
181
+
182
+ while len(packet) > 0:
183
+ if len(packet) < 4:
184
+ return packet
185
+ # currently only HEPv3 is accepted
186
+ if packet[0:4] != b'HEP3':
187
+ logger.warning("packet not HEPv3: [{}]".format(packet[0:4]))
188
+ return None
189
+ length = int.from_bytes(packet[4:6], byteorder="big", signed=False)
190
+ if length > len(packet):
191
+ logger.debug("partial packet: {} out of {}".
192
+ format(len(packet), length))
193
+ # wait for entire packet to parse it
194
+ return packet
195
+ logger.debug("packet size is {}".format(length))
196
+ # skip the header
197
+ hep_packet = HEPpacket(packet[6:length])
198
+ try:
199
+ hep_packet.parse()
200
+ except HEPpacketException:
201
+ return None
202
+ packet = packet[length:]
203
+ print(hep_packet)
204
+
205
+ return packet
206
+
207
+ def __complete__(self, command, text, line, begidx, endidx):
208
+ filters = [ "caller", "callee", "ip" ]
209
+
210
+ # remove the filters already used
211
+ filters = [f for f in filters if line.find(f + "=") == -1]
212
+ if not command:
213
+ return filters
214
+
215
+ if (not text or text == "") and line[-1] == "=":
216
+ return [""]
217
+
218
+ ret = [f for f in filters if (f.startswith(text) and line.find(f + "=") == -1)]
219
+ if len(ret) == 1 :
220
+ ret[0] = ret[0] + "="
221
+ return ret
222
+
223
+ def __get_methods__(self):
224
+ return None
225
+
226
+ def do_trace(self, params, modifiers):
227
+
228
+ filters = []
229
+
230
+ if params is None:
231
+ caller_f = input("Caller filter: ")
232
+ if caller_f != "":
233
+ filters.append("caller={}".format(caller_f))
234
+ callee_f = input("Callee filter: ")
235
+ if callee_f != "":
236
+ filters.append("callee={}".format(callee_f))
237
+ ip_f = input("Source IP filter: ")
238
+ if ip_f != "":
239
+ filters.append("ip={}".format(ip_f))
240
+ if len(filters) == 0:
241
+ ans = cfg.read_param(None, "No filter specified! "\
242
+ "Continue without a filter?", False, True)
243
+ if not ans:
244
+ return False
245
+ filters = None
246
+ else:
247
+ filters = params
248
+
249
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
250
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
251
+ trace_ip = cfg.get("trace_listen_ip")
252
+ trace_port = int(cfg.get("trace_listen_port"))
253
+ s.bind((trace_ip, trace_port))
254
+ if trace_port == 0:
255
+ trace_port = s.getsockname()[1]
256
+ s.listen(1)
257
+ conn = None
258
+ trace_name = "opensips-cli.{}".format(random.randint(0, 65536))
259
+ trace_socket = "hep:{}:{};transport=tcp;version=3".format(
260
+ trace_ip, trace_port)
261
+ args = {
262
+ 'id': trace_name,
263
+ 'uri': trace_socket,
264
+ }
265
+ if filters:
266
+ args['filter'] = filters
267
+
268
+ logger.debug("filters are {}".format(filters))
269
+ trace_started = comm.execute('trace_start', args)
270
+ if not trace_started:
271
+ return False
272
+
273
+ try:
274
+ conn, addr = s.accept()
275
+ logger.debug("New TCP connection from {}:{}".
276
+ format(addr[0], addr[1]))
277
+ remaining = b''
278
+ while True:
279
+ data = conn.recv(TRACE_BUFFER_SIZE)
280
+ if not data:
281
+ break
282
+ remaining = self.__print_hep(remaining + data)
283
+ if remaining is None:
284
+ break
285
+ except KeyboardInterrupt:
286
+ comm.execute('trace_stop', {'id' : trace_name }, True)
287
+ if conn is not None:
288
+ conn.close()
289
+
290
+ def __exclude__(self):
291
+ valid = comm.valid()
292
+ return (not valid[0], valid[1])
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env python
2
+ ##
3
+ ## This file is part of OpenSIPS CLI
4
+ ## (see https://github.com/OpenSIPS/opensips-cli).
5
+ ##
6
+ ## This program is free software: you can redistribute it and/or modify
7
+ ## it under the terms of the GNU General Public License as published by
8
+ ## the Free Software Foundation, either version 3 of the License, or
9
+ ## (at your option) any later version.
10
+ ##
11
+ ## This program is distributed in the hope that it will be useful,
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ ## GNU General Public License for more details.
15
+ ##
16
+ ## You should have received a copy of the GNU General Public License
17
+ ## along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ ##
19
+
20
+ from opensipscli.module import Module
21
+ from opensipscli.logger import logger
22
+ from opensipscli.config import cfg
23
+ from opensipscli import comm
24
+ from threading import Thread
25
+ import subprocess
26
+ import shutil
27
+ import os
28
+
29
+ DEFAULT_PROCESS_NAME = 'opensips'
30
+
31
+ class trap(Module):
32
+
33
+ def get_process_name(self):
34
+ if cfg.exists("process_name"):
35
+ return cfg.get("process_name")
36
+ else:
37
+ return DEFAULT_PROCESS_NAME
38
+
39
+ def get_pids(self):
40
+ try:
41
+ mi_pids = comm.execute('ps')
42
+ self.pids = [str(pid['PID']) for pid in mi_pids['Processes']]
43
+ info = ["Process ID={} PID={} Type={}".
44
+ format(pid['ID'], pid['PID'], pid['Type'])
45
+ for pid in mi_pids['Processes']]
46
+ self.process_info = "\n".join(info)
47
+ except:
48
+ self.pids = []
49
+
50
+ def get_gdb_output(self, pid):
51
+ if os.path.islink("/proc/{}/exe".format(pid)):
52
+ # get process line of pid
53
+ process = os.readlink("/proc/{}/exe".format(pid))
54
+ else:
55
+ logger.error("could not find OpenSIPS process {} running on local machine".format(pid))
56
+ return -1
57
+ # Check if process is opensips (can be different if CLI is running on another host)
58
+ path, filename = os.path.split(process)
59
+ process_name = self.get_process_name()
60
+ if filename != process_name:
61
+ logger.error("process ID {}/{} is not OpenSIPS process".format(pid, filename))
62
+ return -1
63
+ logger.debug("Dumping backtrace for {} pid {}".format(process, pid))
64
+ cmd = ["gdb", process, pid, "-batch", "--eval-command", "bt full"]
65
+ out = subprocess.check_output(cmd)
66
+ if len(out) != 0:
67
+ self.gdb_outputs[pid] = out.decode()
68
+
69
+ def do_trap(self, params, modifiers):
70
+
71
+ self.pids = []
72
+ self.gdb_outputs = {}
73
+ self.process_info = ""
74
+
75
+ trap_file = cfg.get("trap_file")
76
+ process_name = self.get_process_name()
77
+
78
+ logger.info("Trapping {} in {}".format(process_name, trap_file))
79
+ if params and len(params) > 0:
80
+ self.pids = params
81
+ else:
82
+ thread = Thread(target=self.get_pids)
83
+ thread.start()
84
+ thread.join(timeout=1)
85
+ if len(self.pids) == 0:
86
+ logger.warning("could not get OpenSIPS pids through MI!")
87
+ try:
88
+ ps_pids = subprocess.check_output(["pidof", process_name])
89
+ self.pids = ps_pids.decode().split()
90
+ except:
91
+ logger.warning("could not find any OpenSIPS running!")
92
+ self.pids = []
93
+
94
+ if len(self.pids) < 1:
95
+ logger.error("could not find OpenSIPS' pids")
96
+ return -1
97
+
98
+ logger.debug("Dumping PIDs: {}".format(", ".join(self.pids)))
99
+
100
+ threads = []
101
+ for pid in self.pids:
102
+ thread = Thread(target=self.get_gdb_output, args=(pid,))
103
+ thread.start()
104
+ threads.append(thread)
105
+
106
+ for thread in threads:
107
+ thread.join()
108
+
109
+ if len(self.gdb_outputs) == 0:
110
+ logger.error("could not get output of gdb")
111
+ return -1
112
+
113
+ with open(trap_file, "w") as tf:
114
+ tf.write(self.process_info)
115
+ for pid in self.pids:
116
+ if pid not in self.gdb_outputs:
117
+ logger.warning("No output from pid {}".format(pid))
118
+ continue
119
+ try:
120
+ procinfo = subprocess.check_output(
121
+ ["ps", "--no-headers", "-ww", "-fp", pid]).decode()[:-1]
122
+ except:
123
+ procinfo = "UNKNOWN"
124
+
125
+ tf.write("\n\n---start {} ({})\n{}".
126
+ format(pid, procinfo, self.gdb_outputs[pid]))
127
+
128
+ print("Trap file: {}".format(trap_file))
129
+
130
+ def __get_methods__(self):
131
+ return None
132
+
133
+ def __exclude__(self):
134
+ valid = comm.valid()
135
+ if not valid[0]:
136
+ return False, valid[1]
137
+ # check to see if we have gdb installed
138
+ return (shutil.which("gdb") is None, None)