diff --git a/CHANGES b/CHANGES
index 3f7315ea8ae10a46e33a51910966668bf34724da..bdd89b808f25dfa20f38411542159bcfa65be883 100644
--- a/CHANGES
+++ b/CHANGES
@@ -45,6 +45,11 @@ AMI (Asterisk Manager Interface) changes
  * Originate now generates an error response if the extension given
    is not found in the dialplan
 
+FAX changes
+-----------
+ * FAXOPT(faxdetect) will enable a generic fax detect framehook for dialplan
+   control of faxdetect.
+
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 1.8 to Asterisk 10 -------------------
 ------------------------------------------------------------------------------
diff --git a/include/asterisk/res_fax.h b/include/asterisk/res_fax.h
index b296321b254ec9744fe8abdf778396cbb9d48070..ba20944e40267f1f38c6e29049482e213ba5e2b6 100644
--- a/include/asterisk/res_fax.h
+++ b/include/asterisk/res_fax.h
@@ -174,6 +174,8 @@ struct ast_fax_session_details {
 	int gateway_id;
 	/*! the timeout for this gateway in seconds */
 	int gateway_timeout;
+	/*! the id of the faxdetect framehook for this channel */
+	int faxdetect_id;
 };
 
 struct ast_fax_tech;
diff --git a/res/res_fax.c b/res/res_fax.c
index 8966e01bf5b5778e9f104c48b44e0a75832708e3..c914edd4992c68b0c06729c55013cd19456dbeba 100644
--- a/res/res_fax.c
+++ b/res/res_fax.c
@@ -192,6 +192,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 					<enum name="gateway">
 						<para>R/W T38 fax gateway, with optional fax activity timeout in seconds (yes[,timeout]/no)</para>
 					</enum>
+					<enum name="faxdetect">
+						<para>R/W Enable FAX detect with optional timeout in seconds (yes,t38,cng[,timeout]/no)</para>
+					</enum>
 					<enum name="pages">
 						<para>R/O Number of pages transferred.</para>
 					</enum>
@@ -268,6 +271,27 @@ struct fax_gateway {
 	struct ast_format peer_write_format;
 };
 
+/*! \brief used for fax detect framehook */
+struct fax_detect {
+	/*! \brief the start of our timeout counter */
+	struct timeval timeout_start;
+	/*! \brief faxdetect timeout */
+	int timeout;
+	/*! \brief DSP Processor */
+	struct ast_dsp *dsp;
+	/*! \brief original audio formats */
+	struct ast_format orig_format;
+	/*! \brief fax session details */
+	struct ast_fax_session_details *details;
+	/*! \brief mode */
+	int flags;
+};
+
+/*! \brief FAX Detect flags */
+#define FAX_DETECT_MODE_CNG	(1 << 0)
+#define FAX_DETECT_MODE_T38	(1 << 1)
+#define	FAX_DETECT_MODE_BOTH	(FAX_DETECT_MODE_CNG | FAX_DETECT_MODE_T38)
+
 static int fax_logger_level = -1;
 
 /*! \brief maximum buckets for res_fax ao2 containers */
@@ -452,6 +476,7 @@ static struct ast_fax_session_details *session_details_new(void)
 	d->minrate = general_options.minrate;
 	d->maxrate = general_options.maxrate;
 	d->gateway_id = -1;
+	d->faxdetect_id = -1;
 	d->gateway_timeout = 0;
 
 	return d;
@@ -3124,6 +3149,242 @@ static int fax_gateway_attach(struct ast_channel *chan, struct ast_fax_session_d
 	return gateway->framehook;
 }
 
+/*! \brief destroy a FAX detect structure */
+static void destroy_faxdetect(void *data)
+{
+	struct fax_detect *faxdetect = data;
+
+	if (faxdetect->dsp) {
+		ast_dsp_free(faxdetect->dsp);
+		faxdetect->dsp = NULL;
+	}
+	ao2_ref(faxdetect->details, -1);
+}
+
+/*! \brief Create a new fax detect object.
+ * \param chan the channel attaching to
+ * \param timeout remove framehook in this time if set
+ * \param flags required options
+ * \return NULL or a fax gateway object
+ */
+static struct fax_detect *fax_detect_new(struct ast_channel *chan, int timeout, int flags)
+{
+	struct fax_detect *faxdetect = ao2_alloc(sizeof(*faxdetect), destroy_faxdetect);
+	if (!faxdetect) {
+		return NULL;
+	}
+
+	faxdetect->flags = flags;
+
+	if (timeout) {
+		faxdetect->timeout_start = ast_tvnow();
+	} else {
+		faxdetect->timeout_start.tv_sec = 0;
+		faxdetect->timeout_start.tv_usec = 0;
+	}
+
+	if (faxdetect->flags & FAX_DETECT_MODE_CNG) {
+		faxdetect->dsp = ast_dsp_new();
+		if (!faxdetect->dsp) {
+			ao2_ref(faxdetect, -1);
+			return NULL;
+		}
+		ast_dsp_set_features(faxdetect->dsp, DSP_FEATURE_FAX_DETECT);
+		ast_dsp_set_faxmode(faxdetect->dsp, DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_SQUELCH);
+	} else {
+		faxdetect->dsp = NULL;
+	}
+
+	return faxdetect;
+}
+
+/*! \brief Deref the faxdetect data structure when the faxdetect framehook is detached
+ * \param data framehook data (faxdetect data)*/
+static void fax_detect_framehook_destroy(void *data) {
+	struct fax_detect *faxdetect = data;
+
+	ao2_ref(faxdetect, -1);
+}
+
+/*! \brief Fax Detect Framehook
+ *
+ * Listen for fax tones in audio path and enable jumping to a extension when detected.
+ *
+ * \param chan channel
+ * \param f frame to handle may be NULL
+ * \param event framehook event
+ * \param data framehook data (struct fax_detect *)
+ *
+ * \return processed frame or NULL when f is NULL or a null frame
+ */
+static struct ast_frame *fax_detect_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data) {
+	struct fax_detect *faxdetect = data;
+	struct ast_fax_session_details *details;
+	struct ast_control_t38_parameters *control_params;
+	struct ast_channel *peer;
+	int result = 0;
+
+	details = faxdetect->details;
+
+	switch (event) {
+	case AST_FRAMEHOOK_EVENT_ATTACHED:
+		/* Setup format for DSP on ATTACH*/
+		ast_format_copy(&faxdetect->orig_format, &chan->readformat);
+		switch (chan->readformat.id) {
+			case AST_FORMAT_SLINEAR:
+			case AST_FORMAT_ALAW:
+			case AST_FORMAT_ULAW:
+				break;
+			default:
+				if (ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR)) {
+					ast_framehook_detach(chan, details->faxdetect_id);
+					details->faxdetect_id = -1;
+					return f;
+				}
+		}
+		return NULL;
+	case AST_FRAMEHOOK_EVENT_DETACHED:
+		/* restore audio formats when we are detached */
+		ast_set_read_format(chan, &faxdetect->orig_format);
+		if ((peer = ast_bridged_channel(chan))) {
+			ast_channel_make_compatible(chan, peer);
+		}
+		return NULL;
+	case AST_FRAMEHOOK_EVENT_READ:
+		if (f) {
+			break;
+		}
+	default:
+		return f;
+	};
+
+	if (details->faxdetect_id < 0) {
+		return f;
+	}
+
+	if ((!ast_tvzero(faxdetect->timeout_start) &&
+	    (ast_tvdiff_ms(ast_tvnow(), faxdetect->timeout_start) > faxdetect->timeout))) {
+		ast_framehook_detach(chan, details->faxdetect_id);
+		details->faxdetect_id = -1;
+		return f;
+	}
+
+	/* only handle VOICE and CONTROL frames*/
+	switch (f->frametype) {
+	case AST_FRAME_VOICE:
+		/* we have no DSP this means we not detecting CNG */
+		if (!faxdetect->dsp) {
+			break;
+		}
+		/* We can only process some formats*/
+		switch (f->subclass.format.id) {
+			case AST_FORMAT_SLINEAR:
+			case AST_FORMAT_ALAW:
+			case AST_FORMAT_ULAW:
+				break;
+			default:
+				return f;
+		}
+		break;
+	case AST_FRAME_CONTROL:
+		if ((f->subclass.integer == AST_CONTROL_T38_PARAMETERS) &&
+		    (faxdetect->flags & FAX_DETECT_MODE_T38)) {
+			break;
+		}
+		return f;
+	default:
+		return f;
+	}
+
+	if (f->frametype == AST_FRAME_VOICE) {
+		f = ast_dsp_process(chan, faxdetect->dsp, f);
+		if (f->frametype == AST_FRAME_DTMF) {
+			result = f->subclass.integer;
+		}
+	} else if ((f->frametype == AST_FRAME_CONTROL) && (f->datalen == sizeof(struct ast_control_t38_parameters))) {
+		control_params = f->data.ptr;
+		switch (control_params->request_response) {
+		case AST_T38_NEGOTIATED:
+		case AST_T38_REQUEST_NEGOTIATE:
+			result = 't';
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (result) {
+		const char *target_context = S_OR(chan->macrocontext, chan->context);
+		switch (result) {
+		case 'f':
+		case 't':
+			ast_channel_unlock(chan);
+			if (ast_exists_extension(chan, target_context, "fax", 1,
+			    S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
+				ast_channel_lock(chan);
+				ast_verbose(VERBOSE_PREFIX_2 "Redirecting '%s' to fax extension due to %s detection\n",
+					chan->name, (result == 'f') ? "CNG" : "T38");
+				pbx_builtin_setvar_helper(chan, "FAXEXTEN", chan->exten);
+				if (ast_async_goto(chan, target_context, "fax", 1)) {
+					ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", chan->name, target_context);
+				}
+				ast_frfree(f);
+				f = &ast_null_frame;
+			} else {
+				ast_channel_lock(chan);
+				ast_log(LOG_NOTICE, "FAX %s detected but no fax extension in context (%s)\n",
+					(result == 'f') ? "CNG" : "T38", target_context);
+			}
+		}
+		ast_framehook_detach(chan, details->faxdetect_id);
+		details->faxdetect_id = -1;
+	}
+
+	return f;
+}
+
+/*! \brief Attach a faxdetect framehook object to a channel.
+ * \param chan the channel to attach to
+ * \param timeout remove framehook in this time if set
+ * \return the faxdetect structure or NULL on error
+ * \param flags required options
+ * \retval -1 error
+ */
+static int fax_detect_attach(struct ast_channel *chan, int timeout, int flags)
+{
+	struct fax_detect *faxdetect;
+	struct ast_fax_session_details *details;
+	struct ast_framehook_interface fr_hook = {
+		.version = AST_FRAMEHOOK_INTERFACE_VERSION,
+		.event_cb = fax_detect_framehook,
+		.destroy_cb = fax_detect_framehook_destroy,
+	};
+
+	if (!(details = find_or_create_details(chan))) {
+		ast_log(LOG_ERROR, "System cannot provide memory for session requirements.\n");
+		return -1;
+	}
+
+	/* set up the frame hook*/
+	faxdetect = fax_detect_new(chan, timeout, flags);
+	if (!faxdetect) {
+		ao2_ref(details, -1);
+		return -1;
+	}
+
+	fr_hook.data = faxdetect;
+	faxdetect->details = details;
+	ast_channel_lock(chan);
+	details->faxdetect_id = ast_framehook_attach(chan, &fr_hook);
+	ast_channel_unlock(chan);
+
+	if (details->faxdetect_id < 0) {
+		ao2_ref(faxdetect, -1);
+	}
+
+	return details->faxdetect_id;
+}
+
 /*! \brief hash callback for ao2 */
 static int session_hash_cb(const void *obj, const int flags)
 {
@@ -3525,6 +3786,8 @@ static int acf_faxopt_read(struct ast_channel *chan, const char *cmd, char *data
 	} else if (!strcasecmp(data, "t38gateway") || !strcasecmp(data, "gateway") ||
 		   !strcasecmp(data, "t38_gateway") || !strcasecmp(data, "faxgateway")) {
 		ast_copy_string(buf, details->gateway_id != -1 ? "yes" : "no", len);
+	} else if (!strcasecmp(data, "faxdetect")) {
+		ast_copy_string(buf, details->faxdetect_id != -1 ? "yes" : "no", len);
 	} else if (!strcasecmp(data, "error")) {
 		ast_copy_string(buf, details->error, len);
 	} else if (!strcasecmp(data, "filename")) {
@@ -3636,6 +3899,51 @@ static int acf_faxopt_write(struct ast_channel *chan, const char *cmd, char *dat
 		} else {
 			ast_log(LOG_WARNING, "Unsupported value '%s' passed to FAXOPT(%s).\n", value, data);
 		}
+	} else if (!strcasecmp(data, "faxdetect")) {
+		const char *val = ast_skip_blanks(value);
+		char *timeout = strchr(val, ',');
+		unsigned int fdtimeout = 0;
+		int flags;
+		int faxdetect;
+
+		if (timeout) {
+			*timeout++ = '\0';
+		}
+
+		if (ast_true(val) || !strcasecmp(val, "t38") || !strcasecmp(val, "cng")) {
+			if (details->faxdetect_id < 0) {
+				if (timeout && (sscanf(timeout, "%u", &fdtimeout) == 1)) {
+					if (fdtimeout > 0) {
+						fdtimeout = fdtimeout * 1000;
+					} else {
+						ast_log(LOG_WARNING, "Timeout cannot be negative ignoring timeout\n");
+					}
+				}
+
+				if (!strcasecmp(val, "t38")) {
+					flags = FAX_DETECT_MODE_T38;
+				} else if (!strcasecmp(val, "cng")) {
+					flags = FAX_DETECT_MODE_CNG;
+				} else {
+					flags = FAX_DETECT_MODE_BOTH;
+				}
+
+				faxdetect = fax_detect_attach(chan, fdtimeout, flags);
+				if (faxdetect < 0) {
+					ast_log(LOG_ERROR, "Error attaching FAX detect to channel %s.\n", chan->name);
+					res = -1;
+				} else {
+					ast_debug(1, "Attached FAX detect to channel %s.\n", chan->name);
+				}
+			} else {
+				ast_log(LOG_WARNING, "Attempt to attach a FAX detect on channel (%s) with FAX detect already running.\n", chan->name);
+			}
+		} else if (ast_false(val)) {
+			ast_framehook_detach(chan, details->faxdetect_id);
+			details->faxdetect_id = -1;
+		} else {
+			ast_log(LOG_WARNING, "Unsupported value '%s' passed to FAXOPT(%s).\n", value, data);
+		}
 	} else if (!strcasecmp(data, "headerinfo")) {
 		ast_string_field_set(details, headerinfo, value);
 	} else if (!strcasecmp(data, "localstationid")) {