diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 0dbac5f85f40bea97bb7a2ea3f11a4653989d284..f34b9cf4783f7251bbc27733fceb992fa154ad48 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -1303,6 +1303,92 @@ static void iax2_lock_owner(int callno)
 	}
 }
 
+/*!
+ * \internal
+ * \brief Check if a control subtype is allowed on the wire.
+ *
+ * \param subtype Control frame subtype to check if allowed to/from the wire.
+ *
+ * \retval non-zero if allowed.
+ */
+static int iax2_is_control_frame_allowed(int subtype)
+{
+	enum ast_control_frame_type control = subtype;
+	int is_allowed;
+
+	/*
+	 * Note: If we compare the enumeration type, which does not have any
+	 * negative constants, the compiler may optimize this code away.
+	 * Therefore, we must perform an integer comparison here.
+	 */
+	if (subtype == -1) {
+		return -1;
+	}
+
+	/* Default to not allowing control frames to pass. */
+	is_allowed = 0;
+
+	/*
+	 * The switch default is not present in order to take advantage
+	 * of the compiler complaining of a missing enum case.
+	 */
+	switch (control) {
+	/*
+	 * These control frames make sense to send/receive across the link.
+	 */
+	case AST_CONTROL_HANGUP:
+	case AST_CONTROL_RING:
+	case AST_CONTROL_RINGING:
+	case AST_CONTROL_ANSWER:
+	case AST_CONTROL_BUSY:
+	case AST_CONTROL_TAKEOFFHOOK:
+	case AST_CONTROL_OFFHOOK:
+	case AST_CONTROL_CONGESTION:
+	case AST_CONTROL_FLASH:
+	case AST_CONTROL_WINK:
+	case AST_CONTROL_OPTION:
+	case AST_CONTROL_RADIO_KEY:
+	case AST_CONTROL_RADIO_UNKEY:
+	case AST_CONTROL_PROGRESS:
+	case AST_CONTROL_PROCEEDING:
+	case AST_CONTROL_HOLD:
+	case AST_CONTROL_UNHOLD:
+	case AST_CONTROL_VIDUPDATE:
+	case AST_CONTROL_CONNECTED_LINE:
+	case AST_CONTROL_REDIRECTING:
+	case AST_CONTROL_T38_PARAMETERS:
+	case AST_CONTROL_AOC:
+	case AST_CONTROL_INCOMPLETE:
+	case AST_CONTROL_MCID:
+		is_allowed = -1;
+		break;
+
+	/*
+	 * These control frames do not make sense to send/receive across the link.
+	 */
+	case _XXX_AST_CONTROL_T38:
+		/* The control value is deprecated in favor of AST_CONTROL_T38_PARAMETERS. */
+	case AST_CONTROL_SRCUPDATE:
+		/* Across an IAX link the source is still the same. */
+	case AST_CONTROL_TRANSFER:
+		/* A success/fail status report from calling ast_transfer() on this machine. */
+	case AST_CONTROL_CC:
+		/* The payload contains pointers that are valid for the sending machine only. */
+	case AST_CONTROL_SRCCHANGE:
+		/* Across an IAX link the source is still the same. */
+	case AST_CONTROL_READ_ACTION:
+		/* The action can only be done by the sending machine. */
+	case AST_CONTROL_END_OF_Q:
+		/* This frame would cause the call to unexpectedly hangup. */
+	case AST_CONTROL_UPDATE_RTP_PEER:
+		/* Only meaningful across a bridge on this machine for direct-media exchange. */
+	case AST_CONTROL_PVT_CAUSE_CODE:
+		/* Intended only for the sending machine's local channel structure. */
+		break;
+	}
+	return is_allowed;
+}
+
 static void mwi_event_cb(const struct ast_event *event, void *userdata)
 {
 	/* The MWI subscriptions exist just so the core knows we care about those
@@ -5752,8 +5838,13 @@ static int iax2_indicate(struct ast_channel *c, int condition, const void *data,
 		}
 		break;
 	case AST_CONTROL_CONNECTED_LINE:
-		if (!ast_test_flag64(pvt, IAX_SENDCONNECTEDLINE))
+	case AST_CONTROL_REDIRECTING:
+		if (!ast_test_flag64(pvt, IAX_SENDCONNECTEDLINE)) {
+			/* We are not configured to allow sending these updates. */
+			ast_debug(2, "Callno %u: Config blocked sending control frame %d.\n",
+				callno, condition);
 			goto done;
+		}
 		break;
 	case AST_CONTROL_PVT_CAUSE_CODE:
 		res = -1;
@@ -7599,6 +7690,12 @@ static int __send_command(struct chan_iax2_pvt *i, char type, int command, unsig
 
 static int send_command(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno)
 {
+	if (type == AST_FRAME_CONTROL && !iax2_is_control_frame_allowed(command)) {
+		/* Control frame should not go out on the wire. */
+		ast_debug(2, "Callno %u: Blocked sending control frame %d.\n",
+			i->callno, command);
+		return 0;
+	}
 	return __send_command(i, type, command, ts, data, datalen, seqno, 0, 0, 0);
 }
 
@@ -10566,13 +10663,6 @@ static int socket_process_helper(struct iax2_thread *thread)
 				iaxs[fr->callno]->videoformat = ast_format_to_old_bitfield(&f.subclass.format);
 			}
 		}
-		if (f.frametype == AST_FRAME_CONTROL && iaxs[fr->callno]->owner) {
-			if (f.subclass.integer == AST_CONTROL_BUSY) {
-				ast_channel_hangupcause_set(iaxs[fr->callno]->owner, AST_CAUSE_BUSY);
-			} else if (f.subclass.integer == AST_CONTROL_CONGESTION) {
-				ast_channel_hangupcause_set(iaxs[fr->callno]->owner, AST_CAUSE_CONGESTION);
-			}
-		}
 		if (f.frametype == AST_FRAME_IAX) {
 			AST_SCHED_DEL(sched, iaxs[fr->callno]->initid);
 			/* Handle the IAX pseudo frame itself */
@@ -11783,17 +11873,48 @@ immediatedial:
 		ast_mutex_unlock(&iaxsl[fr->callno]);
 		return 1;
 	}
-	/* Don't allow connected line updates unless we are configured to */
-	if (f.frametype == AST_FRAME_CONTROL && f.subclass.integer == AST_CONTROL_CONNECTED_LINE) {
-		struct ast_party_connected_line connected;
 
-		if (!ast_test_flag64(iaxs[fr->callno], IAX_RECVCONNECTEDLINE)) {
+	if (f.frametype == AST_FRAME_CONTROL) {
+		if (!iax2_is_control_frame_allowed(f.subclass.integer)) {
+			/* Control frame not allowed to come from the wire. */
+			ast_debug(2, "Callno %u: Blocked receiving control frame %d.\n",
+				fr->callno, f.subclass.integer);
 			ast_variables_destroy(ies.vars);
 			ast_mutex_unlock(&iaxsl[fr->callno]);
 			return 1;
 		}
+		if (f.subclass.integer == AST_CONTROL_CONNECTED_LINE
+			|| f.subclass.integer == AST_CONTROL_REDIRECTING) {
+			if (!ast_test_flag64(iaxs[fr->callno], IAX_RECVCONNECTEDLINE)) {
+				/* We are not configured to allow receiving these updates. */
+				ast_debug(2, "Callno %u: Config blocked receiving control frame %d.\n",
+					fr->callno, f.subclass.integer);
+				ast_variables_destroy(ies.vars);
+				ast_mutex_unlock(&iaxsl[fr->callno]);
+				return 1;
+			}
+		}
+
+		iax2_lock_owner(fr->callno);
+		if (iaxs[fr->callno] && iaxs[fr->callno]->owner) {
+			if (f.subclass.integer == AST_CONTROL_BUSY) {
+				ast_channel_hangupcause_set(iaxs[fr->callno]->owner, AST_CAUSE_BUSY);
+			} else if (f.subclass.integer == AST_CONTROL_CONGESTION) {
+				ast_channel_hangupcause_set(iaxs[fr->callno]->owner, AST_CAUSE_CONGESTION);
+			}
+			ast_channel_unlock(iaxs[fr->callno]->owner);
+		}
+	}
+
+	if (f.frametype == AST_FRAME_CONTROL
+		&& f.subclass.integer == AST_CONTROL_CONNECTED_LINE) {
+		struct ast_party_connected_line connected;
 
-		/* Initialize defaults */
+		/*
+		 * Process a received connected line update.
+		 *
+		 * Initialize defaults.
+		 */
 		ast_party_connected_line_init(&connected);
 		connected.id.number.presentation = iaxs[fr->callno]->calling_pres;
 		connected.id.name.presentation = iaxs[fr->callno]->calling_pres;
@@ -11816,6 +11937,7 @@ immediatedial:
 		}
 		ast_party_connected_line_free(&connected);
 	}
+
 	/* Common things */
 	f.src = "IAX2";
 	f.mallocd = 0;
diff --git a/configs/iax.conf.sample b/configs/iax.conf.sample
index 0de3d3573ba49bcbb3311a0b80f2cf9b0e198899..eb82ad8cd8afc292abbd85dc46d1fe9a12bdb410 100644
--- a/configs/iax.conf.sample
+++ b/configs/iax.conf.sample
@@ -607,14 +607,18 @@ description=Demo System At Digium    ; Description of this peer, as listed by
 ; IPs can also optionally be given but are not required.  Caller*ID can be
 ; suggested to the other side as well if it is for example a phone instead of
 ; another PBX.
-;connectedline=yes ; Set how connected line information is handled for this
-;                  ; peer. If set to "yes", both sending and receiving
-;                  ; connected line information will be enabled. If set to
-;                  ; "send", this peer will send connected line information
-;                  ; but will not process connected line updates. If set to
-;                  ; "receive", connected line updates will be processed
-;                  ; but not sent. If set to "no", connected line updates
-;                  ; will be disabled. Default is "no".
+;connectedline=yes ; Set if connected line and redirecting information updates
+;                  ; are passed between Asterisk servers for this peer.
+;                  ; yes - Sending and receiving updates are enabled.
+;                  ; send - Only send updates.
+;                  ; receive - Only process received updates.
+;                  ; no - Sending and receiving updates are disabled.
+;                  ; Default is "no".
+;                  ;
+;                  ; Note: Because of an incompatibility between Asterisk v1.4
+;                  ; and Asterisk v1.8 or later, this option must be set
+;                  ; to "no" toward the Asterisk v1.4 peer.  A symptom of the
+;                  ; incompatibility is the call gets disconnected unexpectedly.
 
 
 ;[dynamichost]
diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h
index 77c766711ae6311b639d9902fd4f724c67b00675..0ac0d5fd9747fd6b0d647e789b875b0af622888f 100644
--- a/include/asterisk/frame.h
+++ b/include/asterisk/frame.h
@@ -233,6 +233,18 @@ extern struct ast_frame ast_null_frame;
 /*! Reject link request */
 #define AST_HTML_LINKREJECT	20
 
+/*!
+ * \brief Internal control frame subtype field values.
+ *
+ * \warning
+ * IAX2 sends these values out over the wire.  To prevent future
+ * incompatibilities, pick the next value in the enum from whatever
+ * is on the current trunk.  If you lose the merge race you need to
+ * fix the previous branches to match what is on trunk.  In addition
+ * you need to change chan_iax2 to explicitly allow the control
+ * frame over the wire if it makes sense for the frame to be passed
+ * to another Asterisk instance.
+ */
 enum ast_control_frame_type {
 	AST_CONTROL_HANGUP = 1,			/*!< Other end has hungup */
 	AST_CONTROL_RING = 2,			/*!< Local ring */
@@ -267,6 +279,20 @@ enum ast_control_frame_type {
 	AST_CONTROL_MCID = 31,			/*!< Indicate that the caller is being malicious. */
 	AST_CONTROL_UPDATE_RTP_PEER = 32, /*!< Interrupt the bridge and have it update the peer */
 	AST_CONTROL_PVT_CAUSE_CODE = 33, /*!< Contains an update to the protocol-specific cause-code stored for branching dials */
+
+	/*
+	 * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+	 *
+	 * IAX2 sends these values out over the wire.  To prevent future
+	 * incompatibilities, pick the next value in the enum from whatever
+	 * is on the current trunk.  If you lose the merge race you need to
+	 * fix the previous branches to match what is on trunk.  In addition
+	 * you need to change chan_iax2 to explicitly allow the control
+	 * frame over the wire if it makes sense for the frame to be passed
+	 * to another Asterisk instance.
+	 *
+	 * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+	 */
 };
 
 enum ast_frame_read_action {