inc_sip.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. #
  2. from socket import *
  3. import re
  4. import random
  5. import time
  6. import sys
  7. import inc_cfg as cfg
  8. from select import *
  9. # SIP request template
  10. req_templ = \
  11. """$METHOD $TARGET_URI SIP/2.0\r
  12. Via: SIP/2.0/UDP $LOCAL_IP:$LOCAL_PORT;rport;branch=z9hG4bK$BRANCH\r
  13. Max-Forwards: 70\r
  14. From: <sip:caller@pjsip.org>$FROM_TAG\r
  15. To: <$TARGET_URI>$TO_TAG\r
  16. Contact: <sip:$LOCAL_IP:$LOCAL_PORT;transport=udp>\r
  17. Call-ID: $CALL_ID@pjsip.org\r
  18. CSeq: $CSEQ $METHOD\r
  19. Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, REFER\r
  20. Supported: replaces, 100rel, norefersub\r
  21. User-Agent: pjsip.org Python tester\r
  22. Content-Length: $CONTENT_LENGTH\r
  23. $SIP_HEADERS"""
  24. def is_request(msg):
  25. return msg.split(" ", 1)[0] != "SIP/2.0"
  26. def is_response(msg):
  27. return msg.split(" ", 1)[0] == "SIP/2.0"
  28. def get_code(msg):
  29. if msg=="":
  30. return 0
  31. return int(msg.split(" ", 2)[1])
  32. def get_tag(msg, hdr="To"):
  33. pat = "^" + hdr + ":.*"
  34. result = re.search(pat, msg, re.M | re.I)
  35. if result==None:
  36. return ""
  37. line = result.group()
  38. #print "line=", line
  39. tags = line.split(";tag=")
  40. if len(tags)>1:
  41. return tags[1]
  42. return ""
  43. #return re.split("[;& ]", s)
  44. def get_header(msg, hname):
  45. headers = msg.splitlines()
  46. for hdr in headers:
  47. hfields = hdr.split(": ", 2)
  48. if hfields[0]==hname:
  49. return hfields[1]
  50. return None
  51. class Dialog:
  52. sock = None
  53. dst_addr = ""
  54. dst_port = 5060
  55. local_ip = ""
  56. local_port = 0
  57. tcp = False
  58. call_id = str(random.random())
  59. cseq = 0
  60. local_tag = ";tag=" + str(random.random())
  61. rem_tag = ""
  62. last_resp_code = 0
  63. inv_branch = ""
  64. trace_enabled = True
  65. last_request = ""
  66. def __init__(self, dst_addr, dst_port=5060, tcp=False, trace=True, local_port=0):
  67. self.dst_addr = dst_addr
  68. self.dst_port = dst_port
  69. self.tcp = tcp
  70. self.trace_enabled = trace
  71. if tcp==True:
  72. self.sock = socket(AF_INET, SOCK_STREAM)
  73. self.sock.connect(dst_addr, dst_port)
  74. else:
  75. self.sock = socket(AF_INET, SOCK_DGRAM)
  76. self.sock.bind(("127.0.0.1", local_port))
  77. self.local_ip, self.local_port = self.sock.getsockname()
  78. self.trace("Dialog socket bound to " + self.local_ip + ":" + str(self.local_port))
  79. def trace(self, txt):
  80. if self.trace_enabled:
  81. try:
  82. print(str(time.strftime("%H:%M:%S ")) + txt)
  83. except UnicodeEncodeError:
  84. print((str(time.strftime("%H:%M:%S ")) + txt).encode('utf-8'))
  85. def update_fields(self, msg):
  86. if self.tcp:
  87. transport_param = ";transport=tcp"
  88. else:
  89. transport_param = ""
  90. msg = msg.replace("$TARGET_URI", "sip:"+self.dst_addr+":"+str(self.dst_port) + transport_param)
  91. msg = msg.replace("$LOCAL_IP", self.local_ip)
  92. msg = msg.replace("$LOCAL_PORT", str(self.local_port))
  93. msg = msg.replace("$FROM_TAG", self.local_tag)
  94. msg = msg.replace("$TO_TAG", self.rem_tag)
  95. msg = msg.replace("$CALL_ID", self.call_id)
  96. msg = msg.replace("$CSEQ", str(self.cseq))
  97. branch=str(random.random())
  98. msg = msg.replace("$BRANCH", branch)
  99. return msg
  100. def create_req(self, method, sdp, branch="", extra_headers="", body=""):
  101. if branch=="":
  102. self.cseq = self.cseq + 1
  103. msg = req_templ
  104. msg = msg.replace("$METHOD", method)
  105. msg = msg.replace("$SIP_HEADERS", extra_headers)
  106. if branch=="":
  107. branch=str(random.random())
  108. msg = msg.replace("$BRANCH", branch)
  109. if sdp!="":
  110. msg = msg.replace("$CONTENT_LENGTH", str(len(sdp)))
  111. msg = msg + "Content-Type: application/sdp\r\n"
  112. msg = msg + "\r\n"
  113. msg = msg + sdp
  114. elif body!="":
  115. msg = msg.replace("$CONTENT_LENGTH", str(len(body)))
  116. msg = msg + "\r\n"
  117. msg = msg + body
  118. else:
  119. msg = msg.replace("$CONTENT_LENGTH", "0")
  120. return self.update_fields(msg)
  121. def create_response(self, request, code, reason, to_tag=""):
  122. response = "SIP/2.0 " + str(code) + " " + reason + "\r\n"
  123. lines = request.splitlines()
  124. for line in lines:
  125. hdr = line.split(":", 1)[0]
  126. if hdr in ["Via", "From", "To", "CSeq", "Call-ID"]:
  127. if hdr=="To" and to_tag!="":
  128. line = line + ";tag=" + to_tag
  129. elif hdr=="Via":
  130. line = line + ";received=127.0.0.1"
  131. response = response + line + "\r\n"
  132. return response
  133. def create_invite(self, sdp, extra_headers="", body=""):
  134. self.inv_branch = str(random.random())
  135. return self.create_req("INVITE", sdp, branch=self.inv_branch, extra_headers=extra_headers, body=body)
  136. def create_ack(self, sdp="", extra_headers=""):
  137. return self.create_req("ACK", sdp, extra_headers=extra_headers, branch=self.inv_branch)
  138. def create_bye(self, extra_headers=""):
  139. return self.create_req("BYE", "", extra_headers)
  140. def send_msg(self, msg, dst_addr=None):
  141. if (is_request(msg)):
  142. self.last_request = msg.split(" ", 1)[0]
  143. if not dst_addr:
  144. dst_addr = (self.dst_addr, self.dst_port)
  145. self.trace("============== TX MSG to " + str(dst_addr) + " ============= \n" + msg)
  146. self.sock.sendto(msg.encode('utf-8'), 0, dst_addr)
  147. def wait_msg_from(self, timeout):
  148. endtime = time.time() + timeout
  149. msg = ""
  150. src_addr = None
  151. while time.time() < endtime:
  152. readset = select([self.sock], [], [], 1)
  153. if len(readset[0]) < 1 or not self.sock in readset[0]:
  154. if len(readset[0]) < 1:
  155. print("select() timeout (will wait for " + str(int(endtime - time.time())) + "more secs)")
  156. elif not self.sock in readset[0]:
  157. print("select() alien socket")
  158. else:
  159. print("select other error")
  160. continue
  161. try:
  162. msg, src_addr = self.sock.recvfrom(4096)
  163. break
  164. except:
  165. print("recv() exception: ", sys.exc_info()[0])
  166. continue
  167. msgstr = msg.decode('utf-8')
  168. if msgstr=="":
  169. return "", None
  170. if self.last_request=="INVITE" and self.rem_tag=="":
  171. self.rem_tag = get_tag(msgstr, "To")
  172. self.rem_tag = self.rem_tag.rstrip("\r\n;")
  173. if self.rem_tag != "":
  174. self.rem_tag = ";tag=" + self.rem_tag
  175. self.trace("=== rem_tag:" + self.rem_tag)
  176. self.trace("=========== RX MSG from " + str(src_addr) + " ===========\n" + msgstr)
  177. return (msgstr, src_addr)
  178. def wait_msg(self, timeout):
  179. return self.wait_msg_from(timeout)[0]
  180. # Send request and wait for final response
  181. def send_request_wait(self, msg, timeout):
  182. t1 = 1.0
  183. endtime = time.time() + timeout
  184. resp = ""
  185. code = 0
  186. for i in range(0,5):
  187. self.send_msg(msg)
  188. resp = self.wait_msg(t1)
  189. if resp!="" and is_response(resp):
  190. code = get_code(resp)
  191. break
  192. last_resp = resp
  193. while code < 200 and time.time() < endtime:
  194. resp = self.wait_msg(endtime - time.time())
  195. if resp != "" and is_response(resp):
  196. code = get_code(resp)
  197. last_resp = resp
  198. elif resp=="":
  199. break
  200. return last_resp
  201. def hangup(self, last_code=0):
  202. self.trace("====== hangup =====")
  203. if last_code!=0:
  204. self.last_resp_code = last_code
  205. if self.last_resp_code>0 and self.last_resp_code<200:
  206. msg = self.create_req("CANCEL", "", branch=self.inv_branch, extra_headers="")
  207. self.send_request_wait(msg, 5)
  208. msg = self.create_ack()
  209. self.send_msg(msg)
  210. elif self.last_resp_code>=200 and self.last_resp_code<300:
  211. msg = self.create_ack()
  212. self.send_msg(msg)
  213. msg = self.create_bye()
  214. self.send_request_wait(msg, 5)
  215. else:
  216. msg = self.create_ack()
  217. self.send_msg(msg)
  218. class SendtoCfg:
  219. # Test name
  220. name = ""
  221. # pjsua InstanceParam
  222. inst_param = None
  223. # Complete INVITE message. If this is not empty, then this
  224. # message will be sent instead and the "sdp" and "extra_headers"
  225. # settings will be ignored.
  226. complete_msg = ""
  227. # Initial SDP
  228. sdp = ""
  229. # Extra headers to add to request
  230. extra_headers = ""
  231. # Expected code
  232. resp_code = 0
  233. # Use TCP?
  234. use_tcp = False
  235. # List of RE patterns that must exist in response
  236. resp_include = []
  237. # List of RE patterns that must NOT exist in response
  238. resp_exclude = []
  239. # Full (non-SDP) body
  240. body = ""
  241. # Constructor
  242. def __init__(self, name, pjsua_args, sdp, resp_code,
  243. resp_inc=[], resp_exc=[], use_tcp=False,
  244. extra_headers="", body="", complete_msg="",
  245. enable_buffer = False):
  246. self.complete_msg = complete_msg
  247. self.sdp = sdp
  248. self.resp_code = resp_code
  249. self.resp_include = resp_inc
  250. self.resp_exclude = resp_exc
  251. self.use_tcp = use_tcp
  252. self.extra_headers = extra_headers
  253. self.body = body
  254. self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
  255. self.inst_param.enable_buffer = enable_buffer
  256. class RecvfromTransaction:
  257. # The test title for this transaction
  258. title = ""
  259. # Optinal list of pjsua command and optional expect patterns
  260. # to be invoked to make pjsua send a request
  261. # Sample:
  262. # (to make call and wait for INVITE to be sent)
  263. # cmds = [ ["m"], ["sip:127.0.0.1", "INVITE sip:"] ]
  264. cmds = []
  265. # Check if the CSeq must be greater than last Cseq?
  266. check_cseq = True
  267. # List of RE patterns that must exists in incoming request
  268. include = []
  269. # List of RE patterns that MUST NOT exist in incoming request
  270. exclude = []
  271. # Response code to send
  272. resp_code = 0
  273. # Additional list of headers to be sent on the response
  274. # Note: no need to add CRLF on the header
  275. resp_hdr = []
  276. # Message body. This should include the Content-Type header too.
  277. # Sample:
  278. # body = """Content-Type: application/sdp\r\n
  279. # \r\n
  280. # v=0\r\n
  281. # ...
  282. # """
  283. body = None
  284. # Pattern to be expected on pjsua when receiving the response
  285. expect = ""
  286. # Required config
  287. pj_config = ""
  288. def __init__(self, title, resp_code, check_cseq=True,
  289. include=[], exclude=[], cmds=[], resp_hdr=[], resp_body=None, expect="", pj_config=""):
  290. self.title = title
  291. self.cmds = cmds
  292. self.include = include
  293. self.exclude = exclude
  294. self.resp_code = resp_code
  295. self.resp_hdr = resp_hdr
  296. self.body = resp_body
  297. self.expect = expect
  298. self.pj_config=pj_config
  299. class RecvfromCfg:
  300. # Test name
  301. name = ""
  302. # pjsua InstanceParam
  303. inst_param = None
  304. # List of RecvfromTransaction
  305. transaction = None
  306. # Use TCP?
  307. tcp = False
  308. # Required config
  309. pj_config = ""
  310. # Note:
  311. # Any "$PORT" string in the pjsua_args will be replaced
  312. # by server port
  313. def __init__(self, name, pjsua_args, transaction, tcp=False, pj_config=""):
  314. self.name = name
  315. self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
  316. self.transaction = transaction
  317. self.tcp=tcp
  318. self.pj_config=pj_config