SIPClient.invite() crashes with SIPParseError if an OPTIONS request arrives mid-transaction (master/1.6.x)
Summary
pyVoIP.SIPCompatibleMethods (in pyVoIP/__init__.py) is ["INVITE", "ACK", "BYE", "CANCEL"] -- it doesn't include OPTIONS. SIPMessage.parse() raises SIPParseError for any request whose method isn't in that list.
SIPClient.recv() (the background receive loop) happens to survive this, since its except Exception in recv_loop/recv swallows the parse error. But SIPClient.invite()'s own blocking response-read loop does not:
with self.recvLock:
self.out.sendto(invite.encode("utf8"), (self.server, self.port))
response = SIPMessage(self.s.recv(8192)) # <-- no try/except here
while (...):
...
response = SIPMessage(self.s.recv(8192)) # <-- or here
If a server sends an unsolicited OPTIONS request (e.g. Asterisk's periodic qualify keepalive to a registered contact) at the exact moment invite() is blocked reading the INVITE transaction's response, that OPTIONS packet gets read by invite() instead of the background loop, SIPMessage() raises SIPParseError, and the exception propagates uncaught out of invite(), crashing the entire call attempt.
Reproduction
Register against any PBX that sends periodic OPTIONS to registered contacts (Asterisk with qualify_frequency set on the endpoint is the common case -- this is a default/common PJSIP endpoint setting), then place an outbound call. If the timing lines up, the call crashes instead of completing:
pyVoIP.SIP.SIPParseError: Unable to decipher SIP request: OPTIONS sip:101@x.x.x.x:5060 SIP/2.0
Confirmed live against a real Asterisk 22 / FreePBX 17 PBX, reproduced from a PBX-side pjsip set logger on trace showing the OPTIONS ping landing during the INVITE transaction window.
Related
#73 covers adding full OPTIONS support, and per the thread that's been done in development for 2.0. This issue is scoped more narrowly to master/1.6.x, which is still what's on PyPI today: it doesn't need full OPTIONS handling, just needs to not crash when one arrives. SIPClient.parse_message() already has a graceful fallback for unrecognized methods once parsing succeeds (else: debug("TODO: Add 400 Error on non processable request")), it's only SIPMessage.parse() itself that hard-fails first.
Proposed fix
Add "OPTIONS" to SIPCompatibleMethods so it parses via the generic message path instead of raising. I have this change ready as a small PR if useful.
SIPClient.invite()crashes withSIPParseErrorif an OPTIONS request arrives mid-transaction (master/1.6.x)Summary
pyVoIP.SIPCompatibleMethods(inpyVoIP/__init__.py) is["INVITE", "ACK", "BYE", "CANCEL"]-- it doesn't includeOPTIONS.SIPMessage.parse()raisesSIPParseErrorfor any request whose method isn't in that list.SIPClient.recv()(the background receive loop) happens to survive this, since itsexcept Exceptioninrecv_loop/recvswallows the parse error. ButSIPClient.invite()'s own blocking response-read loop does not:If a server sends an unsolicited OPTIONS request (e.g. Asterisk's periodic qualify keepalive to a registered contact) at the exact moment
invite()is blocked reading the INVITE transaction's response, that OPTIONS packet gets read byinvite()instead of the background loop,SIPMessage()raisesSIPParseError, and the exception propagates uncaught out ofinvite(), crashing the entire call attempt.Reproduction
Register against any PBX that sends periodic OPTIONS to registered contacts (Asterisk with
qualify_frequencyset on the endpoint is the common case -- this is a default/common PJSIP endpoint setting), then place an outbound call. If the timing lines up, the call crashes instead of completing:Confirmed live against a real Asterisk 22 / FreePBX 17 PBX, reproduced from a PBX-side
pjsip set logger ontrace showing the OPTIONS ping landing during the INVITE transaction window.Related
#73 covers adding full OPTIONS support, and per the thread that's been done in
developmentfor 2.0. This issue is scoped more narrowly to master/1.6.x, which is still what's on PyPI today: it doesn't need full OPTIONS handling, just needs to not crash when one arrives.SIPClient.parse_message()already has a graceful fallback for unrecognized methods once parsing succeeds (else: debug("TODO: Add 400 Error on non processable request")), it's onlySIPMessage.parse()itself that hard-fails first.Proposed fix
Add
"OPTIONS"toSIPCompatibleMethodsso it parses via the generic message path instead of raising. I have this change ready as a small PR if useful.