From cfcda2714f5e0aafd878c8f6bd12e5f643943dad Mon Sep 17 00:00:00 2001 From: Fedorov Volodymyr Date: Tue, 8 Sep 2026 10:17:26 +0200 Subject: [PATCH 1/3] reg_agent: keep Expires: 0 on de-REGISTER with contact doUnregister() overwrote the "Expires: 0" header with the Contact header instead of appending to it, so any de-registration for a subscriber with a contact set left the outgoing REGISTER without an Expires header at all. The registrar then treated it as a normal refresh instead of removing the binding, while SEMS locally deleted its registration record, leaving the two sides out of sync. --- core/AmSipRegistration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/AmSipRegistration.cpp b/core/AmSipRegistration.cpp index 8bd29f41..84071353 100644 --- a/core/AmSipRegistration.cpp +++ b/core/AmSipRegistration.cpp @@ -163,7 +163,7 @@ bool AmSIPRegistration::doUnregister() int flags=0; string hdrs = SIP_HDR_COLSP(SIP_HDR_EXPIRES) "0" CRLF; if(!info.contact.empty()) { - hdrs = SIP_HDR_COLSP(SIP_HDR_CONTACT) "<"; + hdrs += SIP_HDR_COLSP(SIP_HDR_CONTACT) "<"; hdrs += info.contact + ">" + CRLF; flags = SIP_FLAGS_NOCONTACT; } From b43c6ca9fb7ae2e5deb5d8fbfac6c40fd439f3cb Mon Sep 17 00:00:00 2001 From: Fedorov Volodymyr Date: Tue, 8 Sep 2026 10:30:15 +0200 Subject: [PATCH 2/3] RegisterCache: fix missing climits include for LONG_MAX get_lowest_expire() uses LONG_MAX without including , which fails to build with newer/stricter compilers. Co-Authored-By: Claude Sonnet 5 --- apps/sbc/RegisterCache.h | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sbc/RegisterCache.h b/apps/sbc/RegisterCache.h index 43c6ec0e..22d94dc1 100644 --- a/apps/sbc/RegisterCache.h +++ b/apps/sbc/RegisterCache.h @@ -9,6 +9,7 @@ #include "AmUriParser.h" #include "AmAppTimer.h" +#include #include #include #include From 1875cdd10aca5166dc36b93d80f5327177e24ff8 Mon Sep 17 00:00:00 2001 From: Vadim Saranov Date: Wed, 9 Sep 2026 10:37:48 +0300 Subject: [PATCH 3/3] core: add SIP_FLAGS_NO_SDP_GENERATION for relayed messages When a B2BUA relays a message (sendRelay/reply with the body taken from the other leg), the offer/answer state machine could still decide to generate an SDP body for it - e.g. force SDP on a 2xx while the state is OfferRecved, or re-use the saved local SDP for a sequential 183 without SDP. A relayed message must be sent as-is SDP-wise: only endpoints in an existing body are updated (updateLocalBody), never a new body generated. Add SIP_FLAGS_NO_SDP_GENERATION (passed by AmB2BSession for relayed requests and replies) and thread it through AmSipDialog into AmOfferAnswer::onRequestOut()/onReplyOut(), where it suppresses all SDP-generation decisions, including the 183 re-use of the saved local SDP. Without the flag, behavior is unchanged. Signed-off-by: Vadim Saranov --- core/AmB2BSession.cpp | 17 +++++++++++++++-- core/AmBasicSipDialog.h | 5 +++++ core/AmOfferAnswer.cpp | 16 +++++++++------- core/AmOfferAnswer.h | 3 ++- core/AmSipDialog.cpp | 5 +++-- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/core/AmB2BSession.cpp b/core/AmB2BSession.cpp index 5466c698..cdcd62e0 100644 --- a/core/AmB2BSession.cpp +++ b/core/AmB2BSession.cpp @@ -1269,6 +1269,12 @@ int AmB2BSession::relaySip(const AmSipRequest& req) updateLocalBody(body); } + /* a relayed message is sent as-is (SDP-wise): never generate an SDP body + * for it, only update endpoints in an existing one (updateLocalBody) */ + int relay_flags = SIP_FLAGS_VERBATIM; + if (!body.hasContentType(SIP_APPLICATION_SDP)) + relay_flags |= SIP_FLAGS_NO_SDP_GENERATION; + /* all methods apart ACK */ if (req.method != "ACK") { relayed_req[dlg->cseq] = req; @@ -1303,7 +1309,7 @@ int AmB2BSession::relaySip(const AmSipRequest& req) ILOG_DLG(L_DBG, "relaying SIP request %s %s\n", req.method.c_str(), req.r_uri.c_str()); - int err = dlg->sendRequest(req.method, &body, *hdrs, SIP_FLAGS_VERBATIM); + int err = dlg->sendRequest(req.method, &body, *hdrs, relay_flags); if(err < 0){ ILOG_DLG(L_ERR, "dlg->sendRequest() failed\n"); @@ -1336,7 +1342,7 @@ int AmB2BSession::relaySip(const AmSipRequest& req) ILOG_DLG(L_DBG, "sending relayed 200 ACK\n"); - int err = dlg->send_200_ack(t->first, &body, req.hdrs, SIP_FLAGS_VERBATIM); + int err = dlg->send_200_ack(t->first, &body, req.hdrs, relay_flags); if(err < 0) { ILOG_DLG(L_ERR, "dlg->send_200_ack() failed\n"); return err; @@ -1382,6 +1388,13 @@ int AmB2BSession::relaySip(const AmSipRequest& orig, const AmSipReply& reply) if(reply.to_tag.empty()) flags |= SIP_FLAGS_NOTAG; + /* a relayed message is sent as-is (SDP-wise): never generate an SDP body + * for it, only update endpoints in an existing one (updateLocalBody). + * IMS preconditions: the O/A is completed in 183+PRACK, the final 200 OK + * has no body and must not get one. */ + if (!body.hasContentType(SIP_APPLICATION_SDP)) + flags |= SIP_FLAGS_NO_SDP_GENERATION; + int err = dlg->reply(orig,reply.code,reply.reason, &body, *hdrs, flags); diff --git a/core/AmBasicSipDialog.h b/core/AmBasicSipDialog.h index bff1029b..2c0a5f4d 100644 --- a/core/AmBasicSipDialog.h +++ b/core/AmBasicSipDialog.h @@ -47,6 +47,11 @@ using std::string; #define SIP_FLAGS_SAVE_ESTB_SDP 1<<10 // save established SDP body when processing in OA, see `AmOfferAnswer::onReplyOut()` +#define SIP_FLAGS_NO_SDP_GENERATION 1<<11 // do not generate an SDP body for this message, + // even if the O/A state would require one. Used for + // relayed messages and explicit no-SDP replies. + + /** \brief SIP transaction representation */ struct AmSipTransaction { diff --git a/core/AmOfferAnswer.cpp b/core/AmOfferAnswer.cpp index 39faf048..af7b9312 100644 --- a/core/AmOfferAnswer.cpp +++ b/core/AmOfferAnswer.cpp @@ -408,16 +408,17 @@ int AmOfferAnswer::onTxSdp(unsigned int m_cseq, return 0; } -int AmOfferAnswer::onRequestOut(AmSipRequest& req) +int AmOfferAnswer::onRequestOut(AmSipRequest& req, bool no_sdp_generation) { AmMimeBody* sdp_body = req.body.hasContentType(SIP_APPLICATION_SDP); AmMimeBody* csta_body = req.body.hasContentType(SIP_APPLICATION_CSTA_XML); - bool generate_sdp = sdp_body && !sdp_body->getLen(); + bool generate_sdp = !no_sdp_generation && sdp_body && !sdp_body->getLen(); bool has_sdp = sdp_body && sdp_body->getLen(); bool has_csta = csta_body && csta_body->getLen(); - if ((!sdp_body && !csta_body) && + if (!no_sdp_generation && + (!sdp_body && !csta_body) && ((req.method == SIP_METH_PRACK) || (req.method == SIP_METH_ACK)) && (state == OA_OfferRecved)) { @@ -464,7 +465,7 @@ int AmOfferAnswer::onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_bod * or 183 with no SDP body */ bool force_no_sdp_update = false; - bool generate_sdp = sdp_body && !sdp_body->getLen(); + bool generate_sdp = !no_sdp_generation && sdp_body && !sdp_body->getLen(); bool has_sdp = sdp_body && sdp_body->getLen(); bool has_csta = csta_body && csta_body->getLen(); @@ -494,8 +495,9 @@ int AmOfferAnswer::onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_bod /* TT#184101, a sequential 183, which has no SDP body, but the media session has already had the local SDP and saved that. - Re-use it, and do not beget the 183 without SDP body */ - if (reply.code == 183 && !sdp_local.media.empty()) { + Re-use it, and do not beget the 183 without SDP body. + Not when SDP generation is suppressed for this reply (relayed messages). */ + if (reply.code == 183 && !no_sdp_generation && !sdp_local.media.empty()) { ILOG_DLG(L_DBG, "The 183 with no SDP, but system already has local SDP for this session, re-using it..\n"); string existing_sdp; @@ -536,7 +538,7 @@ int AmOfferAnswer::onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_bod if ((reply.code >= 200) && (reply.code < 300)) { /* offer received: -> force SDP */ - generate_sdp = (state == OA_OfferRecved); + generate_sdp = !no_sdp_generation && (state == OA_OfferRecved); ILOG_DLG(L_DBG, "Now generate_sdp has been reset to <%c>.\n", generate_sdp ? 't' : 'f'); } } diff --git a/core/AmOfferAnswer.h b/core/AmOfferAnswer.h index eea096a5..54b0cd93 100644 --- a/core/AmOfferAnswer.h +++ b/core/AmOfferAnswer.h @@ -93,7 +93,8 @@ public: /** Event handlers */ int onRequestIn(const AmSipRequest& req); int onReplyIn(const AmSipReply& reply); - int onRequestOut(AmSipRequest& req); + /* `no_sdp_generation` - suppress SDP generation (relayed messages, explicit no-SDP replies) */ + int onRequestOut(AmSipRequest& req, bool no_sdp_generation = false); /* `no_sdp_generation` - can be used for cases like absent SDP in coming INVITE */ int onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_body, bool no_sdp_generation = false); int onRequestSent(const AmSipRequest& req); diff --git a/core/AmSipDialog.cpp b/core/AmSipDialog.cpp index 1cc43638..acfd7b3c 100644 --- a/core/AmSipDialog.cpp +++ b/core/AmSipDialog.cpp @@ -258,7 +258,7 @@ int AmSipDialog::onTxRequest(AmSipRequest& req, int& flags) { rel100.onRequestOut(req); - if (offeranswer_enabled && oa.onRequestOut(req)) + if (offeranswer_enabled && oa.onRequestOut(req, flags & SIP_FLAGS_NO_SDP_GENERATION)) return -1; if(AmBasicSipDialog::onTxRequest(req,flags) < 0) @@ -286,7 +286,8 @@ int AmSipDialog::onTxReply(const AmSipRequest& req, AmSipReply& reply, int& flag { if (offeranswer_enabled) { AmMimeBody sdp_body; - if(oa.onReplyOut(reply, flags, sdp_body, req.body.empty()) < 0) + bool no_sdp_generation = req.body.empty() || (flags & SIP_FLAGS_NO_SDP_GENERATION); + if(oa.onReplyOut(reply, flags, sdp_body, no_sdp_generation) < 0) return -1; /* if generated by OA, save it */