diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 066751f..8a3dc73 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -2175,6 +2175,106 @@ rte_eth_tx_hairpin_queue_setup(uint16_t port_id, uint16_t tx_queue_id,
 	return eth_err(port_id, ret);
 }
 
+int
+rte_eth_hairpin_bind(uint16_t tx_port, uint16_t rx_port)
+{
+	struct rte_eth_dev *dev;
+	struct rte_eth_dev *rdev;
+	uint16_t p;
+	uint16_t rp;
+	int ret = 0;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(tx_port, -EINVAL);
+	dev = &rte_eth_devices[tx_port];
+	if (!dev->data->dev_started) {
+		RTE_ETHDEV_LOG(ERR, "TX port %d is not started", tx_port);
+		return -EBUSY;
+	}
+
+	if (rx_port == RTE_MAX_ETHPORTS) {
+		RTE_ETH_FOREACH_DEV(p) {
+			rdev = &rte_eth_devices[p];
+			if (!rdev->data->dev_started) {
+				RTE_ETHDEV_LOG(ERR,
+					       "RX port %d is not started", p);
+				ret = -EBUSY;
+				goto error;
+			}
+			ret = (*dev->dev_ops->hairpin_bind)(dev, p);
+			if (ret) {
+				RTE_ETHDEV_LOG(ERR, "Failed to bind hairpin TX "
+					       "%d to RX %d", tx_port, p);
+				goto error;
+			}
+		}
+	} else {
+		RTE_ETH_VALID_PORTID_OR_ERR_RET(rx_port, -EINVAL);
+		rdev = &rte_eth_devices[rx_port];
+		if (!rdev->data->dev_started) {
+			RTE_ETHDEV_LOG(ERR,
+				       "RX port %d is not started", rx_port);
+			return -EBUSY;
+		}
+		ret = (*dev->dev_ops->hairpin_bind)(dev, rx_port);
+		if (ret)
+			RTE_ETHDEV_LOG(ERR, "Failed to bind hairpin TX %d "
+				       "to RX %d", tx_port, rx_port);
+	}
+
+	return ret;
+
+error:
+	RTE_ETH_FOREACH_DEV(rp) {
+		if (rp < p)
+			(*dev->dev_ops->hairpin_unbind)(dev, rp);
+	}
+	return ret;
+}
+
+int
+rte_eth_hairpin_unbind(uint16_t tx_port, uint16_t rx_port)
+{
+	struct rte_eth_dev *dev;
+	struct rte_eth_dev *rdev;
+	uint16_t p;
+	uint16_t rp;
+	int ret = 0;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(tx_port, -EINVAL);
+	dev = &rte_eth_devices[tx_port];
+	if (!dev->data->dev_started) {
+		RTE_ETHDEV_LOG(ERR, "TX port %d is stopped", tx_port);
+		return -EBUSY;
+	}
+
+	if (rx_port == RTE_MAX_ETHPORTS) {
+		RTE_ETH_FOREACH_DEV(p) {
+			rdev = &rte_eth_devices[p];
+			if (!rdev->data->dev_started) {
+				RTE_ETHDEV_LOG(ERR, "RX port %d is stopped", p);
+				ret = -EBUSY;
+				break;
+			}
+			ret = (*dev->dev_ops->hairpin_unbind)(dev, p);
+			if (ret) {
+				RTE_ETHDEV_LOG(ERR, "Failed to unbind hairpin "
+					       "TX %d from RX %d", tx_port, p);
+				break;
+			}
+		}
+	} else {
+		RTE_ETH_VALID_PORTID_OR_ERR_RET(rx_port, -EINVAL);
+		rdev = &rte_eth_devices[rx_port];
+		if (!rdev->data->dev_started) {
+			RTE_ETHDEV_LOG(ERR, "RX port %d is stopped", rx_port);
+			return -EBUSY;
+		}
+		ret = (*dev->dev_ops->hairpin_unbind)(dev, rx_port);
+	}
+
+	return ret;
+}
+
 void
 rte_eth_tx_buffer_drop_callback(struct rte_mbuf **pkts, uint16_t unsent,
 		void *userdata __rte_unused)
diff --git a/lib/librte_ethdev/rte_ethdev.h b/lib/librte_ethdev/rte_ethdev.h
index e7733d8..fb217b4 100644
--- a/lib/librte_ethdev/rte_ethdev.h
+++ b/lib/librte_ethdev/rte_ethdev.h
@@ -2101,6 +2101,57 @@ int rte_eth_tx_hairpin_queue_setup
 	 const struct rte_eth_hairpin_conf *conf);
 
 /**
+ * @warning
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice
+ *
+ * Bind all hairpin TX queues of one port to the RX queues of the peer port.
+ * Only allowed after all hairpin queues are configured properly and the
+ * devices of TX and peer RX are in started state.
+ *
+ * @param tx_port
+ *   The TX port identifier of the Ethernet device.
+ * @param rx_port
+ *   The peer RX port identifier of the Ethernet device.
+ *   RTE_MAX_ETHPORTS is allowed for traversal of all devices.
+ *   RX port ID could have the same value with TX port ID.
+ *
+ * @return
+ *   - (0) if successful.
+ *   - (-EINVAL) if bad parameter.
+ *   - (-EBUSY) if device is not in started state.
+ *   - (-ENOTSUP) if hardware doesn't support.
+ *   - Others detailed errors from PMD drivers.
+ */
+__rte_experimental
+int rte_eth_hairpin_bind(uint16_t tx_port, uint16_t rx_port);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice
+ *
+ * Unbind all hairpin TX queues of one port from the RX queues of the peer port.
+ * This should be called before closing the TX or RX devices (optional). After
+ * unbind the hairpin ports pair, it is allowed to bind them again.
+ * Changing queues configuration should be after stopping a device.
+ *
+ * @param tx_port
+ *   The TX port identifier of the Ethernet device.
+ * @param rx_port
+ *   The peer RX port identifier of the Ethernet device.
+ *   RTE_MAX_ETHPORTS is allowed for traversal of all devices.
+ *   RX port ID could have the same value with TX port ID.
+ *
+ * @return
+ *   - (0) if successful.
+ *   - (-EINVAL) if bad parameter.
+ *   - (-EBUSY) if device is not in started state.
+ *   - (-ENOTSUP) if hardware doesn't support.
+ *   - Others detailed errors from PMD drivers.
+ */
+__rte_experimental
+int rte_eth_hairpin_unbind(uint16_t tx_port, uint16_t rx_port);
+
+/**
  * Return the NUMA socket to which an Ethernet device is connected
  *
  * @param port_id
diff --git a/lib/librte_ethdev/rte_ethdev_driver.h b/lib/librte_ethdev/rte_ethdev_driver.h
index 23cc1e0..b4efad6 100644
--- a/lib/librte_ethdev/rte_ethdev_driver.h
+++ b/lib/librte_ethdev/rte_ethdev_driver.h
@@ -575,6 +575,54 @@ typedef int (*eth_tx_hairpin_queue_setup_t)
 	 const struct rte_eth_hairpin_conf *hairpin_conf);
 
 /**
+ * @internal
+ * Bind all hairpin TX queues of one port to the RX queues of the peer port.
+ *
+ * @param dev
+ *   ethdev handle of port.
+ * @param rx_port
+ *   the peer RX port.
+ *
+ * @return
+ *   Negative errno value on error, 0 on success.
+ *
+ * @retval 0
+ *   Success, bind successfully.
+ * @retval -ENOTSUP
+ *   Bind API is not supported.
+ * @retval -EINVAL
+ *   One of the parameters is invalid.
+ * @retval -EBUSY
+ *   Device is not started.
+ */
+typedef int (*eth_hairpin_bind)(struct rte_eth_dev *dev,
+				uint16_t rx_port);
+
+/**
+ * @internal
+ * Unbind all hairpin TX queues of one port from the RX queues of the peer port.
+ *
+ * @param dev
+ *   ethdev handle of port.
+ * @param rx_port
+ *   the peer RX port.
+ *
+ * @return
+ *   Negative errno value on error, 0 on success.
+ *
+ * @retval 0
+ *   Success, bind successfully.
+ * @retval -ENOTSUP
+ *   Bind API is not supported.
+ * @retval -EINVAL
+ *   One of the parameters is invalid.
+ * @retval -EBUSY
+ *   Device is already stopped.
+ */
+typedef int (*eth_hairpin_unbind)(struct rte_eth_dev *dev,
+				  uint16_t rx_port);
+
+/**
  * @internal A structure containing the functions exported by an Ethernet driver.
  */
 struct eth_dev_ops {
@@ -713,6 +761,10 @@ struct eth_dev_ops {
 	/**< Set up device RX hairpin queue. */
 	eth_tx_hairpin_queue_setup_t tx_hairpin_queue_setup;
 	/**< Set up device TX hairpin queue. */
+	eth_hairpin_bind hairpin_bind;
+	/**< Bind all hairpin TX queues of device to the peer port RX queues. */
+	eth_hairpin_unbind hairpin_unbind;
+	/**< Unbind all hairpin TX queues from the peer port RX queues. */
 };
 
 /**
