sippy 2.1.0__tar.gz → 2.2.0__tar.gz

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.
Files changed (132) hide show
  1. {sippy-2.1.0/sippy.egg-info → sippy-2.2.0}/PKG-INFO +11 -3
  2. {sippy-2.1.0 → sippy-2.2.0}/README.md +9 -2
  3. sippy-2.2.0/pyproject.toml +3 -0
  4. {sippy-2.1.0 → sippy-2.2.0}/requirements.txt +1 -0
  5. {sippy-2.1.0 → sippy-2.2.0}/setup.py +1 -1
  6. {sippy-2.1.0 → sippy-2.2.0}/sippy/B2BRoute.py +15 -2
  7. sippy-2.2.0/sippy/B2BTransforms.py +99 -0
  8. {sippy-2.1.0 → sippy-2.2.0}/sippy/CLIManager.py +9 -8
  9. {sippy-2.1.0 → sippy-2.2.0}/sippy/Core/EventDispatcher.py +0 -5
  10. {sippy-2.1.0 → sippy-2.2.0}/sippy/Core/Exceptions.py +1 -1
  11. {sippy-2.1.0 → sippy-2.2.0}/sippy/Exceptions/RtpProxyError.py +3 -0
  12. {sippy-2.1.0 → sippy-2.2.0}/sippy/Exceptions/SipParseError.py +7 -3
  13. {sippy-2.1.0 → sippy-2.2.0}/sippy/External_command.py +1 -1
  14. {sippy-2.1.0 → sippy-2.2.0}/sippy/MsgBody.py +2 -2
  15. {sippy-2.1.0 → sippy-2.2.0}/sippy/MultipartMixBody.py +3 -3
  16. {sippy-2.1.0 → sippy-2.2.0}/sippy/MyConfigParser.py +12 -1
  17. sippy-2.2.0/sippy/Network_server.py +150 -0
  18. {sippy-2.1.0 → sippy-2.2.0}/sippy/RadiusAccounting.py +4 -0
  19. {sippy-2.1.0 → sippy-2.2.0}/sippy/SdpBody.py +14 -14
  20. {sippy-2.1.0 → sippy-2.2.0}/sippy/SdpConnecton.py +2 -2
  21. {sippy-2.1.0 → sippy-2.2.0}/sippy/SdpGeneric.py +1 -1
  22. {sippy-2.1.0 → sippy-2.2.0}/sippy/SdpMedia.py +2 -2
  23. {sippy-2.1.0 → sippy-2.2.0}/sippy/SdpMediaDescription.py +8 -2
  24. {sippy-2.1.0 → sippy-2.2.0}/sippy/SdpOrigin.py +3 -2
  25. {sippy-2.1.0 → sippy-2.2.0}/sippy/Security/SipNonce.py +2 -2
  26. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipAddress.py +2 -2
  27. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipAddressHF.py +3 -3
  28. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipAllow.py +1 -1
  29. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipAuthorization.py +1 -1
  30. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipCSeq.py +1 -1
  31. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipCallId.py +3 -4
  32. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipCiscoGUID.py +1 -1
  33. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipConf.py +19 -0
  34. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipContact.py +4 -2
  35. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipFrom.py +3 -2
  36. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipGenericHF.py +1 -1
  37. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipHeader.py +5 -4
  38. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipLogger.py +1 -1
  39. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipMsg.py +14 -11
  40. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipNumericHF.py +3 -3
  41. sippy-2.2.0/sippy/SipPath.py +37 -0
  42. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipRAck.py +1 -1
  43. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipReason.py +21 -5
  44. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipRecordRoute.py +3 -0
  45. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipReplaces.py +1 -1
  46. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipRequest.py +5 -7
  47. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipResponse.py +2 -2
  48. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipRoute.py +1 -0
  49. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipServer.py +1 -1
  50. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipSupported.py +1 -1
  51. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipTransactionManager.py +69 -36
  52. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipURL.py +29 -10
  53. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipVia.py +37 -25
  54. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipWWWAuthenticate.py +2 -2
  55. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipWarning.py +1 -1
  56. {sippy-2.1.0 → sippy-2.2.0}/sippy/StatefulProxy.py +23 -1
  57. {sippy-2.1.0 → sippy-2.2.0}/sippy/Time/MonoTime.py +0 -11
  58. {sippy-2.1.0 → sippy-2.2.0}/sippy/UA.py +29 -12
  59. {sippy-2.1.0 → sippy-2.2.0}/sippy/UaStateConnected.py +19 -17
  60. {sippy-2.1.0 → sippy-2.2.0}/sippy/UacStateCancelling.py +3 -3
  61. {sippy-2.1.0 → sippy-2.2.0}/sippy/UacStateIdle.py +10 -7
  62. {sippy-2.1.0 → sippy-2.2.0}/sippy/UacStateRinging.py +16 -55
  63. {sippy-2.1.0 → sippy-2.2.0}/sippy/UacStateTrying.py +60 -55
  64. {sippy-2.1.0 → sippy-2.2.0}/sippy/UacStateUpdating.py +5 -5
  65. {sippy-2.1.0 → sippy-2.2.0}/sippy/UasStateIdle.py +9 -9
  66. {sippy-2.1.0 → sippy-2.2.0}/sippy/UasStateRinging.py +2 -2
  67. {sippy-2.1.0 → sippy-2.2.0}/sippy/UasStateTrying.py +2 -2
  68. {sippy-2.1.0 → sippy-2.2.0}/sippy/UasStateUpdating.py +2 -2
  69. {sippy-2.1.0 → sippy-2.2.0}/sippy/Udp_server.py +73 -116
  70. sippy-2.2.0/sippy/Wss_server.py +158 -0
  71. {sippy-2.1.0 → sippy-2.2.0}/sippy/XMPP_server.py +12 -12
  72. {sippy-2.1.0 → sippy-2.2.0}/sippy/b2bua_radius.py +132 -59
  73. {sippy-2.1.0 → sippy-2.2.0/sippy.egg-info}/PKG-INFO +11 -3
  74. {sippy-2.1.0 → sippy-2.2.0}/sippy.egg-info/SOURCES.txt +9 -8
  75. {sippy-2.1.0 → sippy-2.2.0}/sippy.egg-info/requires.txt +1 -0
  76. sippy-2.2.0/tests/test_B2BRoute.py +27 -0
  77. sippy-2.2.0/tests/test_B2BTransforms.py +48 -0
  78. {sippy-2.1.0 → sippy-2.2.0}/tests/test_SdbBody.py +31 -25
  79. sippy-2.2.0/tests/test_Udp_server.py +17 -0
  80. sippy-2.1.0/sippy/Rtp_proxy_client.py +0 -279
  81. sippy-2.1.0/sippy/Rtp_proxy_client_local.py +0 -53
  82. sippy-2.1.0/sippy/Rtp_proxy_client_net.py +0 -42
  83. sippy-2.1.0/sippy/Rtp_proxy_client_stream.py +0 -226
  84. sippy-2.1.0/sippy/Rtp_proxy_client_udp.py +0 -225
  85. sippy-2.1.0/sippy/Rtp_proxy_cmd.py +0 -224
  86. sippy-2.1.0/sippy/Rtp_proxy_session.py +0 -573
  87. {sippy-2.1.0 → sippy-2.2.0}/LICENSE +0 -0
  88. {sippy-2.1.0 → sippy-2.2.0}/MANIFEST.in +0 -0
  89. {sippy-2.1.0 → sippy-2.2.0}/setup.cfg +0 -0
  90. {sippy-2.1.0 → sippy-2.2.0}/sippy/CCEvents.py +0 -0
  91. {sippy-2.1.0 → sippy-2.2.0}/sippy/Core/__init__.py +0 -0
  92. {sippy-2.1.0 → sippy-2.2.0}/sippy/ESipHeaderCSV.py +0 -0
  93. {sippy-2.1.0 → sippy-2.2.0}/sippy/ESipHeaderIgnore.py +0 -0
  94. {sippy-2.1.0 → sippy-2.2.0}/sippy/Exceptions/__init__.py +0 -0
  95. {sippy-2.1.0 → sippy-2.2.0}/sippy/FakeAccounting.py +0 -0
  96. {sippy-2.1.0 → sippy-2.2.0}/sippy/Math/__init__.py +0 -0
  97. {sippy-2.1.0 → sippy-2.2.0}/sippy/Math/recfilter.py +0 -0
  98. {sippy-2.1.0 → sippy-2.2.0}/sippy/RadiusAuthorisation.py +0 -0
  99. {sippy-2.1.0 → sippy-2.2.0}/sippy/Radius_client.py +0 -0
  100. {sippy-2.1.0 → sippy-2.2.0}/sippy/Security/__init__.py +0 -0
  101. {sippy-2.1.0 → sippy-2.2.0}/sippy/Signal.py +0 -0
  102. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipAlso.py +0 -0
  103. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipCCDiversion.py +0 -0
  104. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipContentLength.py +0 -0
  105. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipContentType.py +0 -0
  106. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipDiversion.py +0 -0
  107. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipExpires.py +0 -0
  108. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipMaxForwards.py +0 -0
  109. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipPAssertedIdentity.py +0 -0
  110. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipProxyAuthenticate.py +0 -0
  111. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipProxyAuthorization.py +0 -0
  112. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipRSeq.py +0 -0
  113. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipReferTo.py +0 -0
  114. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipReferredBy.py +0 -0
  115. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipRegistrationAgent.py +0 -0
  116. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipTo.py +0 -0
  117. {sippy-2.1.0 → sippy-2.2.0}/sippy/SipUserAgent.py +0 -0
  118. {sippy-2.1.0 → sippy-2.2.0}/sippy/Time/Timeout.py +0 -0
  119. {sippy-2.1.0 → sippy-2.2.0}/sippy/Time/__init__.py +0 -0
  120. {sippy-2.1.0 → sippy-2.2.0}/sippy/Time/clock_dtime.py +0 -0
  121. {sippy-2.1.0 → sippy-2.2.0}/sippy/UaStateDead.py +0 -0
  122. {sippy-2.1.0 → sippy-2.2.0}/sippy/UaStateDisconnected.py +0 -0
  123. {sippy-2.1.0 → sippy-2.2.0}/sippy/UaStateFailed.py +0 -0
  124. {sippy-2.1.0 → sippy-2.2.0}/sippy/UaStateGeneric.py +0 -0
  125. {sippy-2.1.0 → sippy-2.2.0}/sippy/__init__.py +0 -0
  126. {sippy-2.1.0 → sippy-2.2.0}/sippy/b2bua_simple.py +0 -0
  127. {sippy-2.1.0 → sippy-2.2.0}/sippy/dictionary +0 -0
  128. {sippy-2.1.0 → sippy-2.2.0}/sippy/misc.py +0 -0
  129. {sippy-2.1.0 → sippy-2.2.0}/sippy.egg-info/dependency_links.txt +0 -0
  130. {sippy-2.1.0 → sippy-2.2.0}/sippy.egg-info/entry_points.txt +0 -0
  131. {sippy-2.1.0 → sippy-2.2.0}/sippy.egg-info/top_level.txt +0 -0
  132. {sippy-2.1.0 → sippy-2.2.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sippy
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: RFC3261 SIP Stack and Back-to-Back User Agent (B2BUA)
5
5
  Home-page: http://www.b2bua.org/
6
6
  Author: Sippy Software, Inc.
@@ -14,6 +14,7 @@ Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: ElPeriodic>=1.1
16
16
  Requires-Dist: pycryptodome
17
+ Requires-Dist: websockets
17
18
 
18
19
  [![Build Status@GitHub](https://github.com/sippy/b2bua/workflows/Check%20Python%20Wheels/badge.svg?branch=master)](https://github.com/sippy/b2bua/actions?query=branch%3Amaster++)
19
20
 
@@ -33,6 +34,13 @@ Sippy B2BUA doesn't perform any media relaying or processing, therefore it
33
34
  doesn't introduce any additional packet loss, delay or jitter into the media
34
35
  path.
35
36
 
37
+ ## News
38
+
39
+ - Support for WebRTC clients has been added via RFC7118-compatible secure
40
+ websocket (`wss`) interface. Combined with the functionality provided
41
+ by the RTPProxy's `dtls_gw` and `ice_lite` modules allows building
42
+ high-capacity WebRTC<->SIP gateways.
43
+
36
44
  ## Features
37
45
 
38
46
  - 5,000-10,000 simultaneous sessions per server
@@ -66,8 +74,8 @@ path.
66
74
  To get started, you can use the b2bua_simple implementation. The following
67
75
  example will cause the b2bua run in the foreground so you can see the SIP
68
76
  messaging. If you make a call to the IP address of your host machine, the b2bua
69
- will recieve the call on its UAC side, and it will send a new call leg out its
70
- UAS side to the IP address 192.168.1.1. It is expected that 192.168.1.1 is some
77
+ will recieve the call on its UAS side, and it will send a new call leg out its
78
+ UAC side to the IP address 192.168.1.1. It is expected that 192.168.1.1 is some
71
79
  sort of SIP switch or termination gateway.
72
80
 
73
81
  `b2bua_simple -f -n 192.168.1.1`
@@ -16,6 +16,13 @@ Sippy B2BUA doesn't perform any media relaying or processing, therefore it
16
16
  doesn't introduce any additional packet loss, delay or jitter into the media
17
17
  path.
18
18
 
19
+ ## News
20
+
21
+ - Support for WebRTC clients has been added via RFC7118-compatible secure
22
+ websocket (`wss`) interface. Combined with the functionality provided
23
+ by the RTPProxy's `dtls_gw` and `ice_lite` modules allows building
24
+ high-capacity WebRTC<->SIP gateways.
25
+
19
26
  ## Features
20
27
 
21
28
  - 5,000-10,000 simultaneous sessions per server
@@ -49,8 +56,8 @@ path.
49
56
  To get started, you can use the b2bua_simple implementation. The following
50
57
  example will cause the b2bua run in the foreground so you can see the SIP
51
58
  messaging. If you make a call to the IP address of your host machine, the b2bua
52
- will recieve the call on its UAC side, and it will send a new call leg out its
53
- UAS side to the IP address 192.168.1.1. It is expected that 192.168.1.1 is some
59
+ will recieve the call on its UAS side, and it will send a new call leg out its
60
+ UAC side to the IP address 192.168.1.1. It is expected that 192.168.1.1 is some
54
61
  sort of SIP switch or termination gateway.
55
62
 
56
63
  `b2bua_simple -f -n 192.168.1.1`
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
@@ -1,2 +1,3 @@
1
1
  ElPeriodic>=1.1
2
2
  pycryptodome
3
+ websockets
@@ -8,7 +8,7 @@ with open("README.md", "r") as fh:
8
8
 
9
9
  setup(
10
10
  name = "sippy",
11
- version = "2.1.0",
11
+ version = "2.2.0",
12
12
  packages = find_packages(),
13
13
 
14
14
  install_requires = requirements,
@@ -26,6 +26,7 @@
26
26
 
27
27
  from sippy.SipHeader import SipHeader
28
28
  from sippy.SipConf import SipConf
29
+ from sippy.B2BTransforms import getTransProc
29
30
 
30
31
  try:
31
32
  from urllib import unquote
@@ -33,6 +34,11 @@ except ImportError:
33
34
  from urllib.parse import unquote
34
35
  from socket import getaddrinfo, SOCK_STREAM, AF_INET, AF_INET6
35
36
 
37
+ SRC_WSS = '[[WSS]]'
38
+ SRC_PROXY = '[[PROXY]]'
39
+ DST_SIP_UA = 'sip-ua'
40
+ DST_WSS_UA = 'wss-ua'
41
+
36
42
  class B2BRoute(object):
37
43
  rnum = None
38
44
  addrinfo = None
@@ -48,6 +54,7 @@ class B2BRoute(object):
48
54
  forward_on_fail = False
49
55
  user = None
50
56
  passw = None
57
+ pass_auth = True
51
58
  cli = None
52
59
  cli_set = False
53
60
  params = None
@@ -70,6 +77,7 @@ class B2BRoute(object):
70
77
  self.forward_on_fail = cself.forward_on_fail
71
78
  self.user = cself.user
72
79
  self.passw = cself.passw
80
+ self.pass_auth = cself.pass_auth
73
81
  self.cli = cself.cli
74
82
  self.cli_set = cself.cli_set
75
83
  self.params = dict(cself.params)
@@ -105,7 +113,8 @@ class B2BRoute(object):
105
113
  port = SipConf.default_port
106
114
  else:
107
115
  port = int(hostport[1])
108
- self.ainfo = getaddrinfo(hostport[0], port, af, SOCK_STREAM)
116
+ if not hostport[0] in (DST_SIP_UA, DST_WSS_UA):
117
+ self.ainfo = getaddrinfo(hostport[0], port, af, SOCK_STREAM)
109
118
  self.params = {}
110
119
  extra_headers = []
111
120
  for a, v in [x.split('=', 1) for x in route[1:]]:
@@ -131,7 +140,9 @@ class B2BRoute(object):
131
140
  elif a == 'forward_on_fail':
132
141
  self.forward_on_fail = True
133
142
  elif a == 'auth':
134
- self.user, self.passw = v.split(':', 1)
143
+ self.pass_auth = False
144
+ if v != 'nopass':
145
+ self.user, self.passw = v.split(':', 1)
135
146
  elif a == 'cli':
136
147
  self.cli = v
137
148
  if len(self.cli) == 0:
@@ -153,6 +164,8 @@ class B2BRoute(object):
153
164
  self.params['outbound_proxy'] = (v, 5060)
154
165
  else:
155
166
  self.params['outbound_proxy'] = (host_port[0], int(host_port[1]))
167
+ elif a == 'po_proc':
168
+ self.params['po_proc'] = getTransProc(v)
156
169
  else:
157
170
  self.params[a] = v
158
171
  if len(extra_headers) > 0:
@@ -0,0 +1,99 @@
1
+ # Copyright (c) 2024 Sippy Software, Inc. All rights reserved.
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ #
11
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation and/or
13
+ # other materials provided with the distribution.
14
+ #
15
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
19
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
22
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
26
+ import sys
27
+
28
+ from sippy.SipRequest import SipRequest
29
+ from sippy.CCEvents import CCEventTry
30
+
31
+ class HDR2Xattrs():
32
+ # Source: https://github.com/sippy/b2bua/pull/38
33
+ # Author: @twmobius
34
+ hdr_name:str
35
+ def __init__(self, hdr_name:str):
36
+ self.hdr_name = hdr_name
37
+
38
+ def __call__(self, cc:'CallController', req:SipRequest):
39
+ hfs = req.getHFs(self.hdr_name)
40
+
41
+ if len(hfs) == 0:
42
+ return
43
+
44
+ extra_attributes = []
45
+
46
+ for header in hfs:
47
+ kvPairs = header.body.body.split(';')
48
+ for pair in kvPairs:
49
+ [key, _, value] = pair.partition("=")
50
+ if value != '':
51
+ extra_attributes.append((key, value))
52
+ if len(extra_attributes) == 0:
53
+ return
54
+ if cc.extra_attributes is None:
55
+ cc.extra_attributes = extra_attributes
56
+ else:
57
+ cc.extra_attributes.extend(extra_attributes)
58
+
59
+ class VAL2Xattrs():
60
+ # Source: https://github.com/sippy/b2bua/pull/39
61
+ # Author: @twmobius
62
+ doO: bool = True
63
+ doA: bool = True
64
+ radius_parameters: list
65
+ def __init__(self, v:str):
66
+ radius_parameters = []
67
+ pairs = v.split(',')
68
+ for pair in pairs:
69
+ [key, _, value] = pair.partition("=")
70
+ if value == '': raise ValueError(f'{v}: comma-separated list of key=value pairs is expected')
71
+ radius_parameters.append((key, value))
72
+ self.radius_parameters = radius_parameters
73
+
74
+ def __call__(self, cc:'CallController', _:CCEventTry):
75
+ if self.doO and cc.acctO is not None:
76
+ cc.acctO.addAttributes(self.radius_parameters)
77
+ if self.doA and cc.acctA is not None:
78
+ cc.acctA.addAttributes(self.radius_parameters)
79
+
80
+ class Nop():
81
+ def __init__(self, v:str): pass
82
+ def __call__(self, *a, **kwa): pass
83
+
84
+ class VAL2XattrsA(VAL2Xattrs): doO = False
85
+ class VAL2XattrsO(VAL2Xattrs): doA = False
86
+
87
+ def getTransProc(value:str):
88
+ rparts = value.split('[', 1)
89
+ if not len(rparts) == 2 or not value.endswith(']'):
90
+ raise ValueError(f'getTransProc: `{value}` should be in the format `function[argument]`')
91
+ fname = rparts[0]
92
+ bts = sys.modules[__name__]
93
+ fclass = getattr(bts, fname)
94
+ farg = rparts[1][:-1]
95
+ return fclass(farg)
96
+
97
+ if __name__ == '__main__':
98
+ for t in ('HDR2Xattrs[X-foo-hdr]', 'VAL2Xattrs[foo=bar,baz=xxx]', 'VAL2XattrsA[foo=bar,baz=xxx]', 'VAL2XattrsO[foo=bar,baz=xxx]'):
99
+ p = getTransProc(t)
@@ -43,6 +43,7 @@ except NameError:
43
43
  _uobj = bytes
44
44
 
45
45
  class _Acceptor(Thread):
46
+ daemon = True
46
47
  clicm = None
47
48
  pollobj = None
48
49
  fileno = None
@@ -53,7 +54,6 @@ class _Acceptor(Thread):
53
54
  self.pollobj = poll()
54
55
  self.fileno = self.clicm.serversock.fileno()
55
56
  self.pollobj.register(self.fileno, POLLIN)
56
- self.setDaemon(True)
57
57
  self.start()
58
58
 
59
59
  def run(self):
@@ -85,7 +85,7 @@ class _Acceptor(Thread):
85
85
  class CLIConnectionManager(object):
86
86
  command_cb = None
87
87
  tcp = False
88
- accept_list = None
88
+ accept_list: list = None
89
89
  serversock = None
90
90
  atr = None
91
91
 
@@ -120,9 +120,10 @@ class CLIConnectionManager(object):
120
120
 
121
121
  def handle_accept(self, conn, address):
122
122
  #print(self.handle_accept)
123
- if self.tcp and self.accept_list != None and address[0] not in self.accept_list:
124
- conn.close()
125
- return
123
+ if self.tcp and self.accept_list is not None:
124
+ if address[0] not in self.accept_list: # pylint: disable=unsupported-membership-test
125
+ conn.close()
126
+ return
126
127
  try:
127
128
  cm = CLIManager(conn, self.command_cb, address)
128
129
  except Exception as e:
@@ -136,6 +137,7 @@ class CLIConnectionManager(object):
136
137
  self.atr.join()
137
138
 
138
139
  class _CLIManager_w(Thread):
140
+ daemon = True
139
141
  clim = None
140
142
  wbuffer = None
141
143
  w_available = None
@@ -148,7 +150,6 @@ class _CLIManager_w(Thread):
148
150
  self.clim = clim
149
151
  self.w_available = Condition()
150
152
  self.wbuffer = bytes()
151
- self.setDaemon(True)
152
153
  self.start()
153
154
 
154
155
  def run(self):
@@ -195,6 +196,7 @@ class _CLIManager_w(Thread):
195
196
  self.w_available.release()
196
197
 
197
198
  class _CLIManager_r(Thread):
199
+ daemon = True
198
200
  clim = None
199
201
 
200
202
  def __init__(self, clientsock, clim):
@@ -202,7 +204,6 @@ class _CLIManager_r(Thread):
202
204
  Thread.__init__(self)
203
205
  self.clientsock = clientsock
204
206
  self.clim = clim
205
- self.setDaemon(True)
206
207
  self.start()
207
208
 
208
209
  def run(self):
@@ -274,7 +275,7 @@ class CLIManager(object):
274
275
  try:
275
276
  self.clientsock.shutdown(socket.SHUT_RDWR)
276
277
  except Exception as e:
277
- if not isinstance(e, socket.error) or e.errno != ENOTCONN:
278
+ if not isinstance(e, socket.error) or e.errno != ENOTCONN: # pylint: disable=no-member
278
279
  dump_exception('self.clientsock.shutdown(socket.SHUT_RDWR)')
279
280
  self.clientsock.close()
280
281
  self.wthr = None
@@ -43,11 +43,6 @@ class EventListener(object):
43
43
  cb_with_ts = False
44
44
  randomize_runs = None
45
45
 
46
- def __cmp__(self, other):
47
- if other == None:
48
- return 1
49
- return cmp(self.etime, other.etime)
50
-
51
46
  def __lt__(self, other):
52
47
  return self.etime < other.etime
53
48
 
@@ -34,7 +34,7 @@ class StdException(Exception):
34
34
 
35
35
  def __init__(self, *args):
36
36
  pin_exception(self, 2)
37
- super(self.__class__, self).__init__(*args)
37
+ super(StdException, self).__init__(*args)
38
38
 
39
39
  def dump_exception(msg, f = sys.stdout, extra = None):
40
40
  exc_type, exc_value, exc_traceback = sys.exc_info()
@@ -36,3 +36,6 @@ class RtpProxyError(Exception):
36
36
 
37
37
  def getResponse(self, req):
38
38
  return SdpParseError.getResponse(self, req)
39
+
40
+ def getReason(self):
41
+ return SdpParseError.getReason(self)
@@ -48,9 +48,13 @@ class SdpParseError(SipParseError):
48
48
  if self.sip_response is not None:
49
49
  return self.sip_response
50
50
  resp = req.genResponse(self.code, self.msg)
51
- if self.arg is not None and len(self.arg) > 0:
52
- reason = SipReason(protocol='SIP', cause=self.code,
53
- reason=self.arg)
51
+ if (reason:=self.getReason()) is not None:
54
52
  rsh = SipHeader(body = reason)
55
53
  resp.appendHeader(rsh)
56
54
  return resp
55
+
56
+ def getReason(self):
57
+ if self.arg is not None and len(self.arg) > 0:
58
+ return SipReason(protocol='SIP', cause=self.code,
59
+ reason=self.arg)
60
+ return None
@@ -36,6 +36,7 @@ from sippy.Core.EventDispatcher import ED2
36
36
  _MAX_WORKERS = 20
37
37
 
38
38
  class _Worker(Thread):
39
+ daemon = True
39
40
  command = None
40
41
  master = None
41
42
 
@@ -43,7 +44,6 @@ class _Worker(Thread):
43
44
  Thread.__init__(self)
44
45
  self.command = command
45
46
  self.master = master
46
- self.setDaemon(True)
47
47
  self.start()
48
48
 
49
49
  def run(self):
@@ -68,10 +68,10 @@ class MsgBody(object):
68
68
  def __str__(self):
69
69
  return str(self.content)
70
70
 
71
- def localStr(self, local_addr = None, local_port = None):
71
+ def localStr(self, local_addr = None):
72
72
  if type(self.content) in str_types:
73
73
  return self.content
74
- return self.content.localStr(local_addr, local_port)
74
+ return self.content.localStr(local_addr)
75
75
 
76
76
  def getType(self):
77
77
  return self.mtype.name.lower()
@@ -30,7 +30,7 @@ class MultipartMixBody():
30
30
  boundary = None
31
31
 
32
32
  def __init__(self, body = None, ctype = None):
33
- if body == None:
33
+ if body is None:
34
34
  return
35
35
  sep = f'--{ctype.params["boundary"]}'
36
36
  bparts = body.split(sep)
@@ -53,9 +53,9 @@ class MultipartMixBody():
53
53
  s += f'{bnd[:-2]}--\r\n'
54
54
  return s
55
55
 
56
- def localStr(self, local_addr = None, local_port = None):
56
+ def localStr(self, local_addr = None):
57
57
  bnd = f'--{self.boundary}\r\n'
58
- parts = [f'{bnd}Content-Type: {p.mtype}\r\n{p.localStr(local_addr, local_port)}'
58
+ parts = [f'{bnd}Content-Type: {p.mtype}\r\n{p.localStr(local_addr)}'
59
59
  for p in self.parts]
60
60
  s = ''.join(parts)
61
61
  s += f'{bnd[:-2]}--\r\n'
@@ -33,6 +33,7 @@ except ImportError:
33
33
  from configparser import RawConfigParser
34
34
  _boolean_states = RawConfigParser.BOOLEAN_STATES
35
35
  from sippy.SipConf import SipConf
36
+ from sippy.B2BTransforms import getTransProc
36
37
 
37
38
  SUPPORTED_OPTIONS = { \
38
39
  'acct_enable': ('B', 'enable or disable Radius accounting'), \
@@ -98,6 +99,13 @@ SUPPORTED_OPTIONS = { \
98
99
  'and "SUBSCRIBE" messages. Address in the format ' \
99
100
  '"host[:port]"'),
100
101
  'nat_traversal': ('B', 'enable NAT traversal for signalling'), \
102
+ 'wss_socket': ('S', 'WSS (SIP via websockets, RFC7118) socket ' \
103
+ 'configuration. Configuration in the format ' \
104
+ '"host:port:cert_file:key_file", where "cert_file" ' \
105
+ '/ "key_file" are paths to the TLS certificate ' \
106
+ 'and key file respectively in the X.509 PEM format'),
107
+ 'pre_auth_proc': ('S', 'internal routine to be executed before authentication '\
108
+ 'is being processed. E.g. "HDR2Xattrs[X-foo-hdr]."'), \
101
109
  'xmpp_b2bua_id': ('I', 'ID passed to the XMPP socket server')}
102
110
 
103
111
  class MyConfigParser(RawConfigParser):
@@ -193,7 +201,8 @@ class MyConfigParser(RawConfigParser):
193
201
  if _value <= 0:
194
202
  raise ValueError('max_credit_time should be more than zero')
195
203
  elif key == 'allowed_pts':
196
- self['_allowed_pts'] = [int(x) for x in value.split(',')]
204
+ self['_allowed_pts'] = [int(x) if not (x.startswith('[') and x.endswith(']')) \
205
+ else x[1:-1] for x in value.split(',')]
197
206
  elif key in ('accept_ips', 'rtp_proxy_clients'):
198
207
  self['_' + key] = [x.strip() for x in value.split(',')]
199
208
  elif key == 'pass_headers':
@@ -210,6 +219,8 @@ class MyConfigParser(RawConfigParser):
210
219
  if _value <= 0 or _value > 65535:
211
220
  raise ValueError('sip_port should be in the range 1-65535')
212
221
  self['_sip_port'] = _value
222
+ elif key == 'pre_auth_proc':
223
+ self['_pre_auth_proc'] = getTransProc(value)
213
224
  self[key] = value
214
225
 
215
226
  def options_help(self):
@@ -0,0 +1,150 @@
1
+ # Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
2
+ # Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved.
3
+ #
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without modification,
7
+ # are permitted provided that the following conditions are met:
8
+ #
9
+ # 1. Redistributions of source code must retain the above copyright notice, this
10
+ # list of conditions and the following disclaimer.
11
+ #
12
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation and/or
14
+ # other materials provided with the distribution.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
20
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
+
27
+ from typing import Optional, Tuple, List, Union
28
+ from queue import Queue
29
+ from abc import ABC, abstractmethod
30
+ from random import random, randint
31
+
32
+ from sippy.Core.Exceptions import dump_exception
33
+ from sippy.Time.MonoTime import MonoTime
34
+ from sippy.Time.Timeout import Timeout
35
+ from sippy.SipConf import MyPort
36
+
37
+ class Remote_address():
38
+ transport: str
39
+ address: Tuple[str, int]
40
+ received: str
41
+
42
+ def __init__(self, address:Tuple[str, int], transport:str):
43
+ self.transport = transport
44
+ self.address = address
45
+ self.received = address[0]
46
+
47
+ def __str__(self):
48
+ return f'{self.transport}:{self.address[0]}:{self.address[1]}'
49
+
50
+ class Network_server_opts():
51
+ laddress: Optional[Tuple[str, Union[int, callable, MyPort]]] = None
52
+ data_callback: Optional[callable] = None
53
+ direct_dispatch: bool = False
54
+ ploss_out_rate: float = 0.0
55
+ pdelay_out_max: float = 0.0
56
+ ploss_in_rate: float = 0.0
57
+ pdelay_in_max: float = 0.0
58
+
59
+ def __init__(self, *args:Optional[Tuple[str, int]], o:Optional['Network_server_opts'] = None):
60
+ if o != None:
61
+ self.laddress, self.data_callback, self.direct_dispatch, \
62
+ self.ploss_out_rate, self.pdelay_out_max, self.ploss_in_rate, \
63
+ self.pdelay_in_max = o.laddress, o.data_callback, o.direct_dispatch, \
64
+ o.ploss_out_rate, o.pdelay_out_max, o.ploss_in_rate, o.pdelay_in_max
65
+ return
66
+ self.laddress, self.data_callback = args
67
+
68
+ def getCopy(self) -> 'Network_server_opts':
69
+ return self.__class__(o = self)
70
+
71
+ def isWildCard(self) -> bool:
72
+ return False
73
+
74
+ class Network_server(ABC):
75
+ transport: str
76
+ uopts: Network_server_opts
77
+ sendqueue: Queue
78
+ stats: List[int]
79
+
80
+ def __init__(self, uopts:Network_server_opts):
81
+ self.uopts = uopts.getCopy()
82
+ self.sendqueue = Queue()
83
+ self.stats = [0, 0, 0]
84
+
85
+ def getSIPaddr(self) -> Tuple[Tuple[str, int], int]:
86
+ return (self.uopts.laddress, self.transport)
87
+
88
+ def addr2str(self, address):
89
+ return f'{self.transport}:{address[0]}:{address[1]}'
90
+
91
+ def send_to(self, data:Union[bytes, str], address:object, delayed:bool = False):
92
+ if not isinstance(data, bytes):
93
+ data = data.encode('utf-8')
94
+ if self.uopts.ploss_out_rate > 0.0 and not delayed:
95
+ if random() < self.uopts.ploss_out_rate:
96
+ return
97
+ if self.uopts.pdelay_out_max > 0.0 and not delayed:
98
+ pdelay = self.uopts.pdelay_out_max * random()
99
+ Timeout(self.send_to, pdelay, 1, data, address, True)
100
+ return
101
+ self.sendqueue.put((data, address))
102
+
103
+ def handle_read(self, data:bytes, address:Remote_address, rtime:MonoTime, delayed:bool = False):
104
+ if len(data) > 0 and self.uopts.data_callback != None:
105
+ self.stats[2] += 1
106
+ if self.uopts.ploss_in_rate > 0.0 and not delayed:
107
+ if random() < self.uopts.ploss_in_rate:
108
+ return
109
+ if self.uopts.pdelay_in_max > 0.0 and not delayed:
110
+ pdelay = self.uopts.pdelay_in_max * random()
111
+ Timeout(self.handle_read, pdelay, 1, data, address, rtime.getOffsetCopy(pdelay), True)
112
+ return
113
+ try:
114
+ self.uopts.data_callback(data, address, self, rtime)
115
+ except Exception as ex:
116
+ if isinstance(ex, SystemExit):
117
+ raise
118
+ dump_exception(f'{self.__class__}: unhandled exception when processing incoming data')
119
+
120
+ @abstractmethod
121
+ def join(self):
122
+ pass
123
+
124
+ def shutdown(self):
125
+ self.sendqueue.put(None)
126
+ self.join()
127
+ self.uopts.data_callback = None
128
+
129
+ class PortAllocationError(Exception): pass
130
+
131
+ class RTP_port_allocator():
132
+ min_port: int
133
+ max_port: int
134
+
135
+ def __init__(self, min_port:Optional[int]=None, max_port:Optional[int]=None):
136
+ if min_port is None: min_port = 1024
137
+ if max_port is None: max_port = 65535
138
+ if min_port % 2 != 0:
139
+ min_port += 1
140
+ assert min_port <= max_port, f'min_port={min_port} > max_port={max_port}'
141
+ self.min_port = min_port
142
+ self.max_port = max_port
143
+
144
+ def __call__(self, ntry: int) -> int:
145
+ rlen = self.max_port - self.min_port
146
+ if ntry > (rlen // 2):
147
+ raise PortAllocationError(f'No free ports available after {ntry} tries')
148
+ port = self.min_port + (randint(0, rlen // 2) * 2)
149
+ assert port <= self.max_port, f'port={port} > self.max_port={self.max_port}'
150
+ return port
@@ -188,3 +188,7 @@ class RadiusAccounting(object):
188
188
  else:
189
189
  message = 'Error sending Acct/%s request (delay is %.3f)\n' % (self.origin, delay)
190
190
  self.global_config['_sip_logger'].write(message, call_id = sip_cid)
191
+
192
+ def addAttributes(self, attributes):
193
+ self._attributes.extend(tuple(x) for x in attributes \
194
+ if tuple(x) not in self._attributes)