Skip to content

Commit 5e22996

Browse files
committed
Refactor MI module
1 parent 91d053a commit 5e22996

6 files changed

Lines changed: 186 additions & 178 deletions

File tree

opensips/mi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
## along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
##
1919

20-
from .connector import MI, OpenSIPSMIException
20+
from .connector import OpenSIPSMI, OpenSIPSMIException

opensips/mi/connection.py

Lines changed: 0 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,6 @@
1818
##
1919

2020
from abc import ABC, abstractmethod
21-
import urllib.error
22-
from . import jsonrpc_helper
23-
import socket
24-
25-
import urllib.request
26-
import urllib.parse
27-
import ssl
28-
29-
import os
30-
import time
31-
import errno
3221

3322
class Connection(ABC):
3423
@abstractmethod
@@ -42,167 +31,3 @@ def execute(self, method: str, params: dict):
4231
@abstractmethod
4332
def valid(self):
4433
pass
45-
46-
class Datagram(Connection):
47-
def __init__(self, **kwargs):
48-
if "ip" not in kwargs:
49-
raise ValueError("ip is required for Datagram connector")
50-
51-
if "port" not in kwargs:
52-
raise ValueError("port is required for Datagram connector")
53-
54-
self.ip = kwargs["ip"]
55-
self.port = kwargs["port"]
56-
57-
def execute(self, method: str, params: dict):
58-
jsoncmd = jsonrpc_helper.get_command(method, params)
59-
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
60-
try:
61-
udp_socket.sendto(jsoncmd.encode(), (self.ip, self.port))
62-
udp_socket.settimeout(5.0)
63-
reply = udp_socket.recv(1024)
64-
except Exception as e:
65-
raise jsonrpc_helper.JSONRPCException(e)
66-
finally:
67-
udp_socket.close()
68-
return jsonrpc_helper.get_reply(reply)
69-
70-
def valid(self):
71-
return (True, None)
72-
73-
class HTTP(Connection):
74-
def __init__(self, **kwargs):
75-
if "url" not in kwargs:
76-
raise ValueError("url is required for HTTP connector")
77-
78-
self.url = kwargs["url"]
79-
80-
def execute(self, method: str, params: dict):
81-
jsoncmd = jsonrpc_helper.get_command(method, params)
82-
headers = {
83-
"Content-Type": "application/json"
84-
}
85-
request = urllib.request.Request(self.url, jsoncmd.encode(), headers)
86-
url_parsed = urllib.parse.urlparse(self.url)
87-
try:
88-
if url_parsed.scheme == "https":
89-
reply = urllib.request.urlopen(request, context=ssl._create_unverified_context()).read().decode()
90-
else:
91-
reply = urllib.request.urlopen(request).read().decode()
92-
except urllib.error.HTTPError as e:
93-
raise jsonrpc_helper.JSONRPCException(str(e))
94-
return jsonrpc_helper.get_reply(reply)
95-
96-
def valid(self):
97-
try:
98-
url_parsed = urllib.parse.urlparse(self.url)
99-
if not url_parsed.port:
100-
if url_parsed.scheme == "http":
101-
url_parsed.port = 80
102-
else:
103-
url_parsed.port = 443
104-
sock = socket.socket()
105-
sock.connect((url_parsed.hostname, url_parsed.port))
106-
sock.close()
107-
return (True, None)
108-
except Exception as e:
109-
msg = "Could not connect to {} ({})".format(self.url, e)
110-
return (False, [msg, "Is OpenSIPS running?"])
111-
112-
class FIFO(Connection):
113-
REPLY_FIFO_FILE_TEMPLATE = "opensips_fifo_reply_{}_{}"\
114-
115-
def __init__(self, **kwargs):
116-
if "fifo_file" not in kwargs:
117-
raise ValueError("fifo_file is required for FIFO connector")
118-
if "fifo_file_fallback" not in kwargs:
119-
raise ValueError("fifo_file_fallback is required for FIFO connector")
120-
if "fifo_reply_dir" not in kwargs:
121-
raise ValueError("fifo_reply_dir is required for FIFO connector")
122-
123-
self.fifo_file = kwargs["fifo_file"]
124-
self.fifo_file_fallback = kwargs["fifo_file_fallback"]
125-
self.fifo_reply_dir = kwargs["fifo_reply_dir"]
126-
127-
def execute(self, method: str, params: dict):
128-
jsoncmd = jsonrpc_helper.get_command(method, params)
129-
130-
reply_fifo_file_name = self.REPLY_FIFO_FILE_TEMPLATE.format(os.getpid(), str(time.time()).replace(".", "_"))
131-
reply_fifo_file_path = os.path.join(self.fifo_reply_dir, reply_fifo_file_name)
132-
133-
try:
134-
os.unlink(reply_fifo_file_path)
135-
except OSError as e:
136-
if os.path.exists(reply_fifo_file_path):
137-
raise jsonrpc_helper.JSONRPCException(
138-
"Could not remove old reply FIFO file {}: {}"
139-
.format(reply_fifo_file_path, e))
140-
141-
try:
142-
os.mkfifo(reply_fifo_file_path)
143-
os.chmod(reply_fifo_file_path, 0o666)
144-
except OSError as e:
145-
raise jsonrpc_helper.JSONRPCException(
146-
"Could not create reply FIFO file {}: {}"
147-
.format(reply_fifo_file_path, e))
148-
149-
if not os.path.exists(self.fifo_file):
150-
raise jsonrpc_helper.JSONRPCException(
151-
"FIFO file {} does not exist"
152-
.format(self.fifo_file))
153-
154-
fifocmd = ":{}:{}".format(reply_fifo_file_path, jsoncmd)
155-
try:
156-
with open(self.fifo_file, "w") as fifo:
157-
fifo.write(fifocmd)
158-
except Exception as e:
159-
raise jsonrpc_helper.JSONRPCException(
160-
"Could not access FIFO file {}: {}"
161-
.format(self.fifo_file, e))
162-
163-
reply = None
164-
try:
165-
with open(reply_fifo_file_path, "r") as reply_fifo:
166-
reply = reply_fifo.readline()
167-
except KeyboardInterrupt:
168-
exit()
169-
finally:
170-
os.unlink(reply_fifo_file_path)
171-
172-
return jsonrpc_helper.get_reply(reply)
173-
174-
def valid(self):
175-
opensips_fifo = self.fifo_file
176-
if not os.path.exists(opensips_fifo):
177-
opensips_fifo = self.fifo_file_fallback
178-
if not os.path.exists(opensips_fifo):
179-
return (False, ["FIFO file {} does not exist, nor does fallback file {}"
180-
.format(self.fifo_file, self.fifo_file_fallback), "Is OpenSIPS running?"])
181-
try:
182-
open(opensips_fifo, "w").close()
183-
except OSError as e:
184-
extra = []
185-
if e.errno == errno.EACCES:
186-
sticky = self.get_sticky(os.path.dirname(opensips_fifo))
187-
if sticky:
188-
extra = ["starting with Linux kernel 4.19, processes can " +
189-
"no longer read from FIFO files ",
190-
"that are saved in directories with sticky " +
191-
"bits (such as {})".format(sticky),
192-
"and are not owned by the same user the " +
193-
"process runs with. ",
194-
"To fix this, either store the file in a non-sticky " +
195-
"bit directory (such as /var/run/opensips), ",
196-
"or disable fifo file protection using " +
197-
"'sysctl fs.protected_fifos=0' (NOT RECOMMENDED)"]
198-
msg = "Could not access FIFO file {}: {}".format(opensips_fifo, e)
199-
return (False, [msg] + extra)
200-
self.fifo_file = opensips_fifo
201-
return (True, None)
202-
203-
def get_sticky(self, path):
204-
if path == "/":
205-
return None
206-
if os.stat(path).st_mode & 0o1000 == 0o1000:
207-
return path
208-
return self.get_sticky(os.path.split(path)[0])

opensips/mi/connector.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
## along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
##
1919

20-
from .connection import HTTP, Datagram, FIFO
20+
from .fifo import FIFO
21+
from .datagram import Datagram
22+
from .http import HTTP
2123
from .jsonrpc_helper import JSONRPCError, JSONRPCException
2224

2325
class OpenSIPSMIException(Exception):
2426
pass
2527

26-
class MI():
28+
class OpenSIPSMI():
2729
def __init__(self, conn: str, **kwargs):
2830
if conn == "fifo":
2931
self.conn = FIFO(**kwargs)

opensips/mi/datagram.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from .connection import Connection
2+
from . import jsonrpc_helper
3+
import socket
4+
5+
class Datagram(Connection):
6+
def __init__(self, **kwargs):
7+
if "ip" not in kwargs:
8+
raise ValueError("ip is required for Datagram connector")
9+
10+
if "port" not in kwargs:
11+
raise ValueError("port is required for Datagram connector")
12+
13+
self.ip = kwargs["ip"]
14+
self.port = kwargs["port"]
15+
16+
def execute(self, method: str, params: dict):
17+
jsoncmd = jsonrpc_helper.get_command(method, params)
18+
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
19+
try:
20+
udp_socket.sendto(jsoncmd.encode(), (self.ip, self.port))
21+
udp_socket.settimeout(5.0)
22+
reply = udp_socket.recv(1024)
23+
except Exception as e:
24+
raise jsonrpc_helper.JSONRPCException(e)
25+
finally:
26+
udp_socket.close()
27+
return jsonrpc_helper.get_reply(reply)
28+
29+
def valid(self):
30+
return (True, None)

opensips/mi/fifo.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from .connection import Connection
2+
from . import jsonrpc_helper
3+
4+
import os
5+
import time
6+
import errno
7+
8+
class FIFO(Connection):
9+
REPLY_FIFO_FILE_TEMPLATE = "opensips_fifo_reply_{}_{}"\
10+
11+
def __init__(self, **kwargs):
12+
if "fifo_file" not in kwargs:
13+
raise ValueError("fifo_file is required for FIFO connector")
14+
if "fifo_file_fallback" not in kwargs:
15+
raise ValueError("fifo_file_fallback is required for FIFO connector")
16+
if "fifo_reply_dir" not in kwargs:
17+
raise ValueError("fifo_reply_dir is required for FIFO connector")
18+
19+
self.fifo_file = kwargs["fifo_file"]
20+
self.fifo_file_fallback = kwargs["fifo_file_fallback"]
21+
self.fifo_reply_dir = kwargs["fifo_reply_dir"]
22+
23+
def execute(self, method: str, params: dict):
24+
jsoncmd = jsonrpc_helper.get_command(method, params)
25+
26+
reply_fifo_file_name = self.REPLY_FIFO_FILE_TEMPLATE.format(os.getpid(), str(time.time()).replace(".", "_"))
27+
reply_fifo_file_path = os.path.join(self.fifo_reply_dir, reply_fifo_file_name)
28+
29+
try:
30+
os.unlink(reply_fifo_file_path)
31+
except OSError as e:
32+
if os.path.exists(reply_fifo_file_path):
33+
raise jsonrpc_helper.JSONRPCException(
34+
"Could not remove old reply FIFO file {}: {}"
35+
.format(reply_fifo_file_path, e))
36+
37+
try:
38+
os.mkfifo(reply_fifo_file_path)
39+
os.chmod(reply_fifo_file_path, 0o666)
40+
except OSError as e:
41+
raise jsonrpc_helper.JSONRPCException(
42+
"Could not create reply FIFO file {}: {}"
43+
.format(reply_fifo_file_path, e))
44+
45+
if not os.path.exists(self.fifo_file):
46+
raise jsonrpc_helper.JSONRPCException(
47+
"FIFO file {} does not exist"
48+
.format(self.fifo_file))
49+
50+
fifocmd = ":{}:{}".format(reply_fifo_file_path, jsoncmd)
51+
try:
52+
with open(self.fifo_file, "w") as fifo:
53+
fifo.write(fifocmd)
54+
except Exception as e:
55+
raise jsonrpc_helper.JSONRPCException(
56+
"Could not access FIFO file {}: {}"
57+
.format(self.fifo_file, e))
58+
59+
reply = None
60+
try:
61+
with open(reply_fifo_file_path, "r") as reply_fifo:
62+
reply = reply_fifo.readline()
63+
except KeyboardInterrupt:
64+
exit()
65+
finally:
66+
os.unlink(reply_fifo_file_path)
67+
68+
return jsonrpc_helper.get_reply(reply)
69+
70+
def valid(self):
71+
opensips_fifo = self.fifo_file
72+
if not os.path.exists(opensips_fifo):
73+
opensips_fifo = self.fifo_file_fallback
74+
if not os.path.exists(opensips_fifo):
75+
return (False, ["FIFO file {} does not exist, nor does fallback file {}"
76+
.format(self.fifo_file, self.fifo_file_fallback), "Is OpenSIPS running?"])
77+
try:
78+
open(opensips_fifo, "w").close()
79+
except OSError as e:
80+
extra = []
81+
if e.errno == errno.EACCES:
82+
sticky = self.get_sticky(os.path.dirname(opensips_fifo))
83+
if sticky:
84+
extra = ["starting with Linux kernel 4.19, processes can " +
85+
"no longer read from FIFO files ",
86+
"that are saved in directories with sticky " +
87+
"bits (such as {})".format(sticky),
88+
"and are not owned by the same user the " +
89+
"process runs with. ",
90+
"To fix this, either store the file in a non-sticky " +
91+
"bit directory (such as /var/run/opensips), ",
92+
"or disable fifo file protection using " +
93+
"'sysctl fs.protected_fifos=0' (NOT RECOMMENDED)"]
94+
msg = "Could not access FIFO file {}: {}".format(opensips_fifo, e)
95+
return (False, [msg] + extra)
96+
self.fifo_file = opensips_fifo
97+
return (True, None)
98+
99+
def get_sticky(self, path):
100+
if path == "/":
101+
return None
102+
if os.stat(path).st_mode & 0o1000 == 0o1000:
103+
return path
104+
return self.get_sticky(os.path.split(path)[0])

opensips/mi/http.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from .connection import Connection
2+
import urllib.error
3+
from . import jsonrpc_helper
4+
import socket
5+
6+
import urllib.request
7+
import urllib.parse
8+
import ssl
9+
10+
class HTTP(Connection):
11+
def __init__(self, **kwargs):
12+
if "url" not in kwargs:
13+
raise ValueError("url is required for HTTP connector")
14+
15+
self.url = kwargs["url"]
16+
17+
def execute(self, method: str, params: dict):
18+
jsoncmd = jsonrpc_helper.get_command(method, params)
19+
headers = {
20+
"Content-Type": "application/json"
21+
}
22+
request = urllib.request.Request(self.url, jsoncmd.encode(), headers)
23+
url_parsed = urllib.parse.urlparse(self.url)
24+
try:
25+
if url_parsed.scheme == "https":
26+
reply = urllib.request.urlopen(request, context=ssl._create_unverified_context()).read().decode()
27+
else:
28+
reply = urllib.request.urlopen(request).read().decode()
29+
except urllib.error.HTTPError as e:
30+
raise jsonrpc_helper.JSONRPCException(str(e))
31+
return jsonrpc_helper.get_reply(reply)
32+
33+
def valid(self):
34+
try:
35+
url_parsed = urllib.parse.urlparse(self.url)
36+
if not url_parsed.port:
37+
if url_parsed.scheme == "http":
38+
url_parsed.port = 80
39+
else:
40+
url_parsed.port = 443
41+
sock = socket.socket()
42+
sock.connect((url_parsed.hostname, url_parsed.port))
43+
sock.close()
44+
return (True, None)
45+
except Exception as e:
46+
msg = "Could not connect to {} ({})".format(self.url, e)
47+
return (False, [msg, "Is OpenSIPS running?"])

0 commit comments

Comments
 (0)