diff --git a/configure b/configure index c5d0270d4b..5124d0fdaa 100755 --- a/configure +++ b/configure @@ -3936,8 +3936,8 @@ ac_cxx_conftest_cxx11_main=' } { // Unicode literals - auto const *utf8 = u8"UTF-8 string \u2500"; - char16_t const *utf16 = u"UTF-16 string \u2500"; + char const *utf8 = u8"UTF-8 string \u2500"; + char16_t const *utf16 = u"UTF-8 string \u2500"; char32_t const *utf32 = U"UTF-32 string \u2500"; } ' @@ -19518,9 +19518,6 @@ printf "%s\n" "no" >&6; } PKG_CONFIG="" fi fi -if test -z "$PKG_CONFIG"; then - as_fn_error $? "pkg-config not found" "$LINENO" 5 -fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for bison that supports parse-param" >&5 printf %s "checking for bison that supports parse-param... " >&6; } @@ -21213,12 +21210,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl >= 1.1.0" 2>&1` + OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl >= 1.1.0" 2>&1` else - OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl >= 1.1.0" 2>&1` + OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl >= 1.1.0" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$OPENSSL_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$OPENSSL_PKG_ERRORS" >&5 PBX_OPENSSL=0 @@ -21232,8 +21229,8 @@ printf "%s\n" "no" >&6; } else - OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS - OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS + OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS + OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -21304,12 +21301,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl11" 2>&1` + OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl11" 2>&1` else - OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl11" 2>&1` + OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl11" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$OPENSSL_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$OPENSSL_PKG_ERRORS" >&5 PBX_OPENSSL=0 @@ -21323,8 +21320,8 @@ printf "%s\n" "no" >&6; } else - OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS - OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS + OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS + OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -21400,12 +21397,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl" 2>&1` + OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl" 2>&1` else - OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl" 2>&1` + OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$OPENSSL_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$OPENSSL_PKG_ERRORS" >&5 PBX_OPENSSL=0 @@ -21419,8 +21416,8 @@ printf "%s\n" "no" >&6; } else - OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS - OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS + OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS + OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -22066,6 +22063,9 @@ printf "%s\n" "#define HAVE_PJSIP_AUTH_CLT_DEINIT 1" >>confdefs.h printf "%s\n" "#define HAVE_PJSIP_TSX_LAYER_FIND_TSX2 1" >>confdefs.h +printf "%s\n" "#define HAVE_PJSIP_ENDPT_SEND_REQUEST2 1" >>confdefs.h + + printf "%s\n" "#define HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS 1" >>confdefs.h @@ -26040,12 +26040,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - LIBEDIT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libedit" 2>&1` + LIBEDIT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libedit" 2>&1` else - LIBEDIT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libedit" 2>&1` + LIBEDIT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libedit" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$LIBEDIT_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$LIBEDIT_PKG_ERRORS" >&5 PBX_LIBEDIT=0 @@ -26059,8 +26059,8 @@ printf "%s\n" "no" >&6; } else - LIBEDIT_CFLAGS=$pkg_cv_LIBEDIT_CFLAGS - LIBEDIT_LIBS=$pkg_cv_LIBEDIT_LIBS + LIBEDIT_CFLAGS=$pkg_cv_LIBEDIT_CFLAGS + LIBEDIT_LIBS=$pkg_cv_LIBEDIT_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -26533,12 +26533,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - JANSSON_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "jansson >= 2.11" 2>&1` + JANSSON_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "jansson >= 2.11" 2>&1` else - JANSSON_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "jansson >= 2.11" 2>&1` + JANSSON_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "jansson >= 2.11" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$JANSSON_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$JANSSON_PKG_ERRORS" >&5 PBX_JANSSON=0 @@ -26552,8 +26552,8 @@ printf "%s\n" "no" >&6; } else - JANSSON_CFLAGS=$pkg_cv_JANSSON_CFLAGS - JANSSON_LIBS=$pkg_cv_JANSSON_LIBS + JANSSON_CFLAGS=$pkg_cv_JANSSON_CFLAGS + JANSSON_LIBS=$pkg_cv_JANSSON_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -26777,12 +26777,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - LIBJWT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libjwt >= $LIBJWT_VERSION" 2>&1` + LIBJWT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libjwt >= $LIBJWT_VERSION" 2>&1` else - LIBJWT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libjwt >= $LIBJWT_VERSION" 2>&1` + LIBJWT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libjwt >= $LIBJWT_VERSION" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$LIBJWT_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$LIBJWT_PKG_ERRORS" >&5 PBX_LIBJWT=0 @@ -26796,8 +26796,8 @@ printf "%s\n" "no" >&6; } else - LIBJWT_CFLAGS=$pkg_cv_LIBJWT_CFLAGS - LIBJWT_LIBS=$pkg_cv_LIBJWT_LIBS + LIBJWT_CFLAGS=$pkg_cv_LIBJWT_CFLAGS + LIBJWT_LIBS=$pkg_cv_LIBJWT_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -26966,12 +26966,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - LIBXML2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libxml-2.0" 2>&1` + LIBXML2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libxml-2.0" 2>&1` else - LIBXML2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libxml-2.0" 2>&1` + LIBXML2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libxml-2.0" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$LIBXML2_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$LIBXML2_PKG_ERRORS" >&5 PBX_LIBXML2=0 @@ -26985,8 +26985,8 @@ printf "%s\n" "no" >&6; } else - LIBXML2_CFLAGS=$pkg_cv_LIBXML2_CFLAGS - LIBXML2_LIBS=$pkg_cv_LIBXML2_LIBS + LIBXML2_CFLAGS=$pkg_cv_LIBXML2_CFLAGS + LIBXML2_LIBS=$pkg_cv_LIBXML2_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -34430,12 +34430,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - ILBC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libilbc < 3" 2>&1` + ILBC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libilbc < 3" 2>&1` else - ILBC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libilbc < 3" 2>&1` + ILBC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libilbc < 3" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$ILBC_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$ILBC_PKG_ERRORS" >&5 PBX_ILBC=0 @@ -34449,8 +34449,8 @@ printf "%s\n" "no" >&6; } else - ILBC_CFLAGS=$pkg_cv_ILBC_CFLAGS - ILBC_LIBS=$pkg_cv_ILBC_LIBS + ILBC_CFLAGS=$pkg_cv_ILBC_CFLAGS + ILBC_LIBS=$pkg_cv_ILBC_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -36806,12 +36806,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - NETSNMP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "netsnmp-agent" 2>&1` + NETSNMP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "netsnmp-agent" 2>&1` else - NETSNMP_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "netsnmp-agent" 2>&1` + NETSNMP_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "netsnmp-agent" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$NETSNMP_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$NETSNMP_PKG_ERRORS" >&5 PBX_NETSNMP=0 @@ -36825,8 +36825,8 @@ printf "%s\n" "no" >&6; } else - NETSNMP_CFLAGS=$pkg_cv_NETSNMP_CFLAGS - NETSNMP_LIBS=$pkg_cv_NETSNMP_LIBS + NETSNMP_CFLAGS=$pkg_cv_NETSNMP_CFLAGS + NETSNMP_LIBS=$pkg_cv_NETSNMP_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -38329,12 +38329,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpjproject" 2>&1` + PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpjproject" 2>&1` else - PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpjproject" 2>&1` + PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpjproject" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$PJPROJECT_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$PJPROJECT_PKG_ERRORS" >&5 PBX_PJPROJECT=0 @@ -38348,8 +38348,8 @@ printf "%s\n" "no" >&6; } else - PJPROJECT_CFLAGS=$pkg_cv_PJPROJECT_CFLAGS - PJPROJECT_LIBS=$pkg_cv_PJPROJECT_LIBS + PJPROJECT_CFLAGS=$pkg_cv_PJPROJECT_CFLAGS + PJPROJECT_LIBS=$pkg_cv_PJPROJECT_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -39752,6 +39752,112 @@ fi +if test "x${PBX_PJSIP_ENDPT_SEND_REQUEST2}" != "x1" -a "${USE_PJSIP_ENDPT_SEND_REQUEST2}" != "no"; then + pbxlibdir="" + # if --with-PJSIP_ENDPT_SEND_REQUEST2=DIR has been specified, use it. + if test "x${PJSIP_ENDPT_SEND_REQUEST2_DIR}" != "x"; then + if test -d ${PJSIP_ENDPT_SEND_REQUEST2_DIR}/lib; then + pbxlibdir="-L${PJSIP_ENDPT_SEND_REQUEST2_DIR}/lib" + else + pbxlibdir="-L${PJSIP_ENDPT_SEND_REQUEST2_DIR}" + fi + fi + + ast_ext_lib_check_save_CFLAGS="${CFLAGS}" + CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS" + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pjsip_endpt_send_request2 in -lpjsip" >&5 +printf %s "checking for pjsip_endpt_send_request2 in -lpjsip... " >&6; } +if test ${ac_cv_lib_pjsip_pjsip_endpt_send_request2+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char pjsip_endpt_send_request2 (void); +int +main (void) +{ +return pjsip_endpt_send_request2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_pjsip_pjsip_endpt_send_request2=yes +else case e in #( + e) ac_cv_lib_pjsip_pjsip_endpt_send_request2=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pjsip_pjsip_endpt_send_request2" >&5 +printf "%s\n" "$ac_cv_lib_pjsip_pjsip_endpt_send_request2" >&6; } +if test "x$ac_cv_lib_pjsip_pjsip_endpt_send_request2" = xyes +then : + AST_PJSIP_ENDPT_SEND_REQUEST2_FOUND=yes +else case e in #( + e) AST_PJSIP_ENDPT_SEND_REQUEST2_FOUND=no ;; +esac +fi + + CFLAGS="${ast_ext_lib_check_save_CFLAGS}" + + + # now check for the header. + if test "${AST_PJSIP_ENDPT_SEND_REQUEST2_FOUND}" = "yes"; then + PJSIP_ENDPT_SEND_REQUEST2_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB" + # if --with-PJSIP_ENDPT_SEND_REQUEST2=DIR has been specified, use it. + if test "x${PJSIP_ENDPT_SEND_REQUEST2_DIR}" != "x"; then + PJSIP_ENDPT_SEND_REQUEST2_INCLUDE="-I${PJSIP_ENDPT_SEND_REQUEST2_DIR}/include" + fi + PJSIP_ENDPT_SEND_REQUEST2_INCLUDE="${PJSIP_ENDPT_SEND_REQUEST2_INCLUDE} $PJPROJECT_CFLAGS" + + # check for the header + ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${PJSIP_ENDPT_SEND_REQUEST2_INCLUDE}" + ac_fn_c_check_header_compile "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default" +if test "x$ac_cv_header_pjsip_h" = xyes +then : + PJSIP_ENDPT_SEND_REQUEST2_HEADER_FOUND=1 +else case e in #( + e) PJSIP_ENDPT_SEND_REQUEST2_HEADER_FOUND=0 ;; +esac +fi + + CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}" + + if test "x${PJSIP_ENDPT_SEND_REQUEST2_HEADER_FOUND}" = "x0" ; then + PJSIP_ENDPT_SEND_REQUEST2_LIB="" + PJSIP_ENDPT_SEND_REQUEST2_INCLUDE="" + else + + PBX_PJSIP_ENDPT_SEND_REQUEST2=1 + cat >>confdefs.h <<_ACEOF +#define HAVE_PJSIP_ENDPT_SEND_REQUEST2 1 +_ACEOF + + fi + fi +fi + + + if test "x${PBX_PJSIP_TLS_TRANSPORT_RESTART}" != "x1" -a "${USE_PJSIP_TLS_TRANSPORT_RESTART}" != "no"; then pbxlibdir="" # if --with-PJSIP_TLS_TRANSPORT_RESTART=DIR has been specified, use it. @@ -40128,12 +40234,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - PORTAUDIO_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "portaudio-2.0" 2>&1` + PORTAUDIO_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "portaudio-2.0" 2>&1` else - PORTAUDIO_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "portaudio-2.0" 2>&1` + PORTAUDIO_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "portaudio-2.0" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$PORTAUDIO_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$PORTAUDIO_PKG_ERRORS" >&5 PBX_PORTAUDIO=0 @@ -40147,8 +40253,8 @@ printf "%s\n" "no" >&6; } else - PORTAUDIO_CFLAGS=$pkg_cv_PORTAUDIO_CFLAGS - PORTAUDIO_LIBS=$pkg_cv_PORTAUDIO_LIBS + PORTAUDIO_CFLAGS=$pkg_cv_PORTAUDIO_CFLAGS + PORTAUDIO_LIBS=$pkg_cv_PORTAUDIO_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -46481,12 +46587,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - GMIME_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gmime-$ver" 2>&1` + GMIME_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gmime-$ver" 2>&1` else - GMIME_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gmime-$ver" 2>&1` + GMIME_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gmime-$ver" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$GMIME_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$GMIME_PKG_ERRORS" >&5 PBX_GMIME=0 @@ -46500,8 +46606,8 @@ printf "%s\n" "no" >&6; } else - GMIME_CFLAGS=$pkg_cv_GMIME_CFLAGS - GMIME_LIBS=$pkg_cv_GMIME_LIBS + GMIME_CFLAGS=$pkg_cv_GMIME_CFLAGS + GMIME_LIBS=$pkg_cv_GMIME_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -47849,12 +47955,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - GTK2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gtk+-2.0" 2>&1` + GTK2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gtk+-2.0" 2>&1` else - GTK2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gtk+-2.0" 2>&1` + GTK2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gtk+-2.0" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$GTK2_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$GTK2_PKG_ERRORS" >&5 PBX_GTK2=0 @@ -47868,8 +47974,8 @@ printf "%s\n" "no" >&6; } else - GTK2_CFLAGS=$pkg_cv_GTK2_CFLAGS - GTK2_LIBS=$pkg_cv_GTK2_LIBS + GTK2_CFLAGS=$pkg_cv_GTK2_CFLAGS + GTK2_LIBS=$pkg_cv_GTK2_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -47960,12 +48066,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libsystemd" 2>&1` + SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libsystemd" 2>&1` else - SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libsystemd" 2>&1` + SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libsystemd" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$SYSTEMD_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$SYSTEMD_PKG_ERRORS" >&5 PBX_SYSTEMD=0 @@ -47979,8 +48085,8 @@ printf "%s\n" "no" >&6; } else - SYSTEMD_CFLAGS=$pkg_cv_SYSTEMD_CFLAGS - SYSTEMD_LIBS=$pkg_cv_SYSTEMD_LIBS + SYSTEMD_CFLAGS=$pkg_cv_SYSTEMD_CFLAGS + SYSTEMD_LIBS=$pkg_cv_SYSTEMD_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } diff --git a/configure.ac b/configure.ac index 43023344ca..edf3750748 100644 --- a/configure.ac +++ b/configure.ac @@ -2562,6 +2562,7 @@ if test "$USE_PJPROJECT" != "no" ; then AST_EXT_LIB_CHECK([PJSIP_INV_SESSION_REF], [pjsip], [pjsip_inv_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS]) AST_EXT_LIB_CHECK([PJSIP_AUTH_CLT_DEINIT], [pjsip], [pjsip_auth_clt_deinit], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS]) AST_EXT_LIB_CHECK([PJSIP_TSX_LAYER_FIND_TSX2], [pjsip], [pjsip_tsx_layer_find_tsx2], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS]) + AST_EXT_LIB_CHECK([PJSIP_ENDPT_SEND_REQUEST2], [pjsip], [pjsip_endpt_send_request2], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS]) AST_EXT_LIB_CHECK([PJSIP_TLS_TRANSPORT_RESTART], [pjsip], [pjsip_tls_transport_restart], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS]) AST_EXT_LIB_CHECK([PJSIP_AUTH_NEW_DIGESTS], [pjsip], [pjsip_auth_get_algorithm_by_type], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS]) fi diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index 27291cda94..1a90f41cdb 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -642,6 +642,9 @@ /* Define if your system has the PJSIP_ENDPOINT_COMPACT_FORM headers. */ #undef HAVE_PJSIP_ENDPOINT_COMPACT_FORM +/* Define if your system has pjsip_endpt_send_request2 declared. */ +#undef HAVE_PJSIP_ENDPT_SEND_REQUEST2 + /* Define to 1 if PJPROJECT has the PJSIP EVSUB Group Lock support feature. */ #undef HAVE_PJSIP_EVSUB_GRP_LOCK diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 12cf847494..0eebc14b12 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -2695,6 +2695,23 @@ int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpoint *endpoint, int timeout, void *token, void (*callback)(void *token, pjsip_event *e)); +/*! + * \brief Send an Out-Of-Dialog SIP request and return its transaction + * + * This behaves the same as \ref ast_sip_send_out_of_dialog_request, but also + * returns the created transaction with its reference count incremented. + * + * \param[out] tsx The created transaction, or NULL if the send completed with + * an error callback before returning. If non-NULL, the caller must release the + * reference with pj_grp_lock_dec_ref(tsx->grp_lock) when it is no longer needed. + * + * \retval 0 Success + * \retval -1 Failure (out-of-dialog callback will not be called.) + */ +int ast_sip_send_out_of_dialog_request_with_tsx(pjsip_tx_data *tdata, + struct ast_sip_endpoint *endpoint, int timeout, void *token, + void (*callback)(void *token, pjsip_event *e), pjsip_transaction **tsx); + /*! * \brief General purpose method for creating a SIP response * diff --git a/menuselect/configure b/menuselect/configure index d3810d1ce3..825dd829b5 100755 --- a/menuselect/configure +++ b/menuselect/configure @@ -3880,9 +3880,6 @@ printf "%s\n" "no" >&6; } PKG_CONFIG="" fi fi -if test -z "$PKG_CONFIG"; then - as_fn_error $? "pkg-config not found" "$LINENO" 5 -fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' @@ -5209,12 +5206,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - LIBXML2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libxml-2.0" 2>&1` + LIBXML2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libxml-2.0" 2>&1` else - LIBXML2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libxml-2.0" 2>&1` + LIBXML2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libxml-2.0" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$LIBXML2_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$LIBXML2_PKG_ERRORS" >&5 PBX_LIBXML2=0 @@ -5228,8 +5225,8 @@ printf "%s\n" "no" >&6; } else - LIBXML2_CFLAGS=$pkg_cv_LIBXML2_CFLAGS - LIBXML2_LIBS=$pkg_cv_LIBXML2_LIBS + LIBXML2_CFLAGS=$pkg_cv_LIBXML2_CFLAGS + LIBXML2_LIBS=$pkg_cv_LIBXML2_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } @@ -5457,12 +5454,12 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - GTK2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gtk+-2.0" 2>&1` + GTK2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gtk+-2.0" 2>&1` else - GTK2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gtk+-2.0" 2>&1` + GTK2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gtk+-2.0" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$GTK2_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$GTK2_PKG_ERRORS" >&5 PBX_GTK2=0 @@ -5476,8 +5473,8 @@ printf "%s\n" "no" >&6; } else - GTK2_CFLAGS=$pkg_cv_GTK2_CFLAGS - GTK2_LIBS=$pkg_cv_GTK2_LIBS + GTK2_CFLAGS=$pkg_cv_GTK2_CFLAGS + GTK2_LIBS=$pkg_cv_GTK2_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 18c68a33c4..369cddb81f 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -1692,12 +1692,17 @@ static void send_request_wrapper_destructor(void *obj) } static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint, - pjsip_tx_data *tdata, pj_int32_t timeout, void *token, pjsip_endpt_send_callback cb) + pjsip_tx_data *tdata, pj_int32_t timeout, void *token, pjsip_endpt_send_callback cb, + pjsip_transaction **tsx) { struct send_request_wrapper *req_wrapper; pj_status_t ret_val; pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint(); + if (tsx) { + *tsx = NULL; + } + if (!cb && token) { /* Silly. Without a callback we cannot do anything with token. */ pjsip_tx_data_dec_ref(tdata); @@ -1753,7 +1758,26 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint, * transaction callback is executed. */ ao2_ref(req_wrapper, +1); - ret_val = pjsip_endpt_send_request(endpt, tdata, -1, req_wrapper, endpt_send_request_cb); + if (tsx) { +#ifdef HAVE_PJSIP_ENDPT_SEND_REQUEST2 + ret_val = pjsip_endpt_send_request2(endpt, tdata, -1, req_wrapper, + endpt_send_request_cb, tsx); +#else + /* + * Fallback for PJPROJECT builds that do not provide + * pjsip_endpt_send_request2(). This means that any callers to + * endpt_send_request will not receive a transaction pointer back, + * and therefore will not be able to cancel the in-flight transaction. + */ + ret_val = pjsip_endpt_send_request(endpt, tdata, -1, req_wrapper, + endpt_send_request_cb); + *tsx = NULL; +#endif + } else { + ret_val = pjsip_endpt_send_request(endpt, tdata, -1, req_wrapper, + endpt_send_request_cb); + } + if (ret_val != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; @@ -1870,7 +1894,7 @@ static int check_request_status(struct send_request_data *req_data, pjsip_event if (res) { res = endpt_send_request(endpoint, tdata, -1, - req_data, send_request_cb) == PJ_SUCCESS; + req_data, send_request_cb, NULL) == PJ_SUCCESS; } ao2_ref(endpoint, -1); @@ -1888,9 +1912,10 @@ static void send_request_cb(void *token, pjsip_event *e) case PJSIP_EVENT_TRANSPORT_ERROR: case PJSIP_EVENT_TIMER: /* - * Check the request status on transport error or timeout. A transport - * error can occur when a TCP socket closes and that can be the result - * of a 503. Also we may need to failover on a timeout (408). + * Check the request status on transport error, timeout or user event + * (triggered by a user cancel). A transport error can occur when a + * TCP socket closes and that can be the result of a 503. Also we may + * need to failover on a timeout (408). */ if (check_request_status(req_data, e)) { return; @@ -1921,6 +1946,14 @@ static void send_request_cb(void *token, pjsip_event *e) return; } break; + case PJSIP_EVENT_USER: + /* + * User event is triggered by a user cancel. We don't need to do + * anything here other than invoke the callback. + */ + ast_debug(3, "User event received for request to endpoint %s\n", + ast_sorcery_object_get_id(req_data->endpoint)); + break; default: ast_log(LOG_ERROR, "Unexpected PJSIP event %u\n", e->body.tsx_state.type); break; @@ -1933,14 +1966,18 @@ static void send_request_cb(void *token, pjsip_event *e) ao2_ref(req_data, -1); } -int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, +int ast_sip_send_out_of_dialog_request_with_tsx(pjsip_tx_data *tdata, struct ast_sip_endpoint *endpoint, int timeout, void *token, - void (*callback)(void *token, pjsip_event *e)) + void (*callback)(void *token, pjsip_event *e), pjsip_transaction **tsx) { struct ast_sip_supplement *supplement; struct send_request_data *req_data; struct ast_sip_contact *contact; + if (tsx) { + *tsx = NULL; + } + req_data = send_request_data_alloc(endpoint, token, callback); if (!req_data) { pjsip_tx_data_dec_ref(tdata); @@ -1965,7 +2002,7 @@ int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, ast_sip_mod_data_set(tdata->pool, tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT, NULL); ao2_cleanup(contact); - if (endpt_send_request(endpoint, tdata, timeout, req_data, send_request_cb) + if (endpt_send_request(endpoint, tdata, timeout, req_data, send_request_cb, tsx) != PJ_SUCCESS) { ao2_cleanup(req_data); return -1; @@ -1974,6 +2011,14 @@ int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, return 0; } +int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, + struct ast_sip_endpoint *endpoint, int timeout, void *token, + void (*callback)(void *token, pjsip_event *e)) +{ + return ast_sip_send_out_of_dialog_request_with_tsx(tdata, endpoint, timeout, + token, callback, NULL); +} + int ast_sip_send_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint, void *token, void (*callback)(void *token, pjsip_event *e)) diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c index e8e58a99d2..491652bcf4 100644 --- a/res/res_pjsip/pjsip_options.c +++ b/res/res_pjsip/pjsip_options.c @@ -33,6 +33,7 @@ #include "asterisk/statsd.h" #include "include/res_pjsip_private.h" #include "asterisk/taskprocessor.h" +#include "asterisk/taskpool.h" /* * This implementation for OPTIONS support is based around the idea @@ -548,8 +549,157 @@ struct sip_options_contact_callback_data { struct timeval rtt_start; /*! \brief The new status of the contact */ enum ast_sip_contact_status_type status; + /*! \brief Whether the OPTIONS request(s) have been completed */ + unsigned int completed; + /*! \brief Number of resolved targets waiting for a response */ + unsigned int pending; + /*! \brief Transaction data for the resolve request */ + pjsip_tx_data *resolve_tdata; + /*! \brief Endpoint used to create and send each target request */ + struct ast_sip_endpoint *endpoint; + /*! \brief Transactions for all target OPTIONS requests in this batch */ + AST_VECTOR(, pjsip_transaction *) target_transactions; }; +struct sip_options_qualify_resolve_task_data { + struct sip_options_contact_callback_data *batch; + pj_status_t status; + unsigned int count; + pjsip_server_addresses addresses; + char *names[PJSIP_MAX_RESOLVED_ADDRESSES]; +}; + +static void sip_options_queue_target_result( + struct sip_options_contact_callback_data *batch, + enum ast_sip_contact_status_type status); +static void qualify_contact_cb(void *token, pjsip_event *e); + +static void sip_options_qualify_resolve_task_dtor(void *obj) +{ + struct sip_options_qualify_resolve_task_data *task_data = obj; + unsigned int idx; + + for (idx = 0; idx < task_data->count; ++idx) { + ast_free(task_data->names[idx]); + } + + ao2_ref(task_data->batch, -1); +} + +/*! \brief Process a DNS resolution result on the AOR serializer */ +static int sip_options_qualify_resolve_task(void *obj) +{ + struct sip_options_qualify_resolve_task_data *task_data = obj; + struct sip_options_contact_callback_data *batch = task_data->batch; + unsigned int idx; + + if (task_data->status != PJ_SUCCESS || !task_data->count) { + /* This failure is a de facto single pending result. */ + batch->pending = 1; + sip_options_queue_target_result(batch, UNAVAILABLE); + ao2_ref(task_data, -1); + return 0; + } + + /* Each resolved address must contribute exactly one batch result. */ + batch->pending = task_data->count; + + for (idx = 0; idx < task_data->count; ++idx) { + pjsip_tx_data *tdata; + pjsip_via_hdr *via; + pjsip_transaction *tsx = NULL; + + if (ast_sip_create_request("OPTIONS", NULL, batch->endpoint, NULL, + batch->contact, &tdata)) { + sip_options_queue_target_result(batch, UNAVAILABLE); + continue; + } + + if (!ast_strlen_zero(batch->contact->outbound_proxy) && + ast_sip_set_outbound_proxy(tdata, batch->contact->outbound_proxy)) { + pjsip_tx_data_dec_ref(tdata); + sip_options_queue_target_result(batch, UNAVAILABLE); + continue; + } + + /* + * Build a new request per resolved target so each send gets its own + * transaction and Via branch. Setting exactly one pre-resolved + * destination here avoids PJSIP re-resolving and choosing only the + * first DNS result. + */ + tdata->dest_info.addr.count = 1; + tdata->dest_info.cur_addr = 0; + tdata->dest_info.addr.entry[0].type = task_data->addresses.entry[idx].type; + tdata->dest_info.addr.entry[0].priority = task_data->addresses.entry[idx].priority; + tdata->dest_info.addr.entry[0].weight = task_data->addresses.entry[idx].weight; + tdata->dest_info.addr.entry[0].addr_len = task_data->addresses.entry[idx].addr_len; + pj_sockaddr_cp(&tdata->dest_info.addr.entry[0].addr, + &task_data->addresses.entry[idx].addr); + pj_strdup(tdata->pool, &tdata->dest_info.addr.entry[0].name, + &task_data->addresses.entry[idx].name); + via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + if (via) { + via->branch_param.slen = 0; + } + + /* ast_sip_send_out_of_dialog_request_with_tsx consumes the tdata reference. */ + if (ast_sip_send_out_of_dialog_request_with_tsx(tdata, batch->endpoint, + (int)(batch->aor_options->qualify_timeout * 1000), + ao2_bump(batch), qualify_contact_cb, &tsx)) { + ao2_ref(batch, -1); + sip_options_queue_target_result(batch, UNAVAILABLE); + } else if (tsx) { + /* + * It is possible that we will not get back a transaction under two circumstances: + * 1. The send failed. + * 2. The build of PJPROJECT used does not contain pjsip_endpt_send_request2() so + * there is no way to get an in-flight transaction reference for the OPTIONS. + * + * With no transaction references, we can not cancel in-flight OPTIONS requests when + * an endpoint responds with a 2xx response to one of the requests. This is not ideal + * and will result in unnecessary OPTIONS retransmissions being sent. + * + * The Append should never fail as we init the vector to PJSIP_MAX_RESOLVED_ADDRESSES + * and also limit the count of resolved addresses in sip_options_qualify_resolve_cb. + */ + AST_VECTOR_APPEND(&batch->target_transactions, tsx); + } + } + + ao2_ref(task_data, -1); + return 0; +} + +static void sip_options_transaction_free(pjsip_transaction *tsx) +{ + if (tsx) { + pj_grp_lock_dec_ref(tsx->grp_lock); + } +} + +/*! \brief Result of qualifying an individual address belonging to a contact resolved + from an A, AAAA or SRV record. */ +struct sip_options_target_result { + struct sip_options_contact_callback_data *batch; + enum ast_sip_contact_status_type status; +}; + +/*! \brief Terminate all in-flight target transactions for a batch */ +static void sip_options_cancel_target_transactions( + struct sip_options_contact_callback_data *batch) +{ + unsigned int idx; + + for (idx = 0; idx < AST_VECTOR_SIZE(&batch->target_transactions); ++idx) { + pjsip_transaction *tsx = AST_VECTOR_GET(&batch->target_transactions, idx); + + if (tsx) { + pjsip_tsx_terminate_async(tsx, PJSIP_SC_REQUEST_TERMINATED); + } + } +} + /*! * \brief Return the current state of an endpoint state compositor * \pre The endpoint_state_compositor lock must be held. @@ -782,7 +932,89 @@ static int sip_options_contact_status_notify_task(void *obj) return 0; } -/*! \brief Callback for when we get a result from a SIP OPTIONS request (a response or a timeout) */ +/*! \brief Destructor for a resolved target's qualification result */ +static void sip_options_target_result_dtor(void *obj) +{ + struct sip_options_target_result *result = obj; + ao2_cleanup(result->batch); +} + +/*! + * \brief Combine the result of one resolved target with the other targets + * \note Run by aor_options->serializer + */ +static int sip_options_target_result_task(void *obj) +{ + struct sip_options_target_result *result = obj; + struct sip_options_contact_callback_data *batch = result->batch; + + if (!batch->completed) { + /* If the result is available then we can mark the batch as completed + and notify the AOR */ + ast_assert(batch->pending); + --batch->pending; + + if (result->status == AVAILABLE) { + /* At least one resolved target was available. */ + batch->completed = 1; + batch->status = AVAILABLE; + sip_options_cancel_target_transactions(batch); + /* The notify task inherits this reference. */ + sip_options_contact_status_notify_task(ao2_bump(batch)); + } else if (!batch->pending) { + /* Every resolved target failed. */ + batch->completed = 1; + batch->status = UNAVAILABLE; + sip_options_contact_status_notify_task(ao2_bump(batch)); + } + } + + ao2_ref(result, -1); + return 0; +} + +/*! \brief Queue a resolved target's qualification result to be combined with the other targets */ +static void sip_options_queue_target_result( + struct sip_options_contact_callback_data *batch, + enum ast_sip_contact_status_type status) +{ + struct sip_options_target_result *result; + + result = ao2_alloc_options(sizeof(*result), sip_options_target_result_dtor, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!result) { + ast_log(LOG_WARNING, "Unable to allocate qualify target result for '%s'\n", + ast_sorcery_object_get_id(batch->contact)); + return; + } + + result->batch = ao2_bump(batch); + result->status = status; + /* + * Callbacks may arrive on non-serializer threads, so they queue one result per + * target onto the AOR serializer, the single owner of the batch's processing. + * + * Sends and callbacks can execute synchronously while we are already + * running on that serializer thread. Re-queueing to the same serializer in + * that case would defer updates and can break processing. + * + * Process inline when already on the same taskpool serializer to preserve + * immediate, in-order state transitions. + */ + if (batch->aor_options->serializer == ast_taskpool_serializer_get_current()) { + sip_options_target_result_task(result); + return; + } + + if (ast_sip_push_task(batch->aor_options->serializer, + sip_options_target_result_task, result)) { + ast_log(LOG_WARNING, "Unable to queue qualify target result for '%s'\n", + ast_sorcery_object_get_id(batch->contact)); + ao2_ref(result, -1); + } +} + +/*! \brief Callback for an OPTIONS response, timeout, or transport error */ static void qualify_contact_cb(void *token, pjsip_event *e) { struct sip_options_contact_callback_data *contact_callback_data = token; @@ -804,20 +1036,21 @@ static void qualify_contact_cb(void *token, pjsip_event *e) status = AVAILABLE; } break; - } - - /* Update the callback data with the new status, this will get handled in the AOR serializer */ - contact_callback_data->status = status; - - if (ast_sip_push_task(contact_callback_data->aor_options->serializer, - sip_options_contact_status_notify_task, contact_callback_data)) { - ast_log(LOG_WARNING, "Unable to queue contact status update for '%s' on AOR '%s', state will be incorrect\n", - ast_sorcery_object_get_id(contact_callback_data->contact), - contact_callback_data->aor_options->name); + case PJSIP_EVENT_USER: + /* + * When one resolved target responds, we know the endpoint is available and we cancel the other + * outstanding transactions. The cancelled transactions will trigger this user event, which we + * can ignore as we already have a result for the contact. + */ + ast_debug(3, "Received user event for contact '%s', ignoring\n", + ast_sorcery_object_get_id(contact_callback_data->contact)); + /* Decrement reference count for the contact callback data and return as we have no result */ ao2_ref(contact_callback_data, -1); + return; } - /* The task inherited our reference so we don't unreference here */ + sip_options_queue_target_result(contact_callback_data, status); + ao2_ref(contact_callback_data, -1); } /*! \brief Destructor for contact callback data */ @@ -827,11 +1060,24 @@ static void sip_options_contact_callback_data_dtor(void *obj) ao2_cleanup(contact_callback_data->contact); ao2_cleanup(contact_callback_data->aor_options); + ao2_cleanup(contact_callback_data->endpoint); + /* + * sip_options_qualify_resolve_cb should handle cleaning the resolve_tdata, but + * keep this fallback just in case. + */ + if (contact_callback_data->resolve_tdata) { + pjsip_tx_data_dec_ref(contact_callback_data->resolve_tdata); + contact_callback_data->resolve_tdata = NULL; + } + AST_VECTOR_RESET(&contact_callback_data->target_transactions, + sip_options_transaction_free); + AST_VECTOR_FREE(&contact_callback_data->target_transactions); } /*! \brief Contact callback data allocator */ static struct sip_options_contact_callback_data *sip_options_contact_callback_data_alloc( - struct ast_sip_contact *contact, struct sip_options_aor *aor_options) + struct ast_sip_contact *contact, struct sip_options_aor *aor_options, + struct ast_sip_endpoint *endpoint, pjsip_tx_data *resolve_tdata) { struct sip_options_contact_callback_data *contact_callback_data; @@ -841,13 +1087,151 @@ static struct sip_options_contact_callback_data *sip_options_contact_callback_da return NULL; } + if (AST_VECTOR_INIT(&contact_callback_data->target_transactions, PJSIP_MAX_RESOLVED_ADDRESSES)) { + ao2_ref(contact_callback_data, -1); + return NULL; + } + contact_callback_data->contact = ao2_bump(contact); contact_callback_data->aor_options = ao2_bump(aor_options); + contact_callback_data->endpoint = ao2_bump(endpoint); + contact_callback_data->resolve_tdata = resolve_tdata; contact_callback_data->rtt_start = ast_tvnow(); return contact_callback_data; } +/*! + * \brief Apply a request's transport selector to its DNS destination + * + * pjsip_get_request_dest builds the destination from the Request-URI and + * may miss an outbound transport/listener. + * Before manual DNS resolution, apply the selected transport to the + * destination and check for mismatches so we use the correct transport + * for the request. + */ +static pj_status_t sip_options_apply_transport_to_destination( + const pjsip_tx_data *tdata, pjsip_host_info *destination) +{ + pjsip_transport_type_e transport_type = PJSIP_TRANSPORT_UNSPECIFIED; + + if ((tdata->tp_sel.type != PJSIP_TPSELECTOR_TRANSPORT + && tdata->tp_sel.type != PJSIP_TPSELECTOR_LISTENER) + || !tdata->tp_sel.u.ptr) { + return PJ_SUCCESS; + } + + if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) { + transport_type = tdata->tp_sel.u.transport->key.type; + } else { + transport_type = tdata->tp_sel.u.listener->type; + } + + if (destination->type != PJSIP_TRANSPORT_UNSPECIFIED + && ((destination->type | PJSIP_TRANSPORT_IPV6) + != (transport_type | PJSIP_TRANSPORT_IPV6))) { + return PJSIP_ETPNOTSUITABLE; + } + + destination->type = transport_type; + return PJ_SUCCESS; +} + +/*! \brief DNS callback used to fan out a qualify to every resolved target */ +static void sip_options_qualify_resolve_cb(pj_status_t status, void *token, + const pjsip_server_addresses *addresses) +{ + struct sip_options_contact_callback_data *batch = token; + struct sip_options_qualify_resolve_task_data *task_data; + unsigned int idx; + + task_data = ao2_alloc_options(sizeof(*task_data), + sip_options_qualify_resolve_task_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!task_data) { + /* + * Resolver callbacks are not guaranteed to run on the AOR serializer, + * so on allocation failure we must log and drop to avoid potential state + * issues. + */ + ast_log(LOG_WARNING, + "Unable to allocate qualify resolve task for '%s'\n", + ast_sorcery_object_get_id(batch->contact)); + ao2_ref(batch, -1); + return; + } + + memset(task_data, 0, sizeof(*task_data)); + task_data->batch = ao2_bump(batch); + task_data->status = status; + + if (status != PJ_SUCCESS || !addresses || !addresses->count) { + if (batch->resolve_tdata) { + pjsip_tx_data_dec_ref(batch->resolve_tdata); + batch->resolve_tdata = NULL; + } + + if (ast_sip_push_task(batch->aor_options->serializer, + sip_options_qualify_resolve_task, task_data)) { + ast_log(LOG_WARNING, + "Unable to queue qualify resolve task for '%s'\n", + ast_sorcery_object_get_id(batch->contact)); + ao2_ref(task_data, -1); + } + ao2_ref(batch, -1); + return; + } + + /* Each resolved address must contribute exactly one batch result. */ + task_data->count = addresses->count; + if (task_data->count > PJSIP_MAX_RESOLVED_ADDRESSES) { + task_data->count = PJSIP_MAX_RESOLVED_ADDRESSES; + } + + for (idx = 0; idx < task_data->count; ++idx) { + char *name_copy; + + name_copy = ast_malloc(addresses->entry[idx].name.slen + 1); + if (!name_copy) { + task_data->status = PJ_ENOMEM; + task_data->count = idx; + break; + } + + ast_copy_pj_str(name_copy, &addresses->entry[idx].name, + addresses->entry[idx].name.slen + 1); + task_data->names[idx] = name_copy; + task_data->addresses.entry[idx] = addresses->entry[idx]; + task_data->addresses.entry[idx].name.ptr = name_copy; + task_data->addresses.entry[idx].name.slen = addresses->entry[idx].name.slen; + } + + if (task_data->status == PJ_SUCCESS) { + task_data->addresses.count = task_data->count; + } + + if (batch->resolve_tdata) { + pjsip_tx_data_dec_ref(batch->resolve_tdata); + batch->resolve_tdata = NULL; + } + + /* + * Push the qualify resolve task to the AOR serializer to avoid potential race + * conditions between the resolve callback and the qualify contact task. + */ + if (ast_sip_push_task(batch->aor_options->serializer, + sip_options_qualify_resolve_task, task_data)) { + ast_log(LOG_WARNING, + "Unable to queue qualify resolve task for '%s'\n", + ast_sorcery_object_get_id(batch->contact)); + ao2_ref(task_data, -1); + } else { + ao2_ref(batch, -1); + return; + } + + ao2_ref(batch, -1); +} + /*! \brief Send a SIP OPTIONS request for a contact */ static int sip_options_qualify_contact(void *obj, void *arg, int flags) { @@ -855,6 +1239,7 @@ static int sip_options_qualify_contact(void *obj, void *arg, int flags) struct sip_options_aor *aor_options = arg; RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); pjsip_tx_data *tdata; + pjsip_host_info destination; struct ast_sip_contact_status *contact_status; struct sip_options_contact_callback_data *contact_callback_data; @@ -878,7 +1263,13 @@ static int sip_options_qualify_contact(void *obj, void *arg, int flags) return 0; } - if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, contact, &tdata)) { + /* + * Create a request solely for DNS resolution. Its pool keeps the resolver strings alive, + * and passing a URI with no contact avoids leaving a contact reference behind in case + * resolution fails before it can send. + */ + if (ast_sip_create_request("OPTIONS", NULL, endpoint, contact->uri, NULL, + &tdata)) { ast_log(LOG_ERROR, "Unable to create request to qualify contact %s on AOR %s\n", contact->uri, aor_options->name); return 0; @@ -902,7 +1293,8 @@ static int sip_options_qualify_contact(void *obj, void *arg, int flags) } ao2_ref(contact_status, -1); - contact_callback_data = sip_options_contact_callback_data_alloc(contact, aor_options); + contact_callback_data = sip_options_contact_callback_data_alloc(contact, + aor_options, endpoint, tdata); if (!contact_callback_data) { ast_log(LOG_ERROR, "Unable to create object to contain callback data for contact %s on AOR %s\n", contact->uri, aor_options->name); @@ -910,14 +1302,25 @@ static int sip_options_qualify_contact(void *obj, void *arg, int flags) return 0; } - if (ast_sip_send_out_of_dialog_request(tdata, endpoint, - (int)(aor_options->qualify_timeout * 1000), contact_callback_data, - qualify_contact_cb)) { - ast_log(LOG_ERROR, "Unable to send request to qualify contact %s on AOR %s\n", + + /* + * Resolve the contact destination explicitly via DNS, then create and send + * an independent OPTIONS for each returned transport/address/port tuple. + * Keep the resolver-only tdata alive because its pool owns strings used + * by the asynchronous resolver. + */ + if (pjsip_get_request_dest(tdata, &destination) != PJ_SUCCESS + || sip_options_apply_transport_to_destination(tdata, &destination) + != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to resolve destination to qualify contact %s on AOR %s\n", contact->uri, aor_options->name); ao2_ref(contact_callback_data, -1); + return 0; } + pjsip_endpt_resolve(ast_sip_get_pjsip_endpoint(), tdata->pool, &destination, + contact_callback_data, sip_options_qualify_resolve_cb); + return 0; } diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4 index 8c3c50fcf2..6bd84abca1 100644 --- a/third-party/pjproject/configure.m4 +++ b/third-party/pjproject/configure.m4 @@ -133,6 +133,7 @@ AC_DEFUN([_PJPROJECT_CONFIGURE], AC_DEFINE([HAVE_PJSIP_INV_SESSION_REF], 1, [Define if your system has PJSIP_INV_SESSION_REF]) AC_DEFINE([HAVE_PJSIP_AUTH_CLT_DEINIT], 1, [Define if your system has pjsip_auth_clt_deinit declared.]) AC_DEFINE([HAVE_PJSIP_TSX_LAYER_FIND_TSX2], 1, [Define if your system has pjsip_tsx_layer_find_tsx2 declared.]) + AC_DEFINE([HAVE_PJSIP_ENDPT_SEND_REQUEST2], 1, [Define if your system has pjsip_endpt_send_request2 declared.]) AC_DEFINE([HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS], 1, [Define if your system has HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS declared.]) AC_DEFINE([HAVE_PJSIP_ENDPOINT_COMPACT_FORM], 1, [Define if your system has HAVE_PJSIP_ENDPOINT_COMPACT_FORM declared.]) AC_DEFINE([HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], 1, [Define if your system has HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE declared]) diff --git a/third-party/pjproject/patches/0030-Add_pjsip_endpt_send_request2.patch b/third-party/pjproject/patches/0030-Add_pjsip_endpt_send_request2.patch new file mode 100644 index 0000000000..c2c2d190b1 --- /dev/null +++ b/third-party/pjproject/patches/0030-Add_pjsip_endpt_send_request2.patch @@ -0,0 +1,458 @@ +From f1a1cff26613b5c5ce4087afbf5f3982f09e2765 Mon Sep 17 00:00:00 2001 +From: nanang +Date: Tue, 18 Aug 2026 08:40:44 +0700 +Subject: [PATCH 1/3] pjsip: add pjsip_endpt_send_request2() returning the UAC + transaction + +pjsip_endpt_send_request() does not return any handle to the transaction +it creates, so an application has no way to abandon an out-of-dialog +request, e.g. to stop the retransmissions of an OPTIONS request when the +response is no longer needed. + +Add pjsip_endpt_send_request2(), which optionally returns the created +transaction with its reference counter incremented, following the same +pattern as pjsip_endpt_respond(). Application can then terminate the +request using the existing pjsip_tsx_terminate_async(). The transaction +is returned before the request is sent, as the completion callback may +already be invoked from another thread once it is sent. + +Co-Authored-By Claude Code +--- + pjsip/include/pjsip/sip_util.h | 48 ++++++++++ + pjsip/src/pjsip/sip_util_statefull.c | 29 ++++++ + pjsip/src/test/tsx_basic_test.c | 129 +++++++++++++++++++++++++++ + 3 files changed, 206 insertions(+) + +diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h +index bba120fc74..647e40b457 100644 +--- a/pjsip/include/pjsip/sip_util.h ++++ b/pjsip/include/pjsip/sip_util.h +@@ -793,6 +793,54 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + void *token, + pjsip_endpt_send_callback cb); + ++/** ++ * Variant of #pjsip_endpt_send_request() which can also return the ++ * transaction created to send the request. Application may use the ++ * transaction, for example, to terminate the request before any response ++ * is received, e.g. to stop the retransmissions of an out-of-dialog request ++ * such as OPTIONS. Note that terminating the transaction only abandons the ++ * request locally, nothing is sent to the network, as CANCEL is not ++ * applicable to non-INVITE requests (see RFC 3261 section 9.1). ++ * ++ * To terminate the transaction, application should use ++ * #pjsip_tsx_terminate_async() or #pjsip_tsx_terminate_async2(), which are ++ * safe to be called from any thread, including from within the callback ++ * \a cb itself. Terminating an already completed transaction is harmless. ++ * ++ * @param endpt The endpoint instance. ++ * @param tdata The transmit data to be sent. ++ * @param timeout Optional timeout for final response to be received, or -1 ++ * if the transaction should not have a timeout restriction. ++ * The value is in miliseconds. Note that this is not ++ * implemented yet, so application needs to use its own timer ++ * to handle timeout. ++ * @param token Optional token to be associated with the transaction, and ++ * to be passed to the callback. ++ * @param cb Optional callback to be called when the transaction has ++ * received a final response. The callback will be called with ++ * the previously registered token and the event that triggers ++ * the completion of the transaction. ++ * @param p_tsx Optional pointer to receive the transaction which was ++ * created to send the request. If it is not NULL, on ++ * success it will be set to the transaction with its ++ * reference counter incremented, so application must ++ * release it using pj_grp_lock_dec_ref(tsx->grp_lock) once ++ * it no longer needs the transaction, e.g. after the ++ * callback \a cb is called. On failure, it will be set to ++ * NULL. Note that the callback \a cb may already be called ++ * before this function returns, so application must be ++ * ready for the transaction to be already completed by the ++ * time it inspects this argument. ++ * ++ * @return PJ_SUCCESS, or the appropriate error code. ++ */ ++PJ_DECL(pj_status_t) pjsip_endpt_send_request2(pjsip_endpoint *endpt, ++ pjsip_tx_data *tdata, ++ pj_int32_t timeout, ++ void *token, ++ pjsip_endpt_send_callback cb, ++ pjsip_transaction **p_tsx); ++ + /** + * @} + */ +diff --git a/pjsip/src/pjsip/sip_util_statefull.c b/pjsip/src/pjsip/sip_util_statefull.c +index efa2bd8907..9d0ec18160 100644 +--- a/pjsip/src/pjsip/sip_util_statefull.c ++++ b/pjsip/src/pjsip/sip_util_statefull.c +@@ -88,6 +88,17 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + pj_int32_t timeout, + void *token, + pjsip_endpt_send_callback cb) ++{ ++ return pjsip_endpt_send_request2(endpt, tdata, timeout, token, cb, NULL); ++} ++ ++ ++PJ_DEF(pj_status_t) pjsip_endpt_send_request2( pjsip_endpoint *endpt, ++ pjsip_tx_data *tdata, ++ pj_int32_t timeout, ++ void *token, ++ pjsip_endpt_send_callback cb, ++ pjsip_transaction **p_tsx) + { + pjsip_transaction *tsx; + struct tsx_data *tsx_data; +@@ -100,6 +111,8 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + + PJ_UNUSED_ARG(timeout); + ++ if (p_tsx) *p_tsx = NULL; ++ + status = pjsip_tsx_create_uac(&mod_stateful_util, tdata, &tsx); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); +@@ -119,8 +132,24 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + */ + pj_grp_lock_add_ref(tsx->grp_lock); + ++ /* Return the transaction before sending the request, as the callback may ++ * already be called from another thread once the request is sent. ++ */ ++ if (p_tsx) { ++ pj_grp_lock_add_ref(tsx->grp_lock); ++ *p_tsx = tsx; ++ } ++ + status = pjsip_tsx_send_msg(tsx, NULL); + if (status != PJ_SUCCESS) { ++ /* Release the reference and reset the output before terminating the ++ * transaction, as terminating it may invoke the callback which may ++ * destroy the storage of the output argument. ++ */ ++ if (p_tsx) { ++ *p_tsx = NULL; ++ pj_grp_lock_dec_ref(tsx->grp_lock); ++ } + pjsip_tx_data_dec_ref(tdata); + pjsip_tsx_terminate(tsx, tsx->status_code? tsx->status_code: + PJSIP_SC_SERVICE_UNAVAILABLE); +diff --git a/pjsip/src/test/tsx_basic_test.c b/pjsip/src/test/tsx_basic_test.c +index 01bd2a86bb..caa59ece0d 100644 +--- a/pjsip/src/test/tsx_basic_test.c ++++ b/pjsip/src/test/tsx_basic_test.c +@@ -23,11 +23,23 @@ + + #define THIS_FILE "tsx_basic_test.c" + ++struct send_request2_data ++{ ++ pj_bool_t cb_called; ++ int status_code; ++}; ++ + static struct tsx_basic_test_global_t + { + char TARGET_URI[PJSIP_MAX_URL_SIZE]; + char FROM_URI[PJSIP_MAX_URL_SIZE]; + pjsip_transport *tp; ++ ++ /* Must outlive the test function, as the callback may still be called ++ * after the test returns, e.g. when the test fails to terminate the ++ * transaction. ++ */ ++ struct send_request2_data sr; + } g[MAX_TSX_TESTS]; + + +@@ -159,6 +171,116 @@ static int double_terminate(unsigned tid) + return PJ_SUCCESS; + } + ++static void send_request2_cb(void *token, pjsip_event *e) ++{ ++ struct send_request2_data *sr = (struct send_request2_data*)token; ++ ++ sr->cb_called = PJ_TRUE; ++ if (e->type == PJSIP_EVENT_TSX_STATE && e->body.tsx_state.tsx) ++ sr->status_code = e->body.tsx_state.tsx->status_code; ++} ++ ++/* Test terminating a request sent using pjsip_endpt_send_request2(), e.g. ++ * to stop the retransmissions before any response is received. ++ */ ++static int send_request2_test(unsigned tid) ++{ ++ struct send_request2_data *sr = &g[tid].sr; ++ pj_str_t target, from, tsx_key; ++ pjsip_tx_data *tdata; ++ pjsip_transaction *tsx = NULL; ++ pjsip_tpselector tp_sel; ++ pj_bool_t prev_discard; ++ pj_status_t status; ++ int rc = 0; ++ ++ PJ_LOG(3,(THIS_FILE, " send request2 and terminate test")); ++ ++ target = pj_str(g[tid].TARGET_URI); ++ from = pj_str(g[tid].FROM_URI); ++ ++ /* Discard the outgoing request, so it will never be answered. */ ++ pjsip_loop_set_discard(g[tid].tp, PJ_TRUE, &prev_discard); ++ ++ status = pjsip_endpt_create_request(endpt, &pjsip_options_method, &target, ++ &from, &target, NULL, NULL, -1, NULL, ++ &tdata); ++ if (status != PJ_SUCCESS) { ++ app_perror(" error: unable to create request", status); ++ rc = -200; ++ goto on_return; ++ } ++ ++ pj_bzero(&tp_sel, sizeof(tp_sel)); ++ tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT; ++ tp_sel.u.transport = g[tid].tp; ++ pjsip_tx_data_set_transport(tdata, &tp_sel); ++ ++ pj_bzero(sr, sizeof(*sr)); ++ ++ status = pjsip_endpt_send_request2(endpt, tdata, -1, sr, ++ &send_request2_cb, &tsx); ++ if (status != PJ_SUCCESS) { ++ app_perror(" error: unable to send request", status); ++ rc = -210; ++ goto on_return; ++ } ++ ++ if (tsx == NULL) { ++ PJ_LOG(3,(THIS_FILE, " error: transaction is not returned")); ++ rc = -220; ++ goto on_return; ++ } ++ ++ /* Save the key, to verify that the transaction is unregistered later. */ ++ pj_strdup_with_null(tsx->pool, &tsx_key, &tsx->transaction_key); ++ ++ /* Nothing should complete the transaction by itself. */ ++ flush_events(100); ++ if (sr->cb_called) { ++ PJ_LOG(3,(THIS_FILE, " error: transaction completed prematurely")); ++ rc = -230; ++ goto on_dec_ref; ++ } ++ ++ status = pjsip_tsx_terminate_async(tsx, PJSIP_SC_REQUEST_TERMINATED); ++ if (status != PJ_SUCCESS) { ++ app_perror(" error: unable to terminate transaction", status); ++ rc = -240; ++ goto on_dec_ref; ++ } ++ ++ flush_events(1000); ++ ++ if (!sr->cb_called) { ++ PJ_LOG(3,(THIS_FILE, " error: callback is not called")); ++ rc = -250; ++ goto on_dec_ref; ++ } ++ ++ if (sr->status_code != PJSIP_SC_REQUEST_TERMINATED) { ++ PJ_LOG(3,(THIS_FILE, " error: unexpected status code %d", ++ sr->status_code)); ++ rc = -260; ++ goto on_dec_ref; ++ } ++ ++ /* The transaction must have been unregistered from the transaction ++ * layer, while our reference keeps the instance alive. ++ */ ++ if (pjsip_tsx_layer_find_tsx2(&tsx_key, PJ_FALSE) != NULL) { ++ PJ_LOG(3,(THIS_FILE, " error: transaction is still registered")); ++ rc = -270; ++ } ++ ++on_dec_ref: ++ pj_grp_lock_dec_ref(tsx->grp_lock); ++ ++on_return: ++ pjsip_loop_set_discard(g[tid].tp, prev_discard, NULL); ++ return rc; ++} ++ + int tsx_basic_test(unsigned tid) + { + struct tsx_test_param *param = &tsx_test[tid]; +@@ -187,6 +309,13 @@ int tsx_basic_test(unsigned tid) + if (status != 0) + goto on_return; + ++ /* This test needs the loop transport to blackhole the request. */ ++ if (g[tid].tp) { ++ status = send_request2_test(tid); ++ if (status != 0) ++ goto on_return; ++ } ++ + status = 0; + + on_return: + +From 54e3c78c578abcdb3c2986de557673b48c36d725 Mon Sep 17 00:00:00 2001 +From: nanang +Date: Wed, 2 Sep 2026 11:22:59 +0700 +Subject: [PATCH 2/3] pjsip: hand over the send_request2() transaction only on + successful send + +pjsip_tsx_send_msg() may fail after the transaction has already been +terminated and the completion callback invoked, e.g. when the stateless +send completes synchronously with an error, which tsx_send_msg() then +converts into a failure return (see the "Check if transaction is +terminated" checks in sip_transaction.c). As the output argument was set +before the send, the application callback could run while *p_tsx held a +reference that the failure path then released. An application releasing +that reference from within the callback, as the documentation suggested, +would release it twice. + +Set *p_tsx and add the reference only after the send succeeds. The +reference this function already holds keeps the transaction alive there, +even when it has been completed by the callback, so nothing is lost and +the failure path has nothing to undo. The callback does not need the +argument either, as the event already carries the transaction. + +Co-Authored-By Claude Code +--- + pjsip/include/pjsip/sip_util.h | 17 +++++++++-------- + pjsip/src/pjsip/sip_util_statefull.c | 22 ++++++++-------------- + 2 files changed, 17 insertions(+), 22 deletions(-) + +diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h +index 647e40b457..b8d6eb0537 100644 +--- a/pjsip/include/pjsip/sip_util.h ++++ b/pjsip/include/pjsip/sip_util.h +@@ -821,16 +821,17 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + * the previously registered token and the event that triggers + * the completion of the transaction. + * @param p_tsx Optional pointer to receive the transaction which was +- * created to send the request. If it is not NULL, on +- * success it will be set to the transaction with its ++ * created to send the request. On failure it will be set ++ * to NULL, on success to the transaction with its + * reference counter incremented, so application must + * release it using pj_grp_lock_dec_ref(tsx->grp_lock) once +- * it no longer needs the transaction, e.g. after the +- * callback \a cb is called. On failure, it will be set to +- * NULL. Note that the callback \a cb may already be called +- * before this function returns, so application must be +- * ready for the transaction to be already completed by the +- * time it inspects this argument. ++ * it no longer needs the transaction. The reference is ++ * handed over only after this function returns PJ_SUCCESS, ++ * so it must not be released from within the callback ++ * \a cb, which may be called before this function returns, ++ * i.e. the transaction may already be completed by the time ++ * application inspects this argument. The transaction is ++ * also available in the event given to the callback. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +diff --git a/pjsip/src/pjsip/sip_util_statefull.c b/pjsip/src/pjsip/sip_util_statefull.c +index 9d0ec18160..ee7d62995e 100644 +--- a/pjsip/src/pjsip/sip_util_statefull.c ++++ b/pjsip/src/pjsip/sip_util_statefull.c +@@ -132,24 +132,18 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request2( pjsip_endpoint *endpt, + */ + pj_grp_lock_add_ref(tsx->grp_lock); + +- /* Return the transaction before sending the request, as the callback may +- * already be called from another thread once the request is sent. +- */ +- if (p_tsx) { +- pj_grp_lock_add_ref(tsx->grp_lock); +- *p_tsx = tsx; +- } +- + status = pjsip_tsx_send_msg(tsx, NULL); +- if (status != PJ_SUCCESS) { +- /* Release the reference and reset the output before terminating the +- * transaction, as terminating it may invoke the callback which may +- * destroy the storage of the output argument. ++ if (status == PJ_SUCCESS) { ++ /* Only hand over the transaction after a successful send, as the ++ * send may fail after the callback has been called. Our reference ++ * above keeps the transaction alive here, even when it has already ++ * been completed by the callback. + */ + if (p_tsx) { +- *p_tsx = NULL; +- pj_grp_lock_dec_ref(tsx->grp_lock); ++ pj_grp_lock_add_ref(tsx->grp_lock); ++ *p_tsx = tsx; + } ++ } else { + pjsip_tx_data_dec_ref(tdata); + pjsip_tsx_terminate(tsx, tsx->status_code? tsx->status_code: + PJSIP_SC_SERVICE_UNAVAILABLE); + +From 4653d3c198155d4ee74e7651192ef4b57a00457d Mon Sep 17 00:00:00 2001 +From: nanang +Date: Wed, 2 Sep 2026 13:51:27 +0700 +Subject: [PATCH 3/3] pjsip: reset send_request2() output before the argument + checks + +The PJ_ASSERT_RETURN() checks return before the output was reset, so +*p_tsx was left untouched when an argument is invalid or the transaction +layer module is not registered, which contradicts the documented "on +failure it will be set to NULL". Reset it first. + +Also document that, unlike the p_tsx output of pjsip_endpt_respond(), +the transaction is returned with a reference the application must +release. + +Co-Authored-By Claude Code +--- + pjsip/include/pjsip/sip_util.h | 5 +++++ + pjsip/src/pjsip/sip_util_statefull.c | 7 +++++-- + 2 files changed, 10 insertions(+), 2 deletions(-) + +diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h +index b8d6eb0537..5a3b873317 100644 +--- a/pjsip/include/pjsip/sip_util.h ++++ b/pjsip/include/pjsip/sip_util.h +@@ -807,6 +807,11 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + * safe to be called from any thread, including from within the callback + * \a cb itself. Terminating an already completed transaction is harmless. + * ++ * Note that unlike the \a p_tsx output of #pjsip_endpt_respond(), which is ++ * returned without any reference added, the transaction returned by this ++ * function has its reference counter incremented, so application must ++ * release it once it no longer needs the transaction. ++ * + * @param endpt The endpoint instance. + * @param tdata The transmit data to be sent. + * @param timeout Optional timeout for final response to be received, or -1 +diff --git a/pjsip/src/pjsip/sip_util_statefull.c b/pjsip/src/pjsip/sip_util_statefull.c +index ee7d62995e..806e2f84c1 100644 +--- a/pjsip/src/pjsip/sip_util_statefull.c ++++ b/pjsip/src/pjsip/sip_util_statefull.c +@@ -104,6 +104,11 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request2( pjsip_endpoint *endpt, + struct tsx_data *tsx_data; + pj_status_t status; + ++ /* Reset the output first, so it is also reset when the checks below ++ * fail. ++ */ ++ if (p_tsx) *p_tsx = NULL; ++ + PJ_ASSERT_RETURN(endpt && tdata && (timeout==-1 || timeout>0), PJ_EINVAL); + + /* Check that transaction layer module is registered to endpoint */ +@@ -111,8 +116,6 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request2( pjsip_endpoint *endpt, + + PJ_UNUSED_ARG(timeout); + +- if (p_tsx) *p_tsx = NULL; +- + status = pjsip_tsx_create_uac(&mod_stateful_util, tdata, &tsx); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata);