[Asterisk-code-review] res pjsip: Implement additional SIP RFCs for Google Voice tr... (asterisk[master])

Joshua Colp asteriskteam at digium.com
Thu Oct 25 05:51:04 CDT 2018


Joshua Colp has submitted this change and it was merged. ( https://gerrit.asterisk.org/9505 )

Change subject: res_pjsip: Implement additional SIP RFCs for Google Voice trunk compatability
......................................................................

res_pjsip: Implement additional SIP RFCs for Google Voice trunk compatability

This change implements a few different generic things which were brought
on by Google Voice SIP.

1.  The concept of flow transports have been introduced.  These are
configurable transports in pjsip.conf which can be used to reference a
flow of signaling to a target.  These have runtime configuration that can
be changed by the signaling itself (such as Service-Routes and
P-Preferred-Identity).  When used these guarantee an individual connection
(in the case of TCP or TLS) even if multiple flow transports exist to the
same target.

2.  Service-Routes (RFC 3608) support has been added to the outbound
registration module which when received will be stored on the flow
transport and used for requests referencing it.

3.  P-Associated-URI / P-Preferred-Identity (RFC 3325) support has been
added to the outbound registration module.  If a P-Associated-URI header
is received it will be used on requests as the P-Preferred-Identity.

4.  Configurable outbound extension support has been added to the outbound
registration module.  When set the extension will be placed in the
Supported header.

5.  Header parameters can now be configured on an outbound registration
which will be placed in the Contact header.

6.  Google specific OAuth / Bearer token authentication
(draft-ietf-sipcore-sip-authn-02) has been added to the outbound
registration module.

All functionality changes are controlled by pjsip.conf configuration
options and do not affect non-configured pjsip endpoints otherwise.

ASTERISK-27971 #close

Change-Id: Id214c2d1c550a41fcf564b7df8f3da7be565bd58
---
M configs/samples/pjsip.conf.sample
M configure
M configure.ac
A contrib/ast-db-manage/config/versions/465f47f880be_add_pjsip_google_voice_sip_options.py
M include/asterisk/autoconfig.h.in
M include/asterisk/res_pjsip.h
M res/res_pjsip.c
M res/res_pjsip/config_auth.c
M res/res_pjsip/config_transport.c
M res/res_pjsip_outbound_authenticator_digest.c
M res/res_pjsip_outbound_publish.c
M res/res_pjsip_outbound_registration.c
M res/res_pjsip_session.c
M third-party/pjproject/configure.m4
A third-party/pjproject/patches/0020-oauth.patch
A third-party/pjproject/patches/0030-allow-disabling-of-connection-reuse.patch
16 files changed, 1,190 insertions(+), 52 deletions(-)

Approvals:
  George Joseph: Looks good to me, approved
  Joshua Colp: Approved for Submit



diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index fa16557..dc0679b 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -127,7 +127,7 @@
 ;
 ;[transport-udp]
 ;type=transport
-;protocol=udp    ;udp,tcp,tls,ws,wss
+;protocol=udp    ;udp,tcp,tls,ws,wss,flow
 ;bind=0.0.0.0
 
 ; UDP transport behind NAT
@@ -158,6 +158,18 @@
 ;cipher=ADH-AES256-SHA,ADH-AES128-SHA
 ;method=tlsv1
 
+; Example flow transport
+;
+; A flow transport is used to reference a flow of signaling with a specific
+; target. All endpoints or other objects that reference the transport will use
+; the same underlying transport and can share runtime discovered transport
+; configuration (such as service routes). The protocol in use will be determined
+; based on the URI used to establish the connection. Currently only TCP and TLS
+; are supported.
+;
+;[transport-flow]
+;type=transport
+;protocol=flow
 
 ;===============OUTBOUND REGISTRATION WITH OUTBOUND AUTHENTICATION============
 ;
diff --git a/configure b/configure
index 6b71e19..47cf7e7 100755
--- a/configure
+++ b/configure
@@ -930,6 +930,14 @@
 POPT_DIR
 POPT_INCLUDE
 POPT_LIB
+PBX_PJSIP_OAUTH_AUTHENTICATION
+PJSIP_OAUTH_AUTHENTICATION_DIR
+PJSIP_OAUTH_AUTHENTICATION_INCLUDE
+PJSIP_OAUTH_AUTHENTICATION_LIB
+PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE
+PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR
+PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_INCLUDE
+PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_LIB
 PBX_PJSIP_ENDPOINT_COMPACT_FORM
 PJSIP_ENDPOINT_COMPACT_FORM_DIR
 PJSIP_ENDPOINT_COMPACT_FORM_INCLUDE
@@ -9470,6 +9478,12 @@
 $as_echo "#define HAVE_PJSIP_ENDPOINT_COMPACT_FORM 1" >>confdefs.h
 
 
+$as_echo "#define HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE 1" >>confdefs.h
+
+
+$as_echo "#define HAVE_PJSIP_OAUTH_AUTHENTICATION 1" >>confdefs.h
+
+
 
 
 
@@ -11624,6 +11638,30 @@
 
 
 
+
+PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DESCRIP="PJSIP Transport Connection Reuse Disabling"
+PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_OPTION=pjsip
+PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE=0
+
+
+
+
+
+
+
+PJSIP_OAUTH_AUTHENTICATION_DESCRIP="PJSIP OAuth Authentication Support"
+PJSIP_OAUTH_AUTHENTICATION_OPTION=pjsip
+PJSIP_OAUTH_AUTHENTICATION_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_OAUTH_AUTHENTICATION=0
+
+
+
+
+
+
 fi
 
 
@@ -25577,6 +25615,86 @@
 	CPPFLAGS="${saved_cppflags}"
     fi
 
+
+    if test "x${PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE}" != "x1" -a "${USE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE}" != "no"; then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if \"struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;\" compiles using pjsip.h" >&5
+$as_echo_n "checking if \"struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;\" compiles using pjsip.h... " >&6; }
+	saved_cppflags="${CPPFLAGS}"
+	if test "x${PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR}" != "x"; then
+	    PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_INCLUDE="-I${PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR}/include"
+	fi
+	CPPFLAGS="${CPPFLAGS} ${PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_INCLUDE}"
+
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+ #include <pjsip.h>
+int
+main ()
+{
+ struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE=1
+
+$as_echo "#define HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE 1" >>confdefs.h
+
+
+
+else
+         { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+	CPPFLAGS="${saved_cppflags}"
+    fi
+
+
+    if test "x${PBX_PJSIP_OAUTH_AUTHENTICATION}" != "x1" -a "${USE_PJSIP_OAUTH_AUTHENTICATION}" != "no"; then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if \"struct pjsip_oauth_credential credential;\" compiles using pjsip.h" >&5
+$as_echo_n "checking if \"struct pjsip_oauth_credential credential;\" compiles using pjsip.h... " >&6; }
+	saved_cppflags="${CPPFLAGS}"
+	if test "x${PJSIP_OAUTH_AUTHENTICATION_DIR}" != "x"; then
+	    PJSIP_OAUTH_AUTHENTICATION_INCLUDE="-I${PJSIP_OAUTH_AUTHENTICATION_DIR}/include"
+	fi
+	CPPFLAGS="${CPPFLAGS} ${PJSIP_OAUTH_AUTHENTICATION_INCLUDE}"
+
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+ #include <pjsip.h>
+int
+main ()
+{
+ struct pjsip_oauth_credential credential;;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJSIP_OAUTH_AUTHENTICATION=1
+
+$as_echo "#define HAVE_PJSIP_OAUTH_AUTHENTICATION 1" >>confdefs.h
+
+
+
+else
+         { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+	CPPFLAGS="${saved_cppflags}"
+    fi
+
       LIBS="${saved_libs}"
       CPPFLAGS="${saved_cppflags}"
 
diff --git a/configure.ac b/configure.ac
index dd0c8ed..73727b9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -537,6 +537,8 @@
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TSX_LAYER_FIND_TSX2], [pjsip_tsx_layer_find_tsx2 support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS], [PJSIP INVITE Accept Multiple SDP Answers], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_ENDPOINT_COMPACT_FORM], [PJSIP Compact Form Support on Endpoint], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], [PJSIP Transport Connection Reuse Disabling], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_OAUTH_AUTHENTICATION], [PJSIP OAuth Authentication Support], [PJPROJECT], [pjsip])
 fi
 
 AST_EXT_LIB_SETUP([POPT], [popt], [popt])
@@ -2376,6 +2378,8 @@
       AST_C_COMPILE_CHECK([PJSIP_TLS_TRANSPORT_PROTO], [struct pjsip_tls_setting setting; int proto; proto = setting.proto;], [pjsip.h])
       AST_C_COMPILE_CHECK([PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS], [pjsip_cfg()->endpt.accept_multiple_sdp_answers = 0;], [pjsip.h])
       AST_C_COMPILE_CHECK([PJSIP_ENDPOINT_COMPACT_FORM], [pjsip_cfg()->endpt.use_compact_form = PJ_TRUE;], [pjsip.h])
+      AST_C_COMPILE_CHECK([PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], [struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;], [pjsip.h])
+      AST_C_COMPILE_CHECK([PJSIP_OAUTH_AUTHENTICATION], [struct pjsip_oauth_credential credential;], [pjsip.h])
       LIBS="${saved_libs}"
       CPPFLAGS="${saved_cppflags}"
 
diff --git a/contrib/ast-db-manage/config/versions/465f47f880be_add_pjsip_google_voice_sip_options.py b/contrib/ast-db-manage/config/versions/465f47f880be_add_pjsip_google_voice_sip_options.py
new file mode 100644
index 0000000..0f9e8b9
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/465f47f880be_add_pjsip_google_voice_sip_options.py
@@ -0,0 +1,115 @@
+"""add pjsip google voice sip options
+
+Revision ID: 465f47f880be
+Revises: 7f85dd44c775
+Create Date: 2018-09-25 17:26:12.892161
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '465f47f880be'
+down_revision = '7f85dd44c775'
+
+from alembic import op
+from sqlalchemy.dialects.postgresql import ENUM
+import sqlalchemy as sa
+
+AST_BOOL_NAME = 'ast_bool_values'
+# We'll just ignore the n/y and f/t abbreviations as Asterisk does not write
+# those aliases.
+AST_BOOL_VALUES = [ '0', '1',
+                    'off', 'on',
+                    'false', 'true',
+                    'no', 'yes' ]
+
+PJSIP_TRANSPORT_PROTOCOL_OLD_NAME = 'pjsip_transport_protocol_values'
+PJSIP_TRANSPORT_PROTOCOL_NEW_NAME = 'pjsip_transport_protocol_values_v2'
+
+PJSIP_TRANSPORT_PROTOCOL_OLD_VALUES = ['udp', 'tcp', 'tls', 'ws', 'wss']
+PJSIP_TRANSPORT_PROTOCOL_NEW_VALUES = ['udp', 'tcp', 'tls', 'ws', 'wss', 'flow']
+
+PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE = sa.Enum(*PJSIP_TRANSPORT_PROTOCOL_OLD_VALUES,
+                                            name=PJSIP_TRANSPORT_PROTOCOL_OLD_NAME)
+PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE = sa.Enum(*PJSIP_TRANSPORT_PROTOCOL_NEW_VALUES,
+                                            name=PJSIP_TRANSPORT_PROTOCOL_NEW_NAME)
+
+PJSIP_AUTH_TYPE_OLD_NAME = 'pjsip_auth_type_values'
+PJSIP_AUTH_TYPE_NEW_NAME = 'pjsip_auth_type_values_v2'
+
+PJSIP_AUTH_TYPE_OLD_VALUES = ['md5', 'userpass']
+PJSIP_AUTH_TYPE_NEW_VALUES = ['md5', 'userpass', 'google_oauth']
+
+PJSIP_AUTH_TYPE_OLD_TYPE = sa.Enum(*PJSIP_AUTH_TYPE_OLD_VALUES,
+                                   name=PJSIP_AUTH_TYPE_OLD_NAME)
+PJSIP_AUTH_TYPE_NEW_TYPE = sa.Enum(*PJSIP_AUTH_TYPE_NEW_VALUES,
+                                   name=PJSIP_AUTH_TYPE_NEW_NAME)
+
+
+def upgrade():
+    if op.get_context().bind.dialect.name == 'postgresql':
+        enum = PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE
+        enum.create(op.get_bind(), checkfirst=False)
+        op.execute('ALTER TABLE ps_transports ALTER COLUMN protocol TYPE'
+                   ' ' + PJSIP_TRANSPORT_PROTOCOL_NEW_NAME + ' USING'
+                   ' protocol::text::' + PJSIP_TRANSPORT_PROTOCOL_NEW_NAME)
+        ENUM(name=PJSIP_TRANSPORT_PROTOCOL_OLD_NAME).drop(op.get_bind(), checkfirst=False)
+
+        enum = PJSIP_AUTH_TYPE_NEW_TYPE
+        enum.create(op.get_bind(), checkfirst=False)
+        op.execute('ALTER TABLE ps_auths ALTER COLUMN auth_type TYPE'
+                   ' ' + PJSIP_AUTH_TYPE_NEW_NAME + ' USING'
+                   ' auth_type::text::' + PJSIP_AUTH_TYPE_NEW_NAME)
+        ENUM(name=PJSIP_AUTH_TYPE_OLD_NAME).drop(op.get_bind(), checkfirst=False)
+    else:
+        op.alter_column('ps_transports', 'protocol',
+                        type_=PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE,
+                        existing_type=PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE)
+        op.alter_column('ps_auths', 'auth_type',
+                        type_=PJSIP_AUTH_TYPE_NEW_TYPE,
+                        existing_type=PJSIP_AUTH_TYPE_OLD_TYPE)
+
+    # ast_bool_values have already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False)
+
+    op.add_column('ps_registrations', sa.Column('support_outbound', ast_bool_values))
+    op.add_column('ps_registrations', sa.Column('contact_header_params', sa.String(255)))
+
+    op.add_column('ps_auths', sa.Column('refresh_token', sa.String(255)))
+    op.add_column('ps_auths', sa.Column('oauth_clientid', sa.String(255)))
+    op.add_column('ps_auths', sa.Column('oauth_secret', sa.String(255)))
+
+def downgrade():
+    # First we need to ensure that columns are not using the enum values
+    # that are going away.
+    op.execute("UPDATE ps_transports SET protocol='udp' WHERE protocol='flow'")
+    op.execute("UPDATE ps_auths SET auth_type='userpass' WHERE auth_type='google_oauth'")
+
+    if op.get_context().bind.dialect.name == 'postgresql':
+        enum = PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE
+        enum.create(op.get_bind(), checkfirst=False)
+        op.execute('ALTER TABLE ps_transports ALTER COLUMN protocol TYPE'
+                   ' ' + PJSIP_TRANSPORT_PROTOCOL_OLD_NAME + ' USING'
+                   ' protocol::text::' + PJSIP_TRANSPORT_PROTOCOL_OLD_NAME)
+        ENUM(name=PJSIP_TRANSPORT_PROTOCOL_NEW_NAME).drop(op.get_bind(), checkfirst=False)
+
+        enum = PJSIP_AUTH_TYPE_OLD_TYPE
+        enum.create(op.get_bind(), checkfirst=False)
+        op.execute('ALTER TABLE ps_auths ALTER COLUMN auth_type TYPE'
+                   ' ' + PJSIP_AUTH_TYPE_OLD_NAME + ' USING'
+                   ' auth_type::text::' + PJSIP_AUTH_TYPE_OLD_NAME)
+        ENUM(name=PJSIP_AUTH_TYPE_NEW_NAME).drop(op.get_bind(), checkfirst=False)
+    else:
+        op.alter_column('ps_transports', 'protocol',
+                        type_=PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE,
+                        existing_type=PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE)
+        op.alter_column('ps_auths', 'auth_type',
+                        type_=PJSIP_AUTH_TYPE_OLD_TYPE,
+                        existing_type=PJSIP_AUTH_TYPE_NEW_TYPE)
+
+    op.drop_column('ps_registrations', 'support_outbound')
+    op.drop_column('ps_registrations', 'contact_header_params')
+
+    op.drop_column('ps_auths', 'refresh_token')
+    op.drop_column('ps_auths', 'oauth_clientid')
+    op.drop_column('ps_auths', 'oauth_secret')
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index 30bb907..cce24eb 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -640,12 +640,19 @@
    support feature. */
 #undef HAVE_PJSIP_INV_SESSION_REF
 
+/* Define if your system has the PJSIP_OAUTH_AUTHENTICATION headers. */
+#undef HAVE_PJSIP_OAUTH_AUTHENTICATION
+
 /* Define if your system has the PJSIP_REPLACE_MEDIA_STREAM headers. */
 #undef HAVE_PJSIP_REPLACE_MEDIA_STREAM
 
 /* Define if your system has the PJSIP_TLS_TRANSPORT_PROTO headers. */
 #undef HAVE_PJSIP_TLS_TRANSPORT_PROTO
 
+/* Define if your system has the PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE
+   headers. */
+#undef HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE
+
 /* Define to 1 if PJPROJECT has the pjsip_tsx_layer_find_tsx2 support feature.
    */
 #undef HAVE_PJSIP_TSX_LAYER_FIND_TSX2
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index b323bc1..20af039 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -63,6 +63,8 @@
 /*! \brief Maximum number of ciphers supported for a TLS transport */
 #define SIP_TLS_MAX_CIPHERS 64
 
+AST_VECTOR(ast_sip_service_route_vector, char *);
+
 /*!
  * \brief Structure for SIP transport information
  */
@@ -124,6 +126,21 @@
 	 * \since 13.18.0
 	 */
 	struct ast_sockaddr external_media_address;
+	/*!
+	 * Set when this transport is a flow of signaling to a target
+	 * \since 17.0.0
+	 */
+	int flow;
+	/*!
+	 * The P-Preferred-Identity to use on traffic using this transport
+	 * \since 17.0.0
+	 */
+	char *preferred_identity;
+	/*!
+	 * The Service Routes to use on traffic using this transport
+	 * \since 17.0.0
+	 */
+	struct ast_sip_service_route_vector *service_routes;
 };
 
 #define ast_sip_transport_is_nonlocal(transport_state, addr) \
@@ -214,6 +231,8 @@
 	int allow_reload;
 	/*! Automatically send requests out the same transport requests have come in on */
 	int symmetric_transport;
+	/*! This is a flow to another target */
+	int flow;
 };
 
 #define SIP_SORCERY_DOMAIN_ALIAS_TYPE "domain_alias"
@@ -400,6 +419,8 @@
 	AST_SIP_AUTH_TYPE_USER_PASS,
 	/*! Credentials stored as an MD5 sum */
 	AST_SIP_AUTH_TYPE_MD5,
+	/*! Google Oauth */
+	AST_SIP_AUTH_TYPE_GOOGLE_OAUTH,
 	/*! Credentials not stored this is a fake auth */
 	AST_SIP_AUTH_TYPE_ARTIFICIAL
 };
@@ -418,6 +439,12 @@
 		AST_STRING_FIELD(auth_pass);
 		/*! Authentication credentials in MD5 format (hash of user:realm:pass) */
 		AST_STRING_FIELD(md5_creds);
+		/*! Refresh token to use for OAuth authentication */
+		AST_STRING_FIELD(refresh_token);
+		/*! Client ID to use for OAuth authentication */
+		AST_STRING_FIELD(oauth_clientid);
+		/*! Secret to use for OAuth authentication */
+		AST_STRING_FIELD(oauth_secret);
 	);
 	/*! The time period (in seconds) that a nonce may be reused */
 	unsigned int nonce_lifetime;
@@ -2952,6 +2979,8 @@
  * \param selector The selector to be populated
  * \retval 0 success
  * \retval -1 failure
+ *
+ * \note The transport selector must be unreffed using ast_sip_tpselector_unref
  */
 int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector);
 
@@ -2963,10 +2992,81 @@
  * \param selector The selector to be populated
  * \retval 0 success
  * \retval -1 failure
+ *
+ * \note The transport selector must be unreffed using ast_sip_tpselector_unref
  */
 int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector);
 
 /*!
+ * \brief Unreference a pjsip_tpselector
+ * \since 17.0.0
+ *
+ * \param selector The selector to be unreffed
+ */
+void ast_sip_tpselector_unref(pjsip_tpselector *selector);
+
+/*!
+ * \brief Sets the PJSIP transport on a child transport
+ * \since 17.0.0
+ *
+ * \param transport_name The name of the transport to be updated
+ * \param transport The PJSIP transport
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_transport_state_set_transport(const char *transport_name, pjsip_transport *transport);
+
+/*!
+ * \brief Sets the P-Preferred-Identity on a child transport
+ * \since 17.0.0
+ *
+ * \param transport_name The name of the transport to be set on
+ * \param identity The P-Preferred-Identity to use on requests on this transport
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_transport_state_set_preferred_identity(const char *transport_name, const char *identity);
+
+/*!
+ * \brief Sets the service routes on a child transport
+ * \since 17.0.0
+ *
+ * \param transport_name The name of the transport to be set on
+ * \param service_routes A vector of service routes
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note This assumes ownership of the service routes in both success and failure scenarios
+ */
+int ast_sip_transport_state_set_service_routes(const char *transport_name, struct ast_sip_service_route_vector *service_routes);
+
+/*!
+ * \brief Apply the configuration for a transport to an outgoing message
+ * \since 17.0.0
+ *
+ * \param transport_name The name of the transport to apply configuration from
+ * \param tdata The SIP message
+ */
+void ast_sip_message_apply_transport(const char *transport_name, pjsip_tx_data *tdata);
+
+/*!
+ * \brief Allocate a vector of service routes
+ * \since 17.0.0
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_service_route_vector *ast_sip_service_route_vector_alloc(void);
+
+/*!
+ * \brief Destroy a vector of service routes
+ * \since 17.0.0
+ *
+ * \param service_routes A vector of service routes
+ */
+void ast_sip_service_route_vector_destroy(struct ast_sip_service_route_vector *service_routes);
+
+/*!
  * \brief Set name and number information on an identity header.
  *
  * \param pool Memory pool to use for string duplication
@@ -3033,6 +3133,9 @@
  * This API calls ast_sip_get_transport_name(endpoint, dlg->target) and if the result is
  * non-NULL, calls pjsip_dlg_set_transport.  If 'selector' is non-NULL, it is updated with
  * the selector used.
+ *
+ * \note
+ * It is the responsibility of the caller to unref the passed in selector if one is provided.
  */
 int ast_sip_dlg_set_transport(const struct ast_sip_endpoint *endpoint, pjsip_dialog *dlg,
 	pjsip_tpselector *selector);
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index c2e77cb..a61208f 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1136,11 +1136,12 @@
 						This option specifies which of the password style config options should be read
 						when trying to authenticate an endpoint inbound request. If set to <literal>userpass</literal>
 						then we'll read from the 'password' option. For <literal>md5</literal> we'll read
-						from 'md5_cred'.
+						from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the refresh_token/oauth_clientid/oauth_secret fields.
 						</para>
 						<enumlist>
 							<enum name="md5"/>
 							<enum name="userpass"/>
+							<enum name="google_oauth"/>
 						</enumlist>
 					</description>
 				</configOption>
@@ -1155,6 +1156,15 @@
 					<synopsis>Plain text password used for authentication.</synopsis>
 					<description><para>Only used when auth_type is <literal>userpass</literal>.</para></description>
 				</configOption>
+				<configOption name="refresh_token">
+					<synopsis>OAuth 2.0 refresh token</synopsis>
+				</configOption>
+				<configOption name="oauth_clientid">
+					<synopsis>OAuth 2.0 application's client id</synopsis>
+				</configOption>
+				<configOption name="oauth_secret">
+					<synopsis>OAuth 2.0 application's secret</synopsis>
+				</configOption>
 				<configOption name="realm">
 					<synopsis>SIP realm for endpoint</synopsis>
 					<description><para>
@@ -1307,6 +1317,7 @@
 							<enum name="tls" />
 							<enum name="ws" />
 							<enum name="wss" />
+							<enum name="flow" />
 						</enumlist>
 					</description>
 				</configOption>
@@ -3277,8 +3288,13 @@
 	}
 
 	ast_sip_set_tpselector_from_ep_or_uri(endpoint, uri, selector);
+
 	pjsip_dlg_set_transport(dlg, selector);
 
+	if (selector == &sel) {
+		ast_sip_tpselector_unref(&sel);
+	}
+
 	return 0;
 }
 
@@ -3367,7 +3383,8 @@
 
 int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector)
 {
-	RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
+	int res = 0;
+	struct ast_sip_transport_state *transport_state;
 
 	transport_state = ast_sip_get_transport_state(ast_sorcery_object_get_id(transport));
 	if (!transport_state) {
@@ -3376,9 +3393,15 @@
 		return -1;
 	}
 
+	/* Only flows maintain dynamic state which needs protection */
+	if (transport_state->flow) {
+		ao2_lock(transport_state);
+	}
+
 	if (transport_state->transport) {
 		selector->type = PJSIP_TPSELECTOR_TRANSPORT;
 		selector->u.transport = transport_state->transport;
+		pjsip_transport_add_ref(selector->u.transport);
 	} else if (transport_state->factory) {
 		selector->type = PJSIP_TPSELECTOR_LISTENER;
 		selector->u.listener = transport_state->factory;
@@ -3387,12 +3410,25 @@
 		 * even if an endpoint is locked to a WebSocket transport we let the PJSIP logic
 		 * find the existing connection if available and use it.
 		 */
-		return 0;
+	} else if (transport->flow) {
+		/* This is a child of another transport, so we need to establish a new connection */
+#ifdef HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE
+		selector->disable_connection_reuse = PJ_TRUE;
+#else
+		ast_log(LOG_WARNING, "Connection reuse could not be disabled on transport '%s' as support is not available\n",
+			ast_sorcery_object_get_id(transport));
+#endif
 	} else {
-		return -1;
+		res = -1;
 	}
 
-	return 0;
+	if (transport_state->flow) {
+		ao2_unlock(transport_state);
+	}
+
+	ao2_ref(transport_state, -1);
+
+	return res;
 }
 
 int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector)
@@ -3425,6 +3461,13 @@
 	return ast_sip_set_tpselector_from_transport_name(transport_name, selector);
 }
 
+void ast_sip_tpselector_unref(pjsip_tpselector *selector)
+{
+	if (selector->type == PJSIP_TPSELECTOR_TRANSPORT && selector->u.transport) {
+		pjsip_transport_dec_ref(selector->u.transport);
+	}
+}
+
 void ast_sip_add_usereqphone(const struct ast_sip_endpoint *endpoint, pj_pool_t *pool, pjsip_uri *uri)
 {
 	pjsip_sip_uri *sip_uri;
@@ -3493,9 +3536,12 @@
 	if (sip_dialog_create_from(dlg->pool, &local_uri, endpoint->fromuser, endpoint->fromdomain, &remote_uri, &selector)) {
 		dlg->sess_count--;
 		pjsip_dlg_terminate(dlg);
+		ast_sip_tpselector_unref(&selector);
 		return NULL;
 	}
 
+	ast_sip_tpselector_unref(&selector);
+
 	/* Update the dialog with the new local URI, we do it afterwards so we can use the dialog pool for construction */
 	pj_strdup_with_null(dlg->pool, &dlg->local.info_str, &local_uri);
 	dlg->local.info->uri = pjsip_parse_uri(dlg->pool, dlg->local.info_str.ptr, dlg->local.info_str.slen, 0);
@@ -3642,12 +3688,16 @@
 		pj_strerror(*status, err, sizeof(err));
 		ast_log(LOG_ERROR, "Could not create dialog with endpoint %s. %s\n",
 				ast_sorcery_object_get_id(endpoint), err);
+		ast_sip_tpselector_unref(&selector);
 		return NULL;
 	}
 
 	dlg->sess_count++;
 	pjsip_dlg_set_transport(dlg, &selector);
 	dlg->sess_count--;
+
+	ast_sip_tpselector_unref(&selector);
+
 #ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK
 	pjsip_dlg_dec_lock(dlg);
 #endif
@@ -3817,6 +3867,7 @@
 				(int) pj_strlen(&method->name), pj_strbuf(&method->name),
 				endpoint ? ast_sorcery_object_get_id(endpoint) : "<none>");
 		pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+		ast_sip_tpselector_unref(&selector);
 		return -1;
 	}
 
@@ -3826,11 +3877,14 @@
 				(int) pj_strlen(&method->name), pj_strbuf(&method->name),
 				endpoint ? ast_sorcery_object_get_id(endpoint) : "<none>");
 		pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+		ast_sip_tpselector_unref(&selector);
 		return -1;
 	}
 
 	pjsip_tx_data_set_transport(*tdata, &selector);
 
+	ast_sip_tpselector_unref(&selector);
+
 	if (endpoint && !ast_strlen_zero(endpoint->contact_user)){
 		pjsip_contact_hdr *contact_hdr;
 		pjsip_sip_uri *contact_uri;
@@ -4385,6 +4439,10 @@
 		return -1;
 	}
 
+	if (endpoint) {
+		ast_sip_message_apply_transport(endpoint->transport, tdata);
+	}
+
 	contact = ast_sip_mod_data_get(tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT);
 
 	AST_RWLIST_RDLOCK(&supplements);
@@ -4828,6 +4886,10 @@
 	pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
 	struct ast_sip_contact *contact = ast_sip_mod_data_get(tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT);
 
+	if (sip_endpoint) {
+		ast_sip_message_apply_transport(sip_endpoint->transport, tdata);
+	}
+
 	AST_RWLIST_RDLOCK(&supplements);
 	AST_LIST_TRAVERSE(&supplements, supplement, next) {
 		if (supplement->outgoing_response && does_method_match(&cseq->method.name, supplement->method)) {
diff --git a/res/res_pjsip/config_auth.c b/res/res_pjsip/config_auth.c
index b1bf9c4..2350140 100644
--- a/res/res_pjsip/config_auth.c
+++ b/res/res_pjsip/config_auth.c
@@ -56,6 +56,13 @@
 		auth->type = AST_SIP_AUTH_TYPE_USER_PASS;
 	} else if (!strcasecmp(var->value, "md5")) {
 		auth->type = AST_SIP_AUTH_TYPE_MD5;
+	} else if (!strcasecmp(var->value, "google_oauth")) {
+#ifdef HAVE_PJSIP_OAUTH_AUTHENTICATION
+		auth->type = AST_SIP_AUTH_TYPE_GOOGLE_OAUTH;
+#else
+		ast_log(LOG_WARNING, "OAuth support is not available in the version of PJSIP in use\n");
+		return -1;
+#endif
 	} else {
 		ast_log(LOG_WARNING, "Unknown authentication storage type '%s' specified for %s\n",
 				var->value, var->name);
@@ -66,7 +73,8 @@
 
 static const char *auth_types_map[] = {
 	[AST_SIP_AUTH_TYPE_USER_PASS] = "userpass",
-	[AST_SIP_AUTH_TYPE_MD5] = "md5"
+	[AST_SIP_AUTH_TYPE_MD5] = "md5",
+	[AST_SIP_AUTH_TYPE_GOOGLE_OAUTH] = "google_oauth"
 };
 
 const char *ast_sip_auth_type_to_str(enum ast_sip_auth_type type)
@@ -106,6 +114,16 @@
 			res = -1;
 		}
 		break;
+	case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
+		if (ast_strlen_zero(auth->refresh_token)
+			|| ast_strlen_zero(auth->oauth_clientid)
+			|| ast_strlen_zero(auth->oauth_secret)) {
+			ast_log(LOG_ERROR, "'google_oauth' authentication specified but refresh_token,"
+				" oauth_clientid, or oauth_secret not specified for auth '%s'\n",
+				ast_sorcery_object_get_id(auth));
+			res = -1;
+		}
+		break;
 	case AST_SIP_AUTH_TYPE_USER_PASS:
 	case AST_SIP_AUTH_TYPE_ARTIFICIAL:
 		break;
@@ -365,6 +383,12 @@
 			"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_user));
 	ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "password",
 			"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_pass));
+	ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "refresh_token",
+			"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, refresh_token));
+	ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_clientid",
+			"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_clientid));
+	ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_secret",
+			"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_secret));
 	ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
 			"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, md5_creds));
 	ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "realm",
diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c
index 13a9ff8..a84fee0 100644
--- a/res/res_pjsip/config_transport.c
+++ b/res/res_pjsip/config_transport.c
@@ -203,6 +203,176 @@
 	.format_ami = format_ami_endpoint_transport
 };
 
+int ast_sip_transport_state_set_transport(const char *transport_name, pjsip_transport *transport)
+{
+	struct ast_sip_transport_state *transport_state;
+
+	/* To make it easier on callers we allow an empty transport name */
+	if (ast_strlen_zero(transport_name)) {
+		return 0;
+	}
+
+	transport_state = ast_sip_get_transport_state(transport_name);
+	if (!transport_state) {
+		return -1;
+	}
+
+	if (!transport_state->flow) {
+		ao2_ref(transport_state, -1);
+		return 0;
+	}
+
+	ao2_lock(transport_state);
+	if (transport_state->transport != transport) {
+		if (transport_state->transport) {
+			pjsip_transport_dec_ref(transport_state->transport);
+		}
+		transport_state->transport = transport;
+		if (transport_state->transport) {
+			pjsip_transport_add_ref(transport_state->transport);
+		}
+	}
+	ao2_unlock(transport_state);
+
+	ao2_ref(transport_state, -1);
+
+	return 0;
+}
+
+int ast_sip_transport_state_set_preferred_identity(const char *transport_name, const char *identity)
+{
+	struct ast_sip_transport_state *transport_state;
+
+	if (ast_strlen_zero(transport_name)) {
+		return 0;
+	}
+
+	transport_state = ast_sip_get_transport_state(transport_name);
+	if (!transport_state) {
+		return -1;
+	}
+
+	if (!transport_state->flow) {
+		ao2_ref(transport_state, -1);
+		return 0;
+	}
+
+	ao2_lock(transport_state);
+	ast_free(transport_state->preferred_identity);
+	transport_state->preferred_identity = ast_strdup(identity);
+	ao2_unlock(transport_state);
+
+	ao2_ref(transport_state, -1);
+
+	return 0;
+}
+
+int ast_sip_transport_state_set_service_routes(const char *transport_name, struct ast_sip_service_route_vector *service_routes)
+{
+	struct ast_sip_transport_state *transport_state;
+
+	if (ast_strlen_zero(transport_name)) {
+		ast_sip_service_route_vector_destroy(service_routes);
+		return 0;
+	}
+
+	transport_state = ast_sip_get_transport_state(transport_name);
+	if (!transport_state) {
+		ast_sip_service_route_vector_destroy(service_routes);
+		return -1;
+	}
+
+	if (!transport_state->flow) {
+		ao2_ref(transport_state, -1);
+		ast_sip_service_route_vector_destroy(service_routes);
+		return 0;
+	}
+
+	ao2_lock(transport_state);
+	ast_sip_service_route_vector_destroy(transport_state->service_routes);
+	transport_state->service_routes = service_routes;
+	ao2_unlock(transport_state);
+
+	ao2_ref(transport_state, -1);
+
+	return 0;
+}
+
+void ast_sip_message_apply_transport(const char *transport_name, pjsip_tx_data *tdata)
+{
+	struct ast_sip_transport_state *transport_state;
+
+	if (ast_strlen_zero(transport_name)) {
+		return;
+	}
+
+	/* We only currently care about requests that are of the INVITE, CANCEL, or OPTIONS
+	 * type but in the future we could support other messages.
+	 */
+	if (tdata->msg->type != PJSIP_REQUEST_MSG ||
+		(pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_invite_method) &&
+		pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_cancel_method) &&
+		pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_options_method))) {
+		return;
+	}
+
+	transport_state = ast_sip_get_transport_state(transport_name);
+	if (!transport_state) {
+		return;
+	}
+
+	if (!transport_state->flow) {
+		ao2_ref(transport_state, -1);
+		return;
+	}
+
+	ao2_lock(transport_state);
+
+	/* If a Preferred Identity has been set then add it to the request */
+	if (transport_state->preferred_identity) {
+		ast_sip_add_header(tdata, "P-Preferred-Identity", transport_state->preferred_identity);
+	}
+
+	/* If Service Routes have been set then add them to the request */
+	if (transport_state->service_routes) {
+		int idx;
+
+		for (idx = 0; idx < AST_VECTOR_SIZE(transport_state->service_routes); ++idx) {
+			char *service_route = AST_VECTOR_GET(transport_state->service_routes, idx);
+
+			ast_sip_add_header(tdata, "Route", service_route);
+		}
+	}
+
+	ao2_unlock(transport_state);
+
+	ao2_ref(transport_state, -1);
+}
+
+struct ast_sip_service_route_vector *ast_sip_service_route_vector_alloc(void)
+{
+	struct ast_sip_service_route_vector *service_routes;
+
+	service_routes = ast_calloc(1, sizeof(*service_routes));
+	if (!service_routes) {
+		return NULL;
+	}
+
+	AST_VECTOR_INIT(service_routes, 0);
+
+	return service_routes;
+}
+
+void ast_sip_service_route_vector_destroy(struct ast_sip_service_route_vector *service_routes)
+{
+	if (!service_routes) {
+		return;
+	}
+
+	AST_VECTOR_CALLBACK_VOID(service_routes, ast_free);
+	ast_free(service_routes);
+}
+
 static void set_qos(struct ast_sip_transport *transport, pj_qos_params *qos)
 {
 	int tos_as_dscp = transport->tos >> 2;
@@ -500,7 +670,7 @@
 			return 0;
 		}
 
-		if (!transport->allow_reload) {
+		if (!transport->allow_reload && !transport->flow) {
 			if (!perm_state->change_detected) {
 				perm_state->change_detected = 1;
 				ast_log(LOG_WARNING, "Transport '%s' is not reloadable, maintaining previous values\n", transport_id);
@@ -513,14 +683,16 @@
 		}
 	}
 
-	if (temp_state->state->host.addr.sa_family != PJ_AF_INET && temp_state->state->host.addr.sa_family != PJ_AF_INET6) {
-		ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", transport_id);
-		return -1;
-	}
+	if (!transport->flow) {
+		if (temp_state->state->host.addr.sa_family != PJ_AF_INET && temp_state->state->host.addr.sa_family != PJ_AF_INET6) {
+			ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", transport_id);
+			return -1;
+		}
 
-	/* Set default port if not present */
-	if (!pj_sockaddr_get_port(&temp_state->state->host)) {
-		pj_sockaddr_set_port(&temp_state->state->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060);
+		/* Set default port if not present */
+		if (!pj_sockaddr_get_port(&temp_state->state->host)) {
+			pj_sockaddr_set_port(&temp_state->state->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060);
+		}
 	}
 
 	/* Now that we know what address family we can set up a dnsmgr refresh for the external addresses if present */
@@ -558,7 +730,16 @@
 		}
 	}
 
-	if (transport->type == AST_TRANSPORT_UDP) {
+	if (transport->flow) {
+		pj_str_t address;
+
+		ast_debug(1, "Ignoring any bind configuration on transport '%s' as it is a child of another\n",
+			transport_id);
+		pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&address, "0.0.0.0"), &temp_state->state->host);
+
+		temp_state->state->flow = 1;
+		res = PJ_SUCCESS;
+	} else if (transport->type == AST_TRANSPORT_UDP) {
 
 		for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
 			if (perm_state && perm_state->state && perm_state->state->transport) {
@@ -775,18 +956,23 @@
 		return -1;
 	}
 
-	if (!strcasecmp(var->value, "udp")) {
-		transport->type = AST_TRANSPORT_UDP;
-	} else if (!strcasecmp(var->value, "tcp")) {
-		transport->type = AST_TRANSPORT_TCP;
-	} else if (!strcasecmp(var->value, "tls")) {
-		transport->type = AST_TRANSPORT_TLS;
-	} else if (!strcasecmp(var->value, "ws")) {
-		transport->type = AST_TRANSPORT_WS;
-	} else if (!strcasecmp(var->value, "wss")) {
-		transport->type = AST_TRANSPORT_WSS;
+	if (!strcasecmp(var->value, "flow")) {
+		transport->flow = 1;
 	} else {
-		return -1;
+		if (!strcasecmp(var->value, "udp")) {
+			transport->type = AST_TRANSPORT_UDP;
+		} else if (!strcasecmp(var->value, "tcp")) {
+			transport->type = AST_TRANSPORT_TCP;
+		} else if (!strcasecmp(var->value, "tls")) {
+			transport->type = AST_TRANSPORT_TLS;
+		} else if (!strcasecmp(var->value, "ws")) {
+			transport->type = AST_TRANSPORT_WS;
+		} else if (!strcasecmp(var->value, "wss")) {
+			transport->type = AST_TRANSPORT_WSS;
+		} else {
+			return -1;
+		}
+		transport->flow = 0;
 	}
 
 	state->type = transport->type;
@@ -806,7 +992,9 @@
 {
 	const struct ast_sip_transport *transport = obj;
 
-	if (ARRAY_IN_BOUNDS(transport->type, transport_types)) {
+	if (transport->flow) {
+		*buf = ast_strdup("flow");
+	} else if (ARRAY_IN_BOUNDS(transport->type, transport_types)) {
 		*buf = ast_strdup(transport_types[transport->type]);
 	}
 
@@ -1362,6 +1550,16 @@
 	trans_state = ao2_bump(state->state);
 	ao2_ref(state, -1);
 
+	/* If this is a child transport see if the transport is actually dead */
+	if (trans_state->flow) {
+		ao2_lock(trans_state);
+		if (trans_state->transport && trans_state->transport->is_shutdown == PJ_TRUE) {
+			pjsip_transport_dec_ref(trans_state->transport);
+			trans_state->transport = NULL;
+		}
+		ao2_unlock(trans_state);
+	}
+
 	return trans_state;
 }
 
diff --git a/res/res_pjsip_outbound_authenticator_digest.c b/res/res_pjsip_outbound_authenticator_digest.c
index 5f6d994..b1011b0 100644
--- a/res/res_pjsip_outbound_authenticator_digest.c
+++ b/res/res_pjsip_outbound_authenticator_digest.c
@@ -83,6 +83,9 @@
 			pj_cstr(&auth_creds[i].data, auths[i]->md5_creds);
 			auth_creds[i].data_type = PJSIP_CRED_DATA_DIGEST;
 			break;
+		case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
+			/* nothing to do. handled seperately in res_pjsip_outbound_registration */
+			break;
 		case AST_SIP_AUTH_TYPE_ARTIFICIAL:
 			ast_log(LOG_ERROR, "Trying to set artificial outbound auth credentials shouldn't happen.\n");
 			break;
diff --git a/res/res_pjsip_outbound_publish.c b/res/res_pjsip_outbound_publish.c
index 4894e55..f8594ab 100644
--- a/res/res_pjsip_outbound_publish.c
+++ b/res/res_pjsip_outbound_publish.c
@@ -428,9 +428,11 @@
 {
 	if (!ast_strlen_zero(publisher->owner->publish->transport)) {
 		pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+
 		ast_sip_set_tpselector_from_transport_name(
 			publisher->owner->publish->transport, &selector);
 		pjsip_tx_data_set_transport(tdata, &selector);
+		ast_sip_tpselector_unref(&selector);
 	}
 }
 
diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c
index 0d815ad..648deee 100644
--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -38,6 +38,8 @@
 #include "asterisk/threadpool.h"
 #include "asterisk/statsd.h"
 #include "res_pjsip/include/res_pjsip_private.h"
+#include "asterisk/vector.h"
+#include "asterisk/pbx.h"
 
 /*** DOCUMENTATION
 	<configInfo name="res_pjsip_outbound_registration" language="en_US">
@@ -76,6 +78,9 @@
 				<configOption name="contact_user">
 					<synopsis>Contact User to use in request</synopsis>
 				</configOption>
+				<configOption name="contact_header_params">
+					<synopsis>Header parameters to place in the Contact header</synopsis>
+				</configOption>
 				<configOption name="expiration" default="3600">
 					<synopsis>Expiration time for registrations in seconds</synopsis>
 				</configOption>
@@ -164,13 +169,16 @@
 					<synopsis>Must be of type 'registration'.</synopsis>
 				</configOption>
 				<configOption name="support_path">
-					<synopsis>Enables Path support for outbound REGISTER requests.</synopsis>
+					<synopsis>Enables advertising SIP Path support for outbound REGISTER requests.</synopsis>
 					<description><para>
 						When this option is enabled, outbound REGISTER requests will advertise
 						support for Path headers so that intervening proxies can add to the Path
 						header as necessary.
 					</para></description>
 				</configOption>
+				<configOption name="support_outbound">
+					<synopsis>Enables advertising SIP Outbound support (RFC5626) for outbound REGISTER requests.</synopsis>
+				</configOption>
 			</configObject>
 		</configFile>
 	</configInfo>
@@ -224,6 +232,10 @@
 	</manager>
  ***/
 
+/* forward declarations */
+static int set_outbound_initial_authentication_credentials(pjsip_regc *regc,
+		const struct ast_sip_auth_vector *auth_vector);
+
 /*! \brief Some thread local storage used to determine if the running thread invoked the callback */
 AST_THREADSTORAGE(register_callback_invoked);
 
@@ -291,6 +303,8 @@
 		AST_STRING_FIELD(client_uri);
 		/*! \brief Optional user for contact header */
 		AST_STRING_FIELD(contact_user);
+		/*! \bried Optional header parameters for contact */
+		AST_STRING_FIELD(contact_header_params);
 		/*! \brief Explicit transport to use for registration */
 		AST_STRING_FIELD(transport);
 		/*! \brief Outbound proxy to use */
@@ -316,6 +330,8 @@
 	struct ast_sip_auth_vector outbound_auths;
 	/*! \brief Whether Path support is enabled */
 	unsigned int support_path;
+	/*! \brief Whether Outbound support is enabled */
+	unsigned int support_outbound;
 };
 
 /*! \brief Outbound registration client state information (persists for lifetime of regc) */
@@ -347,6 +363,8 @@
 	unsigned int auth_rejection_permanent;
 	/*! \brief Determines whether SIP Path support should be advertised */
 	unsigned int support_path;
+	/*! \brief Determines whether SIP Outbound support should be advertised */
+	unsigned int support_outbound;
 	/*! CSeq number of last sent auth request. */
 	unsigned int auth_cseq;
 	/*! \brief Serializer for stuff and things */
@@ -520,6 +538,7 @@
 }
 
 static pj_str_t PATH_NAME = { "path", 4 };
+static pj_str_t OUTBOUND_NAME = { "outbound", 8 };
 
 /*! \brief Helper function which sends a message and cleans up, if needed, on failure */
 static pj_status_t registration_client_send(struct sip_outbound_registration_client_state *client_state,
@@ -545,6 +564,8 @@
 	 */
 	ast_sip_set_tpselector_from_transport_name(client_state->transport_name, &selector);
 	pjsip_regc_set_transport(client_state->client, &selector);
+	ast_sip_tpselector_unref(&selector);
+
 	status = pjsip_regc_send(client_state->client, tdata);
 
 	/* If the attempt to send the message failed and the callback was not invoked we need to
@@ -557,12 +578,58 @@
 	return status;
 }
 
+/*! \brief Helper function to add string to Supported header */
+static int add_to_supported_header(pjsip_tx_data *tdata, pj_str_t *name)
+{
+	pjsip_supported_hdr *hdr;
+
+	hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL);
+	if (!hdr) {
+		/* insert a new Supported header */
+		hdr = pjsip_supported_hdr_create(tdata->pool);
+		if (!hdr) {
+			pjsip_tx_data_dec_ref(tdata);
+			return 0;
+		}
+
+		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
+	}
+
+	/* add on to the existing Supported header */
+	pj_strassign(&hdr->values[hdr->count++], name);
+
+	return 1;
+}
+
+/*! \brief Helper function to add configured supported headers */
+static int add_configured_supported_headers(struct sip_outbound_registration_client_state *client_state, pjsip_tx_data *tdata)
+{
+	if (client_state->support_path) {
+		if (!add_to_supported_header(tdata, &PATH_NAME)) {
+			return 0;
+		}
+	}
+
+	if (client_state->support_outbound) {
+		if (!add_to_supported_header(tdata, &OUTBOUND_NAME)) {
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
 /*! \brief Callback function for registering */
 static int handle_client_registration(void *data)
 {
 	RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup);
 	pjsip_tx_data *tdata;
 
+	if (set_outbound_initial_authentication_credentials(client_state->client, &client_state->outbound_auths)) {
+		ast_log(LOG_WARNING, "Failed to set initial authentication credentials\n");
+		return -1;
+	}
+
 	if (client_state->status == SIP_REGISTRATION_STOPPED
 		|| pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS) {
 		return 0;
@@ -578,23 +645,9 @@
 			(int) info.client_uri.slen, info.client_uri.ptr);
 	}
 
-	if (client_state->support_path) {
-		pjsip_supported_hdr *hdr;
-
-		hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL);
-		if (!hdr) {
-			/* insert a new Supported header */
-			hdr = pjsip_supported_hdr_create(tdata->pool);
-			if (!hdr) {
-				pjsip_tx_data_dec_ref(tdata);
-				return -1;
-			}
-
-			pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
-		}
-
-		/* add on to the existing Supported header */
-		pj_strassign(&hdr->values[hdr->count++], &PATH_NAME);
+	if (!add_configured_supported_headers(client_state, tdata)) {
+		ast_log(LOG_WARNING, "Failed to set supported headers\n");
+		return -1;
 	}
 
 	registration_client_send(client_state, tdata);
@@ -707,6 +760,7 @@
 			update_client_state_status(client_state, SIP_REGISTRATION_STOPPING);
 			client_state->destroy = 1;
 			if (pjsip_regc_unregister(client_state->client, &tdata) == PJ_SUCCESS
+				&& add_configured_supported_headers(client_state, tdata)
 				&& registration_client_send(client_state, tdata) == PJ_SUCCESS) {
 				ao2_ref(client_state, -1);
 				return 0;
@@ -885,6 +939,75 @@
 	ao2_ref(monitor, -1);
 }
 
+static void save_response_fields_to_transport(struct registration_response *response)
+{
+	static const pj_str_t associated_uri_str = { "P-Associated-URI", 16 };
+	static const pj_str_t service_route_str = { "Service-Route", 13 };
+	pjsip_hdr *header = NULL;
+	pjsip_msg *msg = response->rdata->msg_info.msg;
+	struct ast_sip_service_route_vector *service_routes = NULL;
+
+	/* If no transport is specified then we can't update any */
+	if (ast_strlen_zero(response->client_state->transport_name)) {
+		return;
+	}
+
+	ast_sip_transport_state_set_transport(response->client_state->transport_name, response->rdata->tp_info.transport);
+
+	while ((header = pjsip_msg_find_hdr_by_name(msg, &service_route_str, header ? header->next : NULL))) {
+		char *service_route;
+		size_t size;
+
+		/* The below code takes the approach that if we can't store all service routes then we
+		 * store none at all. This gives a predictable failure condition instead of storing a
+		 * partial list and having partial route headers.
+		 */
+		size = pj_strlen(&((pjsip_generic_string_hdr*)header)->hvalue) + 1;
+		service_route = ast_malloc(size);
+		if (!service_route) {
+			if (service_routes) {
+				ast_sip_service_route_vector_destroy(service_routes);
+				service_routes = NULL;
+			}
+			break;
+		}
+
+		ast_copy_pj_str(service_route, &((pjsip_generic_string_hdr*)header)->hvalue, size);
+
+		if (!service_routes) {
+			service_routes = ast_sip_service_route_vector_alloc();
+			if (!service_routes) {
+				ast_free(service_route);
+				break;
+			}
+		}
+
+		if (AST_VECTOR_APPEND(service_routes, service_route)) {
+			ast_free(service_route);
+			ast_sip_service_route_vector_destroy(service_routes);
+			service_routes = NULL;
+			break;
+		}
+	}
+
+	/* If any service routes were handled then store them on the transport */
+	if (service_routes) {
+		ast_sip_transport_state_set_service_routes(response->client_state->transport_name, service_routes);
+	}
+
+	/* If an associated URI is present in the response we need to use it on any outgoing
+	 * traffic on the transport.
+	 */
+	header = pjsip_msg_find_hdr_by_name(msg, &associated_uri_str, NULL);
+	if (header) {
+		char value[pj_strlen(&((pjsip_generic_string_hdr*)header)->hvalue) + 1];
+
+		ast_copy_pj_str(value, &((pjsip_generic_string_hdr*)header)->hvalue, sizeof(value));
+		ast_sip_transport_state_set_preferred_identity(response->client_state->transport_name, value);
+	}
+}
+
+
 /*! \brief Callback function for handling a response to a registration attempt */
 static int handle_registration_response(void *data)
 {
@@ -964,6 +1087,8 @@
 				registration_transport_shutdown_cb, response->client_state->registration_name,
 				monitor_matcher);
 		}
+
+		save_response_fields_to_transport(response);
 	} else if (response->client_state->destroy) {
 		/* We need to deal with the pending destruction instead. */
 	} else if (response->retry_after) {
@@ -1190,7 +1315,7 @@
 
 /*! \brief Helper function which populates a pj_str_t with a contact header */
 static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const char *user,
-	const pj_str_t *target, pjsip_tpselector *selector, const char *line)
+	const pj_str_t *target, pjsip_tpselector *selector, const char *line, const char *header_params)
 {
 	pj_str_t tmp, local_addr;
 	pjsip_uri *uri;
@@ -1234,7 +1359,7 @@
 
 	contact->ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
 	contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
-		"<%s:%s@%s%.*s%s:%d%s%s%s%s>",
+		"<%s:%s@%s%.*s%s:%d%s%s%s%s>%s%s",
 		((pjsip_transport_get_flag_from_type(type) & PJSIP_TRANSPORT_SECURE) && PJSIP_URI_SCHEME_IS_SIPS(uri)) ? "sips" : "sip",
 		user,
 		(type & PJSIP_TRANSPORT_IPV6) ? "[" : "",
@@ -1245,7 +1370,9 @@
 		(type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? ";transport=" : "",
 		(type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? pjsip_transport_get_type_name(type) : "",
 		!ast_strlen_zero(line) ? ";line=" : "",
-		S_OR(line, ""));
+		S_OR(line, ""),
+		!ast_strlen_zero(header_params) ? ";" : "",
+		S_OR(header_params, ""));
 
 	return 0;
 }
@@ -1283,6 +1410,124 @@
 	return rc;
 }
 
+/* \brief Get google oauth2 access token using refresh token */
+static const char *fetch_google_access_token(const struct ast_sip_auth *auth)
+{
+	char *cmd = NULL;
+	const char *token;
+	const char *url = "https://www.googleapis.com/oauth2/v3/token";
+	char buf[4096];
+	int res;
+	struct ast_json_error error;
+	struct ast_json *json;
+
+	/* set timeout to be shorter than default 180s (also checks func_curl is available) */
+	if (ast_func_write(NULL, "CURLOPT(conntimeout)", "10")) {
+		ast_log(LOG_ERROR, "CURL is unavailable. This is required for Google OAuth 2.0 authentication. Please ensure it is loaded.\n");
+		return NULL;
+	}
+
+	res = ast_asprintf(&cmd,
+		"CURL(%s,client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token)",
+		url, auth->oauth_clientid, auth->oauth_secret, auth->refresh_token);
+	if (res < 0) {
+		return NULL;
+	}
+
+	ast_debug(2, "Performing Google OAuth 2.0 authentication using command: %s\n", cmd);
+
+	buf[0] = '\0';
+	res = ast_func_read(NULL, cmd, buf, sizeof(buf));
+	ast_free(cmd);
+	if (res) {
+		ast_log(LOG_ERROR, "Could not retrieve Google OAuth 2.0 authentication\n");
+		return NULL;
+	}
+
+	ast_debug(2, "Google OAuth 2.0 authentication returned: %s\n", buf);
+
+	json = ast_json_load_string(buf, &error);
+	if (!json) {
+		ast_log(LOG_ERROR, "Could not parse Google OAuth 2.0 authentication: %d(%d) %s: '%s'\n",
+			error.line, error.column, error.text, buf);
+		return NULL;
+	}
+
+	token = ast_json_string_get(ast_json_object_get(json, "access_token"));
+	if (!token) {
+		ast_log(LOG_ERROR, "Could not find Google OAuth 2.0 access_token in: '%s'\n",
+			buf);
+	}
+	token = ast_strdup(token);
+	ast_json_unref(json);
+	return token;
+}
+
+/*!
+ * \internal
+ * \brief Set pjsip registration context with any authentication credentials that need to be
+ * sent in the initial registration request
+ *
+ * \param regc The pjsip registration context
+ * \param auth_vector The vector of configured authentication credentials
+ */
+static int set_outbound_initial_authentication_credentials(pjsip_regc *regc,
+		const struct ast_sip_auth_vector *auth_vector)
+{
+	size_t auth_size = AST_VECTOR_SIZE(auth_vector);
+	struct ast_sip_auth *auths[auth_size];
+	const char *access_token;
+	pjsip_cred_info auth_creds[1];
+	pjsip_auth_clt_pref prefs;
+	int res = 0;
+	int idx;
+
+	memset(auths, 0, sizeof(auths));
+	if (ast_sip_retrieve_auths(auth_vector, auths)) {
+		res = -1;
+		goto cleanup;
+	}
+
+	for (idx = 0; idx < auth_size; ++idx) {
+		switch (auths[idx]->type) {
+		case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
+			pj_cstr(&auth_creds[0].username, auths[idx]->auth_user);
+			pj_cstr(&auth_creds[0].scheme, "Bearer");
+			pj_cstr(&auth_creds[0].realm, auths[idx]->realm);
+			ast_debug(2, "Obtaining Google OAuth access token\n");
+			access_token = fetch_google_access_token(auths[idx]);
+			if (!access_token) {
+				ast_log(LOG_WARNING, "Obtaining Google OAuth access token failed\n");
+				access_token = auths[idx]->auth_pass;
+				res = -1;
+			}
+			ast_debug(2, "Setting data to '%s'\n", access_token);
+
+			pj_cstr(&auth_creds[0].data, access_token);
+			auth_creds[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+
+			pjsip_regc_set_credentials(regc, 1, auth_creds);
+
+			/* for oauth, send auth without waiting for unauthorized response */
+			prefs.initial_auth = PJ_TRUE;
+			pj_cstr(&prefs.algorithm, "oauth");
+			pjsip_regc_set_prefs(regc, &prefs);
+
+			if (access_token != auths[idx]->auth_pass) {
+				ast_free((char *) access_token);
+			}
+			break;
+		default:
+			/* other cases handled after receiving auth rejection */
+			break;
+		}
+	}
+
+cleanup:
+	ast_sip_cleanup_auths(auths, auth_size);
+	return res;
+}
+
 /*! \brief Helper function that allocates a pjsip registration client and configures it */
 static int sip_outbound_registration_regc_alloc(void *data)
 {
@@ -1356,6 +1601,7 @@
 		route = pjsip_parse_hdr(pjsip_regc_get_pool(state->client_state->client),
 			&ROUTE_HNAME, tmp.ptr, tmp.slen, NULL);
 		if (!route) {
+			ast_sip_tpselector_unref(&selector);
 			return -1;
 		}
 		pj_list_insert_nodes_before(&route_set, route);
@@ -1369,13 +1615,15 @@
 
 	pj_cstr(&server_uri, registration->server_uri);
 
-
 	if (sip_dialog_create_contact(pjsip_regc_get_pool(state->client_state->client),
 		&contact_uri, S_OR(registration->contact_user, "s"), &server_uri, &selector,
-		state->client_state->line)) {
+		state->client_state->line, registration->contact_header_params)) {
+		ast_sip_tpselector_unref(&selector);
 		return -1;
 	}
 
+	ast_sip_tpselector_unref(&selector);
+
 	pj_cstr(&client_uri, registration->client_uri);
 	if (pjsip_regc_init(state->client_state->client, &server_uri, &client_uri,
 		&client_uri, 1, &contact_uri, registration->expiration) != PJ_SUCCESS) {
@@ -1409,6 +1657,7 @@
 	state->client_state->max_retries = registration->max_retries;
 	state->client_state->retries = 0;
 	state->client_state->support_path = registration->support_path;
+	state->client_state->support_outbound = registration->support_outbound;
 	state->client_state->auth_rejection_permanent = registration->auth_rejection_permanent;
 
 	pjsip_regc_update_expires(state->client_state->client, registration->expiration);
@@ -1550,7 +1799,8 @@
 
 	cancel_registration(state->client_state);
 
-	if (pjsip_regc_unregister(client, &tdata) == PJ_SUCCESS) {
+	if (pjsip_regc_unregister(client, &tdata) == PJ_SUCCESS
+		&& add_configured_supported_headers(state->client_state, tdata)) {
 		registration_client_send(state->client_state, tdata);
 	}
 
@@ -2219,6 +2469,7 @@
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "server_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, server_uri));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "client_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, client_uri));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "contact_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, contact_user));
+	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "contact_header_params", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, contact_header_params));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, transport));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, outbound_proxy));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration));
@@ -2229,6 +2480,7 @@
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent));
 	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0);
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, support_path));
+	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_outbound", "no", OPT_YESNO_T, 1, FLDSET(struct sip_outbound_registration, support_outbound));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "line", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, line));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, endpoint));
 
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index e681dcb..1dd8ce9 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -3344,6 +3344,9 @@
 	struct pjsip_request_line req = tdata->msg->line.req;
 
 	ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
+
+	ast_sip_message_apply_transport(session->endpoint->transport, tdata);
+
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
 		if (supplement->outgoing_request && does_method_match(&req.method.name, supplement->method)) {
 			supplement->outgoing_request(session, tdata);
@@ -3366,6 +3369,8 @@
 		pj_strbuf(&cseq->method.name), status.code, (int) pj_strlen(&status.reason),
 		pj_strbuf(&status.reason));
 
+	ast_sip_message_apply_transport(session->endpoint->transport, tdata);
+
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
 		if (supplement->outgoing_response && does_method_match(&cseq->method.name, supplement->method)) {
 			supplement->outgoing_response(session, tdata);
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
index 94be9b8..9e89098 100644
--- a/third-party/pjproject/configure.m4
+++ b/third-party/pjproject/configure.m4
@@ -106,6 +106,8 @@
 	AC_DEFINE([HAVE_PJSIP_TSX_LAYER_FIND_TSX2], 1, [Define if your system has pjsip_tsx_layer_find_tsx2 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])
+	AC_DEFINE([HAVE_PJSIP_OAUTH_AUTHENTICATION], 1, [Define if your system has HAVE_PJSIP_OAUTH_AUTHENTICATION declared])
 
 	AC_SUBST([PJPROJECT_BUNDLED])
 	AC_SUBST([PJPROJECT_DIR])
diff --git a/third-party/pjproject/patches/0020-oauth.patch b/third-party/pjproject/patches/0020-oauth.patch
new file mode 100644
index 0000000..927cb5c
--- /dev/null
+++ b/third-party/pjproject/patches/0020-oauth.patch
@@ -0,0 +1,129 @@
+diff -x '*.o' -x '*.a' -ru a/pjsip/include/pjsip/sip_auth_msg.h b/pjsip/include/pjsip/sip_auth_msg.h
+--- a/pjsip/include/pjsip/sip_auth_msg.h	2011-05-05 02:14:19.000000000 -0400
++++ b/pjsip/include/pjsip/sip_auth_msg.h	2018-09-14 16:42:03.986813665 -0400
+@@ -89,6 +89,23 @@
+ typedef struct pjsip_pgp_credential pjsip_pgp_credential;
+ 
+ /**
++ * This structure describe credential used in Authorization and
++ * Proxy-Authorization header for OAuth authentication scheme.
++ */
++struct pjsip_oauth_credential
++{
++    pj_str_t    realm;          /**< Realm of the credential    */
++    pjsip_param other_param;    /**< Other parameters.          */
++    pj_str_t    username;       /**< Username parameter.        */
++    pj_str_t    token;          /**< Token parameter.           */
++};
++
++/**
++ * @see pjsip_oauth_credential
++ */
++typedef struct pjsip_oauth_credential pjsip_oauth_credential;
++
++/**
+  * This structure describes SIP Authorization header (and also SIP
+  * Proxy-Authorization header).
+  */
+@@ -106,6 +123,7 @@
+ 	pjsip_common_credential common;	/**< Common fields.	    */
+ 	pjsip_digest_credential digest;	/**< Digest credentials.    */
+ 	pjsip_pgp_credential	pgp;	/**< PGP credentials.	    */
++	pjsip_oauth_credential  oauth;  /**< OAuth credentials.     */
+     } credential;
+ };
+ 
+diff -x '*.o' -x '*.a' -ru a/pjsip/include/pjsip/sip_auth_parser.h b/pjsip/include/pjsip/sip_auth_parser.h
+--- a/pjsip/include/pjsip/sip_auth_parser.h	2011-05-05 02:14:19.000000000 -0400
++++ b/pjsip/include/pjsip/sip_auth_parser.h	2018-09-14 16:42:11.982807508 -0400
+@@ -64,6 +64,7 @@
+ 			pjsip_FALSE_STR,    /**< "false" string const.	    */
+ 			pjsip_DIGEST_STR,   /**< "digest" string const.	    */
+ 			pjsip_PGP_STR,	    /**< "pgp" string const.	    */
++			pjsip_BEARER_STR,   /**< "bearer" string const.     */
+ 			pjsip_MD5_STR,	    /**< "md5" string const.	    */
+ 			pjsip_AUTH_STR;	    /**< "auth" string const.	    */
+ 
+diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c
+--- a/pjsip/src/pjsip/sip_auth_client.c	2017-03-31 02:02:48.000000000 -0400
++++ b/pjsip/src/pjsip/sip_auth_client.c	2018-09-14 16:42:28.138795061 -0400
+@@ -959,13 +959,22 @@
+ 
+ 		hs = pjsip_authorization_hdr_create(tdata->pool);
+ 		pj_strdup(tdata->pool, &hs->scheme, &c->scheme);
+-		pj_strdup(tdata->pool, &hs->credential.digest.username,
+-			  &c->username);
+-		pj_strdup(tdata->pool, &hs->credential.digest.realm,
+-			  &c->realm);
+-		pj_strdup(tdata->pool, &hs->credential.digest.uri, &uri);
+-		pj_strdup(tdata->pool, &hs->credential.digest.algorithm,
+-			  &sess->pref.algorithm);
++		if (pj_stricmp(&c->scheme, &pjsip_BEARER_STR)==0) {
++			pj_strdup(tdata->pool, &hs->credential.oauth.username,
++                                  &c->username);
++                        pj_strdup(tdata->pool, &hs->credential.oauth.realm,
++                                  &c->realm);
++                        pj_strdup(tdata->pool, &hs->credential.oauth.token,
++                                  &c->data);
++		} else { //if (pj_stricmp(&c->scheme, &pjsip_DIGEST_STR)==0)
++			pj_strdup(tdata->pool, &hs->credential.digest.username,
++				  &c->username);
++			pj_strdup(tdata->pool, &hs->credential.digest.realm,
++				  &c->realm);
++			pj_strdup(tdata->pool,&hs->credential.digest.uri, &uri);
++			pj_strdup(tdata->pool, &hs->credential.digest.algorithm,
++			  	  &sess->pref.algorithm);
++		}
+ 
+ 		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs);
+ 	    }
+diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_auth_msg.c b/pjsip/src/pjsip/sip_auth_msg.c
+--- a/pjsip/src/pjsip/sip_auth_msg.c	2016-01-27 00:42:20.000000000 -0500
++++ b/pjsip/src/pjsip/sip_auth_msg.c	2018-09-14 16:42:15.882804502 -0400
+@@ -103,6 +103,23 @@
+     return -1;
+ }
+ 
++static int print_oauth_credential(pjsip_oauth_credential *cred, char *buf,
++				  pj_size_t size)
++{
++    pj_ssize_t printed;
++    char *startbuf = buf;
++    char *endbuf = buf + size;
++
++    copy_advance_pair_quote_cond_always(buf, "token=", 6, cred->token,
++    					'"', '"');
++    copy_advance_pair_quote_cond_always(buf, ", username=", 11, cred->username,
++    					'"', '"');
++    copy_advance_pair_quote_cond_always(buf, ", realm=", 8, cred->realm,
++    					'"', '"');
++
++    return (int) (buf-startbuf);
++}
++
+ static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr,
+ 					  char *buf, pj_size_t size)
+ {
+@@ -125,6 +142,11 @@
+     {
+ 	printed = print_pgp_credential(&hdr->credential.pgp, buf, endbuf - buf);
+     } 
++    else if (pj_stricmp(&hdr->scheme, &pjsip_BEARER_STR) == 0)
++    {
++        printed = print_oauth_credential(&hdr->credential.oauth, buf,
++        				 endbuf - buf);
++    }
+     else {
+ 	pj_assert(0);
+ 	return -1;
+diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_auth_parser.c b/pjsip/src/pjsip/sip_auth_parser.c
+--- a/pjsip/src/pjsip/sip_auth_parser.c	2014-06-09 22:56:56.000000000 -0400
++++ b/pjsip/src/pjsip/sip_auth_parser.c	2018-09-14 16:42:21.418800238 -0400
+@@ -59,6 +59,7 @@
+ 		pjsip_QUOTED_DIGEST_STR =   { "\"Digest\"", 8},
+ 		pjsip_PGP_STR =		    { "PGP", 3 },
+ 		pjsip_QUOTED_PGP_STR =	    { "\"PGP\"", 5 },
++		pjsip_BEARER_STR =          { "Bearer", 6 },
+ 		pjsip_MD5_STR =		    { "md5", 3 },
+ 		pjsip_QUOTED_MD5_STR =	    { "\"md5\"", 5},
+ 		pjsip_AUTH_STR =	    { "auth", 4},
diff --git a/third-party/pjproject/patches/0030-allow-disabling-of-connection-reuse.patch b/third-party/pjproject/patches/0030-allow-disabling-of-connection-reuse.patch
new file mode 100644
index 0000000..4727085
--- /dev/null
+++ b/third-party/pjproject/patches/0030-allow-disabling-of-connection-reuse.patch
@@ -0,0 +1,102 @@
+diff -x '*.o' -x '*.a' -ru a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h
+--- a/pjsip/include/pjsip/sip_transport.h	2017-02-19 20:16:58.000000000 -0500
++++ b/pjsip/include/pjsip/sip_transport.h	2018-09-14 16:47:25.145266710 -0400
+@@ -221,12 +221,26 @@
+  * application specificly request that a particular transport/listener
+  * should be used to send request. This structure is used when calling
+  * pjsip_tsx_set_transport() and pjsip_dlg_set_transport().
++ *
++ * If application disables connection reuse and wants to force creating
++ * a new transport, it needs to consider the following couple of things:
++ * - If it still wants to reuse an existing transport (if any), it
++ *   needs to keep a reference to that transport and specifically set
++ *   the transport to be used for sending requests.
++ * - Delete those existing transports manually when no longer needed.
+  */
+ typedef struct pjsip_tpselector
+ {
+     /** The type of data in the union */
+     pjsip_tpselector_type   type;
+ 
++    /**
++     * Whether to disable reuse of an existing connection.
++     * This setting will be ignored if (type == PJSIP_TPSELECTOR_TRANSPORT)
++     * and transport in the union below is set.
++     */
++    pj_bool_t disable_connection_reuse;
++
+     /** Union representing the transport/listener criteria to be used. */
+     union {
+ 	pjsip_transport	*transport;
+diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
+--- a/pjsip/src/pjsip/sip_transport.c	2017-11-07 21:58:18.000000000 -0500
++++ b/pjsip/src/pjsip/sip_transport.c	2018-09-14 16:47:25.145266710 -0400
+@@ -2118,7 +2118,7 @@
+ 	 */
+ 	pjsip_transport_key key;
+ 	int key_len;
+-	pjsip_transport *transport;
++	pjsip_transport *transport = NULL;
+ 
+ 	/* If listener is specified, verify that the listener type matches
+ 	 * the destination type.
+@@ -2131,17 +2131,21 @@
+ 	    }
+ 	}
+ 
+-	pj_bzero(&key, sizeof(key));
+-	key_len = sizeof(key.type) + addr_len;
++	if (!sel || sel->disable_connection_reuse == PJ_FALSE) {
++	    pj_bzero(&key, sizeof(key));
++	    key_len = sizeof(key.type) + addr_len;
++
++	    /* First try to get exact destination. */
++	    key.type = type;
++	    pj_memcpy(&key.rem_addr, remote, addr_len);
+ 
+-	/* First try to get exact destination. */
+-	key.type = type;
+-	pj_memcpy(&key.rem_addr, remote, addr_len);
+-
+-	transport = (pjsip_transport*)
+-		    pj_hash_get(mgr->table, &key, key_len, NULL);
++	    transport = (pjsip_transport*)
++		        pj_hash_get(mgr->table, &key, key_len, NULL);
++	}
+ 
+-	if (transport == NULL) {
++	if (transport == NULL &&
++	    (!sel || sel->disable_connection_reuse == PJ_FALSE))
++	{
+ 	    unsigned flag = pjsip_transport_get_flag_from_type(type);
+ 	    const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote;
+ 
+@@ -2179,9 +2183,7 @@
+ 	    transport = NULL;
+ 	    /* This will cause a new transport to be created which will be a
+ 	     * 'duplicate' of the existing transport (same type & remote addr,
+-	     * but different factory). Any future hash lookup will return
+-	     * the new one, and eventually the old one will still be freed
+-	     * (by application or #1774).
++	     * but different factory).
+ 	     */
+ 	}
+ 
+@@ -2199,9 +2201,14 @@
+ 
+ 
+ 	/*
+-	 * Transport not found!
+-	 * So we need to create one, find factory that can create
+-	 * such transport.
++	 * Either transport not found, or we don't want to use the existing
++	 * transport (such as in the case of different factory or
++	 * if connection reuse is disabled). So we need to create one,
++	 * find factory that can create such transport.
++	 *
++	 * If there's an existing transport, its place in the hash table
++	 * will be replaced by this new one. And eventually the existing
++	 * transport will still be freed (by application or #1774).
+ 	 */
+ 	if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener)
+ 	{

-- 
To view, visit https://gerrit.asterisk.org/9505
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Id214c2d1c550a41fcf564b7df8f3da7be565bd58
Gerrit-Change-Number: 9505
Gerrit-PatchSet: 19
Gerrit-Owner: Nick French <naf at ou.edu>
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Jenkins2 (1000185)
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: Michael Kuron <m.kuron at gmx.de>
Gerrit-Reviewer: Michael L. Young <elgueromexicano at gmail.com>
Gerrit-Reviewer: Nick French <naf at ou.edu>
Gerrit-Reviewer: Richard Mudgett <rmudgett at digium.com>
Gerrit-Reviewer: Sean Bright <sean.bright at gmail.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20181025/9dac0879/attachment-0001.html>


More information about the asterisk-code-review mailing list