From 8d5c36c9bb91af574fbe0dd261f3b6601304b263 Mon Sep 17 00:00:00 2001
From: Matthew Jordan <mjordan@digium.com>
Date: Tue, 5 Mar 2013 13:14:43 +0000
Subject: [PATCH] Add RFC 3327 Path header support to chan_sip

This patch adds support for RFC 3327 "Path" headers. This can be enabled in
sip.conf using the 'supportpath' setting, either on a global basis or on a
peer basis. This setting enables Asterisk to route outgoing out-of-dialog
requests via a set of proxies by using a pre-loaded route-set defined by the
Path headers in the REGISTER request. This patch also adds Realtime support
for dynamically updating the Path information for a peer.

A huge thank-you to Klaus Darillion and Olle E Johansson for their efforts
in writing this patch.

Review: https://reviewboard.asterisk.org/r/2235/
Review: https://reviewboard.asterisk.org/r/991/

(closes issue ASTERISK-16884)
Reported by: klaus3000
Tested by: klaus3000, oej, mjordan
patches:
  path-1.8.0-patch.txt uploaded by klaus3000 (License 5054)
  oolong-path-support-trunk in team branch by oej (License 5267)



git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@382440 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 CHANGES                                  |  16 ++
 CREDITS                                  |  12 +-
 channels/chan_sip.c                      | 279 ++++++++++++++++++++---
 channels/sip/include/sip.h               |   5 +-
 configs/res_ldap.conf.sample             |   1 +
 configs/sip.conf.sample                  |  15 ++
 contrib/realtime/mysql/sippeers.sql      |   2 +
 contrib/realtime/postgresql/realtime.sql |   2 +
 contrib/scripts/asterisk.ldap-schema     |  12 +-
 contrib/scripts/asterisk.ldif            |  11 +-
 10 files changed, 316 insertions(+), 39 deletions(-)

diff --git a/CHANGES b/CHANGES
index 2ef538eab1..6b49cb61c4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -50,6 +50,13 @@ chan_mobile
 
  * Added ECAM command support for Sony Ericsson phones.
 
+chan_sip
+------------------
+ * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf
+   using the 'supportpath' setting, either on a global basis or on a peer basis.
+   This setting enables Asterisk to route outgoing out-of-dialog requests via a
+   set of proxies by using a pre-loaded route-set defined by the Path headers in
+   the REGISTER request. See Realtime updates for more configuration information.
 
 Features
 -------------------
@@ -95,6 +102,15 @@ Core
    reason to any string. It also allows for custom strings to be read as the
    redirecting reason from SIP Diversion headers.
 
+Realtime
+------------------
+ * Dynamic realtime tables for SIP Users can now include a 'path' field. This
+   will store the path information for that peer when it registers. Realtime
+   tables can also use the 'supportpath' field to enable Path header support.
+
+ * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport
+   objectIdentifier. This maps to the supportpath option in sip.conf. 
+
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 10 to Asterisk 11 --------------------
 ------------------------------------------------------------------------------
diff --git a/CREDITS b/CREDITS
index aa3c1ab532..a7673963ca 100644
--- a/CREDITS
+++ b/CREDITS
@@ -22,7 +22,7 @@
 	* John Todd, TalkPlus, Inc.  and JR Richardson, Ntegrated Solutions. 
 		for funding the development of SIP Session Timers support.
 
-	* Omnitor AB, Gunnar Hellström, for funding work with videocaps, 
+	* Omnitor AB, Gunnar Hellstr�m, for funding work with videocaps, 
 		T.140 RED, originate with video/text and many more 
 		contributions.
 
@@ -54,7 +54,7 @@
 
 === HARDWARE DONORS === 
 
- We'd like to thank the followwing for granting access to hardware for testing.
+ We'd like to thank the following for granting access to hardware for testing.
 
 	* Thanks to QuickNet Technologies for their donation of an Internet
 		PhoneJack and Linejack card to the project.  
@@ -63,13 +63,12 @@
 	* Thanks to VoipSupply for their donation of Sipura ATAs to the project
 		for T.38 testing. (http://www.voipsupply.com)
 
-
 	* Thanks to Grandstream for their donation of ATAs to the project for
 		T.38 testing. (http://www.grandstream.com)
 
 === MISCELLANEOUS PATCHES ===
 
- We'd like to thank the flollowing for their patches
+ We'd like to thank the following for their patches
 
 	* Jim Dixon - Zapata Telephony and app_rpt
 		http://www.zapatatelephony.org/app_rpt.html
@@ -240,7 +239,8 @@
 		ControlPlayback, and multiple bug fixes See 
 		http://voip-info.org/users/view/sergee serg(AT)voipsolutions.ru
 
-	* Klaus Darillon - the SIPremoveHeader function in chan_sip
+	* Klaus Darillon - the SIPremoveHeader function in chan_sip and SIP Path
+		Support.
 
 	* Moises Silva (moy) - for writing LibOpenR2, and providing support for
 		it in chan_dahdi moises.silva(AT)gmail.com
@@ -252,7 +252,7 @@
 		cdr_tds rewrite, countless other improvements, fixes, and good
 		ideas. sean(AT)malleable.com
 
-	* Jan Kaláb - Calendaring support for Exchange Server 2007+ via 
+	* Jan Kal�b - Calendaring support for Exchange Server 2007+ via 
 		Exchange Web Services.
 
 	* University of Oslo (uio.no), Norway - SIP Max-Forwards setting 
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 1adf7a00fa..9538441926 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -1187,6 +1187,8 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
 static void free_old_route(struct sip_route *route);
 static void list_route(struct sip_route *route);
 static void build_route(struct sip_pvt *p, struct sip_request *req, int backwards, int resp);
+static int build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, char *pathbuf);
+static int copy_route(struct sip_route **dst, const struct sip_route *src);
 static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr,
 					      struct sip_request *req, const char *uri);
 static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag);
@@ -1360,7 +1362,7 @@ static void set_socket_transport(struct sip_socket *socket, int transport);
 static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags);
 
 /* Realtime device support */
-static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *username, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms);
+static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *username, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms, const char *path);
 static void update_peer(struct sip_peer *p, int expire);
 static struct ast_variable *get_insecure_variable_from_config(struct ast_config *config);
 static const char *get_name_from_variable(const struct ast_variable *var);
@@ -1442,6 +1444,7 @@ static int add_digit(struct sip_request *req, char digit, unsigned int duration,
 static int add_rpid(struct sip_request *req, struct sip_pvt *p);
 static int add_vidupdate(struct sip_request *req);
 static void add_route(struct sip_request *req, struct sip_route *route);
+static void make_route_list(struct sip_route *route, char *r, int rem);
 static int copy_header(struct sip_request *req, const struct sip_request *orig, const char *field);
 static int copy_all_header(struct sip_request *req, const struct sip_request *orig, const char *field);
 static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const struct sip_request *orig, const char *field);
@@ -5112,7 +5115,7 @@ static int sip_sendtext(struct ast_channel *ast, const char *text)
 	that name and store that in the "regserver" field in the sippeers
 	table to facilitate multi-server setups.
 */
-static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
+static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms, const char *path)
 {
 	char port[10];
 	char ipaddr[INET6_ADDRSTRLEN];
@@ -5135,10 +5138,11 @@ static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr
 	ast_copy_string(ipaddr, ast_sockaddr_isnull(addr) ? "" : ast_sockaddr_stringify_addr(addr), sizeof(ipaddr));
 	ast_copy_string(port, ast_sockaddr_port(addr) ? ast_sockaddr_stringify_port(addr) : "", sizeof(port));
 
-	if (ast_strlen_zero(sysname))	/* No system name, disable this */
+	if (ast_strlen_zero(sysname)) {	/* No system name, disable this */
 		sysname = NULL;
-	else if (sip_cfg.rtsave_sysname)
+	} else if (sip_cfg.rtsave_sysname) {
 		syslabel = "regserver";
+	}
 
 	/* XXX IMPORTANT: Anytime you add a new parameter to be updated, you
          *  must also add it to contrib/scripts/asterisk.ldap-schema,
@@ -5146,18 +5150,38 @@ static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr
          *  and to configs/res_ldap.conf.sample as described in
          *  bugs 15156 and 15895
          */
-	if (fc) {
-		ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
-			"port", port, "regseconds", regseconds,
-			deprecated_username ? "username" : "defaultuser", defaultuser,
-			"useragent", useragent, "lastms", str_lastms,
-			fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+
+	/* This is ugly, we need something better ;-) */
+	if (sip_cfg.rtsave_path) {
+		if (fc) {
+			ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+				"port", port, "regseconds", regseconds,
+				deprecated_username ? "username" : "defaultuser", defaultuser,
+				"useragent", useragent, "lastms", str_lastms,
+				"path", path,			/* Path data can be NULL */
+				fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+		} else {
+			ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+				"port", port, "regseconds", regseconds,
+				"useragent", useragent, "lastms", str_lastms,
+				deprecated_username ? "username" : "defaultuser", defaultuser,
+				"path", path,			/* Path data can be NULL */
+				syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+		}
 	} else {
-		ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
-			"port", port, "regseconds", regseconds,
-			"useragent", useragent, "lastms", str_lastms,
-			deprecated_username ? "username" : "defaultuser", defaultuser,
-			syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+		if (fc) {
+			ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+				"port", port, "regseconds", regseconds,
+				deprecated_username ? "username" : "defaultuser", defaultuser,
+				"useragent", useragent, "lastms", str_lastms,
+				fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+		} else {
+			ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+				"port", port, "regseconds", regseconds,
+				"useragent", useragent, "lastms", str_lastms,
+				deprecated_username ? "username" : "defaultuser", defaultuser,
+				syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+		}
 	}
 }
 
@@ -5252,6 +5276,10 @@ static void sip_destroy_peer(struct sip_peer *peer)
 		ast_variables_destroy(peer->chanvars);
 		peer->chanvars = NULL;
 	}
+	if (peer->path) {
+		free_old_route(peer->path);
+		peer->path = NULL;
+	}
 
 	register_peer_exten(peer, FALSE);
 	ast_free_acl_list(peer->acl);
@@ -5294,7 +5322,9 @@ static void update_peer(struct sip_peer *p, int expire)
 	int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
 	if (sip_cfg.peer_rtupdate &&
 	    (p->is_realtime || rtcachefriends)) {
-		realtime_update_peer(p->name, &p->addr, p->username, p->fullcontact, p->useragent, expire, p->deprecated_username, p->lastms);
+		char path[SIPBUFSIZE * 2];
+		make_route_list(p->path, path, sizeof(path));
+		realtime_update_peer(p->name, &p->addr, p->username, p->fullcontact, p->useragent, expire, p->deprecated_username, p->lastms, path);
 	}
 }
 
@@ -5989,6 +6019,8 @@ static int dialog_initialize_rtp(struct sip_pvt *dialog)
 	return 0;
 }
 
+static int __set_address_from_contact(const char *fullcontact, struct ast_sockaddr *addr, int tcp);
+
 /*! \brief Create address structure from peer reference.
  *	This function copies data from peer to the dialog, so we don't have to look up the peer
  *	again from memory or database during the life time of the dialog.
@@ -6029,6 +6061,12 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
 	dialog->rtptimeout = peer->rtptimeout;
 	dialog->rtpholdtimeout = peer->rtpholdtimeout;
 	dialog->rtpkeepalive = peer->rtpkeepalive;
+	copy_route(&dialog->route, peer->path);
+	if (dialog->route) {
+		/* Parse SIP URI of first route-set hop and use it as target address */
+		__set_address_from_contact(dialog->route->hop, &dialog->sa, dialog->socket.type == SIP_TRANSPORT_TLS ? 1 : 0);
+	}
+
 	if (dialog_initialize_rtp(dialog)) {
 		return -1;
 	}
@@ -6391,7 +6429,7 @@ static int sip_call(struct ast_channel *ast, const char *dest, int timeout)
 	ast_clear_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_T38);
 
 	if (p->options->transfer) {
-		char buf[SIPBUFSIZE/2];
+		char buf[SIPBUFSIZE / 2];
 
 		if (referer) {
 			if (sipdebug)
@@ -11342,12 +11380,14 @@ static int process_sdp_a_image(const char *a, struct sip_pvt *p)
  *  is supported for this dialog. */
 static int add_supported(struct sip_pvt *pvt, struct sip_request *req)
 {
+	char supported_value[SIPBUFSIZE];
 	int res;
-	if (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) {
-		res = add_header(req, "Supported", "replaces, timer");
-	} else {
-		res = add_header(req, "Supported", "replaces");
-	}
+
+	sprintf(supported_value, "replaces%s%s",
+		(st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) ? ", timer" : "",
+		ast_test_flag(&pvt->flags[0], SIP_USEPATH) ? ", path" : "");
+	res = add_header(req, "Supported", supported_value);
+
 	return res;
 }
 
@@ -11523,12 +11563,21 @@ static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const st
 /*! \brief Add route header into request per learned route */
 static void add_route(struct sip_request *req, struct sip_route *route)
 {
-	char r[SIPBUFSIZE*2], *p;
-	int n, rem = sizeof(r);
+	char r[SIPBUFSIZE * 2];
 
 	if (!route)
 		return;
 
+	make_route_list(route, r, sizeof(r));
+	add_header(req, "Route", r);
+}
+
+/*! \brief Make the comma separated list of route headers from the route list */
+static void make_route_list(struct sip_route *route, char *r, int rem)
+{
+	char *p;
+	int n;
+
 	p = r;
 	for (;route ; route = route->next) {
 		n = strlen(route->hop);
@@ -11545,7 +11594,6 @@ static void add_route(struct sip_request *req, struct sip_route *route)
 		rem -= (n+2);
 	}
 	*p = '\0';
-	add_header(req, "Route", r);
 }
 
 /*! \brief Set destination from SIP URI
@@ -11822,6 +11870,9 @@ static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg
 			snprintf(contact, sizeof(contact), "%s%s%s;expires=%d", brackets ? "" : "<", contact_uri, brackets ? "" : ">", p->expiry);
 			add_header(resp, "Contact", contact);	/* Not when we unregister */
 		}
+		if (p->method == SIP_REGISTER && ast_test_flag(&p->flags[0], SIP_USEPATH)) {
+			copy_header(resp, req, "Path");
+		}
 	} else if (!ast_strlen_zero(p->our_contact) && resp_needs_contact(msg, p->method)) {
 		add_header(resp, "Contact", p->our_contact);
 	}
@@ -15337,6 +15388,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
 	add_header(&req, "To", to);
 	add_header(&req, "Call-ID", p->callid);
 	add_header(&req, "CSeq", tmp);
+	add_supported(p, &req);
 	if (!ast_strlen_zero(global_useragent))
 		add_header(&req, "User-Agent", global_useragent);
 
@@ -15648,6 +15700,7 @@ static void destroy_association(struct sip_peer *peer)
 			ast_update_realtime(tablename, "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "", "regseconds", "0", "regserver", "", "useragent", "", "lastms", "0", SENTINEL);
 		} else {
 			ast_db_del("SIP/Registry", peer->name);
+			ast_db_del("SIP/RegistryPath", peer->name);
 			ast_db_del("SIP/PeerMethods", peer->name);
 		}
 	}
@@ -15751,6 +15804,7 @@ static int sip_poke_peer_s(const void *data)
 static void reg_source_db(struct sip_peer *peer)
 {
 	char data[256];
+	char path[SIPBUFSIZE * 2];
 	struct ast_sockaddr sa;
 	int expire;
 	char full_addr[128];
@@ -15809,6 +15863,9 @@ static void reg_source_db(struct sip_peer *peer)
 			sip_unref_peer(peer, "remove registration ref"),
 			sip_ref_peer(peer, "add registration ref"));
 	register_peer_exten(peer, TRUE);
+	if (!ast_db_get("SIP/RegistryPath", peer->name, path, sizeof(path))) {
+		build_path(NULL, peer, NULL, path);
+	}
 }
 
 /*! \brief Save contact header for 200 OK on INVITE */
@@ -16104,11 +16161,21 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st
 		}
 	}
 	pvt->expiry = expire;
+	if (!build_path(pvt, peer, req, NULL)) {
+		/* Tell the dialog to use the Path header in the response */
+		ast_set2_flag(&pvt->flags[0], 1, SIP_USEPATH);
+	}
 	snprintf(data, sizeof(data), "%s:%d:%s:%s", ast_sockaddr_stringify(&peer->addr),
 		 expire, peer->username, peer->fullcontact);
 	/* We might not immediately be able to reconnect via TCP, but try caching it anyhow */
-	if (!peer->rt_fromcontact || !sip_cfg.peer_rtupdate)
+	if (!peer->rt_fromcontact || !sip_cfg.peer_rtupdate) {
+		char path[SIPBUFSIZE * 2];
+		if (peer->path) {
+			make_route_list(peer->path, path, sizeof(path));
+			ast_db_put("SIP/RegistryPath", peer->name, path);
+		}
 		ast_db_put("SIP/Registry", peer->name, data);
+	}
 	manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Registered\r\nAddress: %s\r\n", peer->name,  ast_sockaddr_stringify(&peer->addr));
 
 	/* Is this a new IP address for us? */
@@ -16144,10 +16211,10 @@ static void free_old_route(struct sip_route *route)
 static void list_route(struct sip_route *route)
 {
 	if (!route) {
-		ast_verbose("list_route: no route\n");
+		ast_verbose("list_route: no route/path\n");
 	} else {
 		for (;route; route = route->next)
-			ast_verbose("list_route: hop: <%s>\n", route->hop);
+			ast_verbose("list_route: route/path hop: <%s>\n", route->hop);
 	}
 }
 
@@ -16277,6 +16344,134 @@ static void build_route(struct sip_pvt *p, struct sip_request *req, int backward
 	}
 }
 
+/*! \internal \brief Create a new route
+ * \retval NULL on error
+ * \retval sip_route on success
+ */
+static struct sip_route *create_route(const char *hop, struct sip_route *prev)
+{
+	struct sip_route *route;
+	int len;
+
+	if (ast_strlen_zero(hop)) {
+		return NULL;
+	}
+	len = strlen(hop) + 1;
+
+	/* ast_calloc is not needed because all fields are initialized in
+	 * this block */
+	route = ast_malloc(sizeof(*route) + len);
+	if (!route) {
+		return NULL;
+	}
+	ast_copy_string(route->hop, hop, len);
+
+	route->next = NULL;
+	if (prev) {
+		prev->next = route;
+	}
+	return route;
+}
+
+/*! \internal \brief copy route-set
+ * \retval non-zero on failure
+ * \retval 0 on success
+ */
+static int copy_route(struct sip_route **dst, const struct sip_route *src)
+{
+	struct sip_route *thishop, *head, *tail;
+
+	/* Build a tailq, then assign it to **d when done. */
+	head = NULL;
+	tail = head;
+	for (; src; src = src->next) {
+		thishop = create_route(src->hop, tail);
+		if (!thishop) {
+			return -1;
+		}
+		if (!head) {
+			head = thishop;
+		}
+		tail = thishop;
+
+		ast_debug(2, "copy_route: copied hop: <%s>\n", thishop->hop);
+	}
+	*dst = head;
+
+	return 0;
+}
+
+/*! \brief Build route list from Path header
+ *  RFC 3327 requires that the Path header contains SIP URIs with lr paramter.
+ *  Thus, we do not care about strict routing SIP routers
+ */
+static int build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, char *pathbuf)
+{
+	struct sip_route *thishop, *head, *tail;
+	int start = 0;
+	int len;
+	char *pr;
+
+	if (peer->path) {
+		free_old_route(peer->path);
+		peer->path = NULL;
+	}
+
+	if (!ast_test_flag(&peer->flags[0], SIP_USEPATH)) {
+		ast_debug(2, "build_path: do not use Path headers\n");
+		return -1;
+	}
+	ast_debug(2, "build_path: try to build pre-loaded route-set by parsing Path headers\n");
+
+	/* Build a tailq, then assign it to peer->path when done. */
+	head = NULL;
+	tail = head;
+	/* 1st we pass through all the hops in any Path headers */
+	for (;;) {
+		/* Either loop over the request's Path headers or parse the buffer */
+		if (req) {
+			pr = ast_strdupa(__get_header(req, "Path", &start));
+			if (*pr == '\0') {
+				break;
+			}
+		} else if (pathbuf) {
+			if (start == 0) {
+				pr = ast_strdupa(pathbuf);
+				start++;
+			} else {
+				break;
+			}
+		} else {
+			break;
+		}
+		for (; (pr = strchr(pr, '<')) ; pr += (len + 1)) {
+			/* Parse out each route entry */
+			++pr;
+			len = strcspn(pr, ">");
+			*(pr + len) = '\0';
+			thishop = create_route(pr, tail);
+			if (!thishop) {
+				return -1;
+			}
+
+			if (!head) {
+				head = thishop;
+			}
+			tail = thishop;
+			ast_debug(2, "build_path: Path hop: <%s>\n", thishop->hop);
+		}
+	}
+
+	/* Store as new route */
+	peer->path = head;
+
+	/* For debugging dump what we ended up with */
+	if (p && sip_debug_test_pvt(p)) {
+		list_route(peer->path);
+	}
+	return 0;
+}
+
 /*! \brief builds the sip_pvt's nonce field which is used for the authentication 
  *  challenge.  When forceupdate is not set, the nonce is only updated if
  *  the current one is stale.  In this case, a stalenonce is one which
@@ -19984,6 +20179,20 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct
 		ast_cli(fd, "  Ign SDP ver  : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_IGNORESDPVERSION)));
 		ast_cli(fd, "  Trust RPID   : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_TRUSTRPID)));
 		ast_cli(fd, "  Send RPID    : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_SENDRPID)));
+		ast_cli(fd, "  Path support : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USEPATH)));
+		ast_cli(fd, "  Path         : ");
+		if (!peer->path) {
+			ast_cli(fd, "N/A\n");
+		} else {
+			struct sip_route *r = peer->path;
+			int first = 1;
+			while (r) {
+				ast_cli(fd, "%s<%s>", first ? "" : ", ", r->hop);
+				first = 0;
+				r = r->next;
+			}
+			ast_cli(fd, "\n");
+		}
 		ast_cli(fd, "  Subscriptions: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)));
 		ast_cli(fd, "  Overlap dial : %s\n", allowoverlap2str(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWOVERLAP)));
 		if (peer->outboundproxy)
@@ -20577,6 +20786,7 @@ static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_
 	ast_cli(a->fd, "  Allow promisc. redir:   %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_PROMISCREDIR)));
 	ast_cli(a->fd, "  Enable call counters:   %s\n", AST_CLI_YESNO(global_callcounter));
 	ast_cli(a->fd, "  SIP domain support:     %s\n", AST_CLI_YESNO(!AST_LIST_EMPTY(&domain_list)));
+	ast_cli(a->fd, "  Path support :          %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_USEPATH)));
 	ast_cli(a->fd, "  Realm. auth:            %s\n", AST_CLI_YESNO(credentials != NULL));
 	if (credentials) {
 		struct sip_auth *auth;
@@ -20750,6 +20960,7 @@ static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_
 		ast_cli(a->fd, "  Update:                 %s\n", AST_CLI_YESNO(sip_cfg.peer_rtupdate));
 		ast_cli(a->fd, "  Ignore Reg. Expire:     %s\n", AST_CLI_YESNO(sip_cfg.ignore_regexpire));
 		ast_cli(a->fd, "  Save sys. name:         %s\n", AST_CLI_YESNO(sip_cfg.rtsave_sysname));
+		ast_cli(a->fd, "  Save path header:       %s\n", AST_CLI_YESNO(sip_cfg.rtsave_path));
 		ast_cli(a->fd, "  Auto Clear:             %d (%s)\n", sip_cfg.rtautoclear, ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR) ? "Enabled" : "Disabled");
 	}
 	ast_cli(a->fd, "\n----\n");
@@ -23342,7 +23553,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 {
 	int expires, expires_ms;
 	struct sip_registry *r;
-	r=p->registry;
+	r = p->registry;
 	
 	switch (resp) {
 	case 401:	/* Unauthorized */
@@ -29492,6 +29703,11 @@ static int sip_poke_peer(struct sip_peer *peer, int force)
 	ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
 	ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
 	ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
+	copy_route(&p->route, peer->path);
+	if (p->route) {
+		/* Parse SIP URI of first route-set hop and use it as target address */
+		__set_address_from_contact(p->route->hop, &p->sa, p->socket.type == SIP_TRANSPORT_TLS ? 1 : 0);	
+	}
 
 	/* Send OPTIONs to peer's fullcontact */
 	if (!ast_strlen_zero(peer->fullcontact)) {
@@ -29946,6 +30162,9 @@ static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask
 	if (!strcasecmp(v->name, "trustrpid")) {
 		ast_set_flag(&mask[0], SIP_TRUSTRPID);
 		ast_set2_flag(&flags[0], ast_true(v->value), SIP_TRUSTRPID);
+	} else if (!strcasecmp(v->name, "supportpath")) {
+		ast_set_flag(&mask[0], SIP_USEPATH);
+		ast_set2_flag(&flags[0], ast_true(v->value), SIP_USEPATH);
 	} else if (!strcasecmp(v->name, "sendrpid")) {
 		ast_set_flag(&mask[0], SIP_SENDRPID);
 		if (!strcasecmp(v->value, "pai")) {
@@ -31528,6 +31747,8 @@ static int reload_config(enum channelreloadreason reason)
 			ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_RTCACHEFRIENDS);
 		} else if (!strcasecmp(v->name, "rtsavesysname")) {
 			sip_cfg.rtsave_sysname = ast_true(v->value);
+		} else if (!strcasecmp(v->name, "rtsavepath")) {
+			sip_cfg.rtsave_path = ast_true(v->value);
 		} else if (!strcasecmp(v->name, "rtupdate")) {
 			sip_cfg.peer_rtupdate = ast_true(v->value);
 		} else if (!strcasecmp(v->name, "ignoreregexpire")) {
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index f74446ab84..3af35b0485 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -294,6 +294,7 @@
 #define SIP_PROG_INBAND_NO     (1 << 25)
 #define SIP_PROG_INBAND_YES    (2 << 25)
 
+#define SIP_USEPATH          (1 << 27) /*!< GDP: Trust and use incoming Path headers? */
 #define SIP_SENDRPID         (3 << 29) /*!< DP: Remote Party-ID Support */
 #define SIP_SENDRPID_NO      (0 << 29)
 #define SIP_SENDRPID_PAI     (1 << 29) /*!< Use "P-Asserted-Identity" for rpid */
@@ -304,7 +305,7 @@
 #define SIP_FLAGS_TO_COPY \
 	(SIP_PROMISCREDIR | SIP_TRUSTRPID | SIP_SENDRPID | SIP_DTMF | SIP_REINVITE | \
 	 SIP_PROG_INBAND | SIP_USECLIENTCODE | SIP_NAT_FORCE_RPORT | SIP_G726_NONSTANDARD | \
-	 SIP_USEREQPHONE | SIP_INSECURE)
+	 SIP_USEREQPHONE | SIP_INSECURE | SIP_USEPATH)
 /*@}*/
 
 /*! \name SIPflags2
@@ -737,6 +738,7 @@ struct __show_chan_arg {
 struct sip_settings {
 	int peer_rtupdate;          /*!< G: Update database with registration data for peer? */
 	int rtsave_sysname;         /*!< G: Save system name at registration? */
+	int rtsave_path;            /*!< G: Save path header on registration */
 	int ignore_regexpire;       /*!< G: Ignore expiration of peer  */
 	int rtautoclear;            /*!< Realtime ?? */
 	int directrtpsetup;         /*!< Enable support for Direct RTP setup (no re-invites) */
@@ -1368,6 +1370,7 @@ struct sip_peer {
 	int timer_t1;                   /*!<  The maximum T1 value for the peer */
 	int timer_b;                    /*!<  The maximum timer B (transaction timeouts) */
 	int fromdomainport;             /*!<  The From: domain port */
+	struct sip_route *path;         /*!<  Head of linked list of out-of-dialog outgoing routing steps (fm Path headers) */
 
 	/*XXX Seems like we suddenly have two flags with the same content. Why? To be continued... */
 	enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */
diff --git a/configs/res_ldap.conf.sample b/configs/res_ldap.conf.sample
index 9a2accb80e..ac345cdd4e 100644
--- a/configs/res_ldap.conf.sample
+++ b/configs/res_ldap.conf.sample
@@ -121,6 +121,7 @@ ipaddr = AstAccountIPAddress
 defaultuser = AstAccountDefaultUser
 regserver = AstAccountRegistrationServer
 lastms = AstAccountLastQualifyMilliseconds
+supportpath = AstAccountPathSupport
 additionalFilter=(objectClass=AsteriskSIPUser)
 
 ;
diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample
index d54398c442..a0ceabeb7e 100644
--- a/configs/sip.conf.sample
+++ b/configs/sip.conf.sample
@@ -442,6 +442,21 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
 ;outboundproxy=[2001:db8::1]:5062               ; IPv6 address literal with explicit port
 ;                                               ; (could also be tcp,udp) - defining transports on the proxy line only
 ;                                               ; applies for the global proxy, otherwise use the transport= option
+
+;supportpath=yes		; This activates parsing and handling of Path header as defined in RFC 3327. This enables
+				; Asterisk to route outgoing out-of-dialog requests via a set of proxies by using a pre-loaded
+				; route-set defined by the Path headers in the REGISTER request.
+				; NOTE: There are multiple things to consider with this setting:
+				;  * As this influences routing of SIP requests make sure to not trust Path headers provided
+				;    by the user's SIP client (the proxy in front of Asterisk should remove existing user
+				;    provided Path headers).
+				;  * When a peer has both a path and outboundproxy set, the path will be added to Route: header
+				;    but routing to next hop is done using the outboundproxy.
+				;  * If set globally, not only will all peers use the Path header, but outbound REGISTER
+				;    requests from Asterisk will add path to the Supported header.
+
+;rtsavepath=yes                 ; If using dynamic realtime, store the path headers
+
 ;matchexternaddrlocally = yes     ; Only substitute the externaddr or externhost setting if it matches
                                 ; your localnet setting. Unless you have some sort of strange network
                                 ; setup you will not need to enable this.
diff --git a/contrib/realtime/mysql/sippeers.sql b/contrib/realtime/mysql/sippeers.sql
index e0dbe1a430..1b01b28091 100644
--- a/contrib/realtime/mysql/sippeers.sql
+++ b/contrib/realtime/mysql/sippeers.sql
@@ -79,6 +79,7 @@ CREATE TABLE IF NOT EXISTS `sippeers` (
       `callingpres` enum('allowed_not_screened','allowed_passed_screen','allowed_failed_screen','allowed','prohib_not_screened','prohib_passed_screen','prohib_failed_screen','prohib') DEFAULT NULL,
       `mohinterpret` varchar(40) DEFAULT NULL,
       `mohsuggest` varchar(40) DEFAULT NULL,
+      `path` varchar(256) DEFAULT NULL,
       `parkinglot` varchar(40) DEFAULT NULL,
       `hasvoicemail` enum('yes','no') DEFAULT NULL,
       `subscribemwi` enum('yes','no') DEFAULT NULL,
@@ -90,6 +91,7 @@ CREATE TABLE IF NOT EXISTS `sippeers` (
       `ignoresdpversion` enum('yes','no') DEFAULT NULL,
       `allowtransfer` enum('yes','no') DEFAULT NULL,
       `dynamic` enum('yes','no') DEFAULT NULL,
+      `supportpath` enum('yes','no') DEFAULT NULL,
       PRIMARY KEY (`id`),
       UNIQUE KEY `name` (`name`),
       KEY `ipaddr` (`ipaddr`,`port`),
diff --git a/contrib/realtime/postgresql/realtime.sql b/contrib/realtime/postgresql/realtime.sql
index cba8d3895d..96cbb16c2a 100644
--- a/contrib/realtime/postgresql/realtime.sql
+++ b/contrib/realtime/postgresql/realtime.sql
@@ -38,6 +38,7 @@ amaflags character varying(7),
 callgroup character varying(10),
 callerid character varying(80),
 canreinvite character varying(3) DEFAULT 'yes',
+supportpath character varying(3) DEFAULT 'no',
 context character varying(80),
 defaultip character varying(15),
 dtmfmode character varying(7),
@@ -70,6 +71,7 @@ cancallforward character varying(3) DEFAULT 'yes',
 lastms integer DEFAULT 0 NOT NULL,
 defaultuser character varying(80),
 fullcontact character varying(80),
+path character varying(256),
 regserver character varying(30),
 useragent character varying(40),
 callbackextension character varying(40)
diff --git a/contrib/scripts/asterisk.ldap-schema b/contrib/scripts/asterisk.ldap-schema
index 3d81a1b931..85f9103e29 100644
--- a/contrib/scripts/asterisk.ldap-schema
+++ b/contrib/scripts/asterisk.ldap-schema
@@ -112,7 +112,7 @@ objectIdentifier AstAccountSetVar AstAttrType:66
 objectIdentifier AstAccountAllowOverlap AstAttrType:67
 objectIdentifier AstAccountVideoSupport AstAttrType:68
 objectIdentifier AstAccountIgnoreSDPVersion AstAttrType:69
-
+objectIdentifier AstAccountPathSupport AstAttrType:70
 
 #############################################################################
 # Object Class OIDs
@@ -640,6 +640,13 @@ attributetype ( AstAccountIgnoreSDPVersion
         SUBSTR caseIgnoreSubstringsMatch
         SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
 
+attributetype ( AstAccountPathSupport
+        NAME 'AstAccountPathSupport'
+        DESC 'Asterisk account support Path RFC 3327'
+        EQUALITY caseIgnoreMatch
+        SUBSTR caseIgnoreSubstringsMatch
+        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
+
 #############################################################################
 # Object Class definitions
 #
@@ -762,7 +769,8 @@ objectclass ( AsteriskSIPUser
         AstAccountTransport $
         AstAccountType $ 
         AstAccountUserAgent $	
-        AstAccountVideoSupport
+        AstAccountVideoSupport $
+        AstAccountPathSupport
     )
     )
 
diff --git a/contrib/scripts/asterisk.ldif b/contrib/scripts/asterisk.ldif
index 0546cdd7cc..562105730a 100644
--- a/contrib/scripts/asterisk.ldif
+++ b/contrib/scripts/asterisk.ldif
@@ -108,6 +108,7 @@ olcObjectIdentifier: AstAccountSetVar AstAttrType:66
 olcObjectIdentifier: AstAccountAllowOverlap AstAttrType:67
 olcObjectIdentifier: AstAccountVideoSupport AstAttrType:68
 olcObjectIdentifier: AstAccountIgnoreSDPVersion AstAttrType:69
+olcObjectIdentifier: AstAccountPathSupport AstAttrType:70
 #
 #
 #############################################################################
@@ -636,6 +637,13 @@ olcAttributeTypes: ( AstAccountIgnoreSDPVersion
         SUBSTR caseIgnoreSubstringsMatch
         SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
 #
+olcAttributeTypes: ( AstAccountPathSupport
+        NAME 'AstAccountPathSupport'
+        DESC 'Asterisk account support Path RFC 3327'
+        EQUALITY caseIgnoreMatch
+        SUBSTR caseIgnoreSubstringsMatch
+        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
+#
 #############################################################################
 # Object Class definitions
 #
@@ -758,7 +766,8 @@ olcObjectClasses: ( AsteriskSIPUser
         AstAccountTransport $
         AstAccountType $
         AstAccountUserAgent $
-        AstAccountVideoSupport
+        AstAccountVideoSupport $
+        AstAccountPathSupport
     )
     )
 #
-- 
GitLab