diff --git a/examples/ip_pipeline/app.h b/examples/ip_pipeline/app.h
index 6510d6d..f749a5f 100644
--- a/examples/ip_pipeline/app.h
+++ b/examples/ip_pipeline/app.h
@@ -49,7 +49,7 @@
 #include "pipeline.h"
 
 #define APP_PARAM_NAME_SIZE                      PIPELINE_NAME_SIZE
-
+#define APP_LINK_PCI_BDF_SIZE                    16
 struct app_mempool_params {
 	char *name;
 	uint32_t parsed;
@@ -64,7 +64,7 @@ struct app_link_params {
 	uint32_t parsed;
 	uint32_t pmd_id; /* Generated based on port mask */
 	uint32_t arp_q; /* 0 = Disabled (packets go to default queue 0) */
-	uint32_t tcp_syn_local_q; /* 0 = Disabled (pkts go to default queue) */
+	uint32_t tcp_syn_q; /* 0 = Disabled (pkts go to default queue) */
 	uint32_t ip_local_q; /* 0 = Disabled (pkts go to default queue 0) */
 	uint32_t tcp_local_q; /* 0 = Disabled (pkts go to default queue 0) */
 	uint32_t udp_local_q; /* 0 = Disabled (pkts go to default queue 0) */
@@ -73,6 +73,7 @@ struct app_link_params {
 	uint32_t ip; /* 0 = Invalid */
 	uint32_t depth; /* Valid only when IP is valid */
 	uint64_t mac_addr; /* Read from HW */
+	char pci_bdf[APP_LINK_PCI_BDF_SIZE];
 
 	struct rte_eth_conf conf;
 	uint8_t promisc;
@@ -265,6 +266,10 @@ struct app_thread_data {
 	struct rte_ring *msgq_out;
 };
 
+#ifndef APP_MAX_LINKS
+#define APP_MAX_LINKS                            16
+#endif
+
 struct app_eal_params {
 	/* Map lcore set to physical cpu set */
 	char *coremap;
@@ -286,13 +291,13 @@ struct app_eal_params {
 	uint32_t ranks;
 
 	/* Add a PCI device in black list. */
-	char *pci_blacklist;
+	char *pci_blacklist[APP_MAX_LINKS];
 
 	/* Add a PCI device in white list. */
-	char *pci_whitelist;
+	char *pci_whitelist[APP_MAX_LINKS];
 
 	/* Add a virtual device. */
-	char *vdev;
+	char *vdev[APP_MAX_LINKS];
 
 	 /* Use VMware TSC map instead of native RDTSC */
 	uint32_t vmware_tsc_map_present;
@@ -367,10 +372,6 @@ struct app_eal_params {
 #define APP_MAX_MEMPOOLS                         8
 #endif
 
-#ifndef APP_MAX_LINKS
-#define APP_MAX_LINKS                            16
-#endif
-
 #ifndef APP_LINK_MAX_HWQ_IN
 #define APP_LINK_MAX_HWQ_IN                      64
 #endif
diff --git a/examples/ip_pipeline/config_check.c b/examples/ip_pipeline/config_check.c
index 1ff5763..fd9ff49 100644
--- a/examples/ip_pipeline/config_check.c
+++ b/examples/ip_pipeline/config_check.c
@@ -59,12 +59,16 @@ check_mempools(struct app_params *app)
 static void
 check_links(struct app_params *app)
 {
-	uint32_t n_links_port_mask = __builtin_popcountll(app->port_mask);
 	uint32_t i;
 
 	/* Check that number of links matches the port mask */
-	APP_CHECK((app->n_links == n_links_port_mask),
-		"Not enough links provided in the PORT_MASK\n");
+	if (app->port_mask) {
+		uint32_t n_links_port_mask =
+			__builtin_popcountll(app->port_mask);
+
+		APP_CHECK((app->n_links == n_links_port_mask),
+			"Not enough links provided in the PORT_MASK\n");
+	}
 
 	for (i = 0; i < app->n_links; i++) {
 		struct app_link_params *link = &app->link_params[i];
@@ -76,8 +80,8 @@ check_links(struct app_params *app)
 		rxq_max = 0;
 		if (link->arp_q > rxq_max)
 			rxq_max = link->arp_q;
-		if (link->tcp_syn_local_q > rxq_max)
-			rxq_max = link->tcp_syn_local_q;
+		if (link->tcp_syn_q > rxq_max)
+			rxq_max = link->tcp_syn_q;
 		if (link->ip_local_q > rxq_max)
 			rxq_max = link->ip_local_q;
 		if (link->tcp_local_q > rxq_max)
@@ -89,7 +93,7 @@ check_links(struct app_params *app)
 
 		for (i = 1; i <= rxq_max; i++)
 			APP_CHECK(((link->arp_q == i) ||
-				(link->tcp_syn_local_q == i) ||
+				(link->tcp_syn_q == i) ||
 				(link->ip_local_q == i) ||
 				(link->tcp_local_q == i) ||
 				(link->udp_local_q == i) ||
diff --git a/examples/ip_pipeline/config_parse.c b/examples/ip_pipeline/config_parse.c
index 5f72af9..4695ac1 100644
--- a/examples/ip_pipeline/config_parse.c
+++ b/examples/ip_pipeline/config_parse.c
@@ -56,6 +56,7 @@
 static struct app_params app_params_default = {
 	.config_file = "./config/ip_pipeline.cfg",
 	.log_level = APP_LOG_LEVEL_HIGH,
+	.port_mask = 0,
 
 	.eal_params = {
 		.channels = 4,
@@ -74,7 +75,7 @@ static const struct app_link_params link_params_default = {
 	.parsed = 0,
 	.pmd_id = 0,
 	.arp_q = 0,
-	.tcp_syn_local_q = 0,
+	.tcp_syn_q = 0,
 	.ip_local_q = 0,
 	.tcp_local_q = 0,
 	.udp_local_q = 0,
@@ -83,6 +84,7 @@ static const struct app_link_params link_params_default = {
 	.ip = 0,
 	.depth = 0,
 	.mac_addr = 0,
+	.pci_bdf = {0},
 
 	.conf = {
 		.link_speed = 0,
@@ -205,12 +207,13 @@ struct app_pipeline_params default_pipeline_params = {
 };
 
 static const char app_usage[] =
-	"Usage: %s [-f CONFIG_FILE] [-s SCRIPT_FILE] -p PORT_MASK "
+	"Usage: %s [-f CONFIG_FILE] [-s SCRIPT_FILE] [-p PORT_MASK] "
 	"[-l LOG_LEVEL] [--preproc PREPROCESSOR] [--preproc-args ARGS]\n"
 	"\n"
 	"Arguments:\n"
 	"\t-f CONFIG_FILE: Default config file is %s\n"
-	"\t-p PORT_MASK: Mask of NIC port IDs in hexadecimal format\n"
+	"\t-p PORT_MASK: Mask of NIC port IDs in hex format (generated from "
+		"config file when not provided)\n"
 	"\t-s SCRIPT_FILE: No CLI script file is run when not specified\n"
 	"\t-l LOG_LEVEL: 0 = NONE, 1 = HIGH PRIO (default), 2 = LOW PRIO\n"
 	"\t--preproc PREPROCESSOR: Configuration file pre-processor\n"
@@ -283,6 +286,11 @@ parser_read_arg_bool(const char *p)
 #define PARSE_ERROR(exp, section, entry)				\
 APP_CHECK(exp, "Parse error in section \"%s\": entry \"%s\"\n", section, entry)
 
+#define PARSE_ERROR_MESSAGE(exp, section, entry, message)		\
+APP_CHECK(exp, "Parse error in section \"%s\", entry \"%s\": %s\n",	\
+	section, entry, message)
+
+
 #define PARSE_ERROR_MALLOC(exp)						\
 APP_CHECK(exp, "Parse error: no free memory\n")
 
@@ -657,29 +665,62 @@ parse_eal(struct app_params *app,
 		/* pci_blacklist */
 		if ((strcmp(entry->name, "pci_blacklist") == 0) ||
 			(strcmp(entry->name, "b") == 0)) {
-			PARSE_ERROR_DUPLICATE((p->pci_blacklist == NULL),
-				section_name,
-				entry->name);
-			p->pci_blacklist = strdup(entry->value);
+			uint32_t i;
+
+			for (i = 0; i < APP_MAX_LINKS; i++) {
+				if (p->pci_blacklist[i])
+					continue;
+
+				p->pci_blacklist[i] =
+					strdup(entry->value);
+				PARSE_ERROR_MALLOC(p->pci_blacklist[i]);
+			}
+
+			PARSE_ERROR_MESSAGE((i < APP_MAX_LINKS),
+				section_name, entry->name,
+				"too many elements");
 			continue;
 		}
 
 		/* pci_whitelist */
 		if ((strcmp(entry->name, "pci_whitelist") == 0) ||
 			(strcmp(entry->name, "w") == 0)) {
-			PARSE_ERROR_DUPLICATE((p->pci_whitelist == NULL),
-				section_name,
-				entry->name);
-			p->pci_whitelist = strdup(entry->value);
+			uint32_t i;
+
+			PARSE_ERROR_MESSAGE((app->port_mask != 0),
+				section_name, entry->name, "entry to be "
+				"generated by the application (port_mask "
+				"not provided)");
+
+			for (i = 0; i < APP_MAX_LINKS; i++) {
+				if (p->pci_whitelist[i])
+					continue;
+
+				p->pci_whitelist[i] = strdup(entry->value);
+				PARSE_ERROR_MALLOC(p->pci_whitelist[i]);
+			}
+
+			PARSE_ERROR_MESSAGE((i < APP_MAX_LINKS),
+				section_name, entry->name,
+				"too many elements");
 			continue;
 		}
 
 		/* vdev */
 		if (strcmp(entry->name, "vdev") == 0) {
-			PARSE_ERROR_DUPLICATE((p->vdev == NULL),
-				section_name,
-				entry->name);
-			p->vdev = strdup(entry->value);
+			uint32_t i;
+
+			for (i = 0; i < APP_MAX_LINKS; i++) {
+				if (p->vdev[i])
+					continue;
+
+				p->vdev[i] = strdup(entry->value);
+				PARSE_ERROR_MALLOC(p->vdev[i]);
+			}
+
+			PARSE_ERROR_MESSAGE((i < APP_MAX_LINKS),
+				section_name, entry->name,
+				"too many elements");
 			continue;
 		}
 
@@ -1395,6 +1436,7 @@ parse_link(struct app_params *app,
 	struct app_link_params *param;
 	struct rte_cfgfile_entry *entries;
 	int n_entries, i;
+	int pci_bdf_present = 0;
 	ssize_t param_idx;
 
 	n_entries = rte_cfgfile_section_num_entries(cfg, section_name);
@@ -1433,7 +1475,7 @@ parse_link(struct app_params *app,
 
 		if (strcmp(ent->name, "tcp_syn_q") == 0) {
 			int status = parser_read_uint32(
-				&param->tcp_syn_local_q, ent->value);
+				&param->tcp_syn_q, ent->value);
 
 			PARSE_ERROR((status == 0), section_name, ent->name);
 			continue;
@@ -1476,10 +1518,31 @@ parse_link(struct app_params *app,
 			continue;
 		}
 
+		if (strcmp(ent->name, "pci_bdf") == 0) {
+			PARSE_ERROR_DUPLICATE((pci_bdf_present == 0),
+				section_name, ent->name);
+
+			snprintf(param->pci_bdf, APP_LINK_PCI_BDF_SIZE,
+				"%s", ent->value);
+			pci_bdf_present = 1;
+			continue;
+		}
+
 		/* unrecognized */
 		PARSE_ERROR_INVALID(0, section_name, ent->name);
 	}
 
+	/* Check for mandatory fields */
+	if (app->port_mask)
+		PARSE_ERROR_MESSAGE((pci_bdf_present == 0),
+			section_name, "pci_bdf",
+			"entry not allowed (port_mask is provided)");
+	else
+		PARSE_ERROR_MESSAGE((pci_bdf_present),
+			section_name, "pci_bdf",
+			"this entry is mandatory (port_mask is not "
+			"provided)");
+
 	param->parsed = 1;
 
 	free(entries);
@@ -2121,7 +2184,8 @@ create_implicit_mempools(struct app_params *app)
 }
 
 static void
-parse_port_mask(struct app_params *app, uint64_t port_mask)
+create_implicit_links_from_port_mask(struct app_params *app,
+	uint64_t port_mask)
 {
 	uint32_t pmd_id, link_id;
 
@@ -2142,6 +2206,18 @@ parse_port_mask(struct app_params *app, uint64_t port_mask)
 	}
 }
 
+static void
+assign_link_pmd_id_from_pci_bdf(struct app_params *app)
+{
+	uint32_t i;
+
+	for (i = 0; i < app->n_links; i++) {
+		struct app_link_params *link = &app->link_params[i];
+
+		link->pmd_id = i;
+	}
+}
+
 int
 app_config_parse(struct app_params *app, const char *file_name)
 {
@@ -2153,7 +2229,8 @@ app_config_parse(struct app_params *app, const char *file_name)
 	create_implicit_mempools(app);
 
 	/* Port mask */
-	parse_port_mask(app, app->port_mask);
+	if (app->port_mask)
+		create_implicit_links_from_port_mask(app, app->port_mask);
 
 	/* Load application configuration file */
 	cfg = rte_cfgfile_load(file_name, 0);
@@ -2232,6 +2309,9 @@ app_config_parse(struct app_params *app, const char *file_name)
 	APP_PARAM_COUNT(app->msgq_params, app->n_msgq);
 	APP_PARAM_COUNT(app->pipeline_params, app->n_pipelines);
 
+	if (app->port_mask == 0)
+		assign_link_pmd_id_from_pci_bdf(app);
+
 	/* Save configuration to output file */
 	app_config_save(app, app->output_file);
 
@@ -2245,6 +2325,7 @@ static void
 save_eal_params(struct app_params *app, FILE *f)
 {
 	struct app_eal_params *p = &app->eal_params;
+	uint32_t i;
 
 	fprintf(f, "[EAL]\n");
 
@@ -2263,14 +2344,29 @@ save_eal_params(struct app_params *app, FILE *f)
 	if (p->ranks_present)
 		fprintf(f, "%s = %" PRIu32 "\n", "r", p->ranks);
 
-	if (p->pci_blacklist)
-		fprintf(f, "%s = %s\n", "pci_blacklist", p->pci_blacklist);
+	for (i = 0; i < APP_MAX_LINKS; i++) {
+		if (p->pci_blacklist[i] == NULL)
+			break;
+
+		fprintf(f, "%s = %s\n", "pci_blacklist",
+			p->pci_blacklist[i]);
+	}
 
-	if (p->pci_whitelist)
-		fprintf(f, "%s = %s\n", "pci_whitelist", p->pci_whitelist);
+	for (i = 0; i < APP_MAX_LINKS; i++) {
+		if (p->pci_whitelist[i] == NULL)
+			break;
 
-	if (p->vdev)
-		fprintf(f, "%s = %s\n", "vdev", p->vdev);
+		fprintf(f, "%s = %s\n", "pci_whitelist",
+			p->pci_whitelist[i]);
+	}
+
+	for (i = 0; i < APP_MAX_LINKS; i++) {
+		if (p->vdev[i] == NULL)
+			break;
+
+		fprintf(f, "%s = %s\n", "vdev",
+			p->vdev[i]);
+	}
 
 	if (p->vmware_tsc_map_present)
 		fprintf(f, "%s = %s\n", "vmware_tsc_map",
@@ -2371,14 +2467,17 @@ save_links_params(struct app_params *app, FILE *f)
 		fprintf(f, "; %s = %" PRIu32 "\n", "pmd_id", p->pmd_id);
 		fprintf(f, "%s = %s\n", "promisc", p->promisc ? "yes" : "no");
 		fprintf(f, "%s = %" PRIu32 "\n", "arp_q", p->arp_q);
-		fprintf(f, "%s = %" PRIu32 "\n", "tcp_syn_local_q",
-			p->tcp_syn_local_q);
+		fprintf(f, "%s = %" PRIu32 "\n", "tcp_syn_q",
+			p->tcp_syn_q);
 		fprintf(f, "%s = %" PRIu32 "\n", "ip_local_q", p->ip_local_q);
 		fprintf(f, "%s = %" PRIu32 "\n", "tcp_local_q", p->tcp_local_q);
 		fprintf(f, "%s = %" PRIu32 "\n", "udp_local_q", p->udp_local_q);
 		fprintf(f, "%s = %" PRIu32 "\n", "sctp_local_q",
 			p->sctp_local_q);
 
+		if (strlen(p->pci_bdf))
+			fprintf(f, "%s = %s\n", "pci_bdf", p->pci_bdf);
+
 		fputc('\n', f);
 	}
 }
@@ -2891,10 +2990,6 @@ app_config_args(struct app_params *app, int argc, char **argv)
 
 	optind = 0; /* reset getopt lib */
 
-	/* Check that mandatory args have been provided */
-	if (!p_present)
-		rte_panic("Error: PORT_MASK is not provided\n");
-
 	/* Check dependencies between args */
 	if (preproc_params_present && (preproc_present == 0))
 		rte_panic("Error: Preprocessor args specified while "
diff --git a/examples/ip_pipeline/init.c b/examples/ip_pipeline/init.c
index 186ca03..0bbef05 100644
--- a/examples/ip_pipeline/init.c
+++ b/examples/ip_pipeline/init.c
@@ -96,9 +96,10 @@ app_init_core_mask(struct app_params *app)
 static void
 app_init_eal(struct app_params *app)
 {
-	char buffer[32];
+	char buffer[256];
 	struct app_eal_params *p = &app->eal_params;
 	uint32_t n_args = 0;
+	uint32_t i;
 	int status;
 
 	app->eal_argv[n_args++] = strdup(app->app_name);
@@ -132,24 +133,47 @@ app_init_eal(struct app_params *app)
 		app->eal_argv[n_args++] = strdup(buffer);
 	}
 
-	if (p->pci_blacklist) {
+	for (i = 0; i < APP_MAX_LINKS; i++) {
+		if (p->pci_blacklist[i] == NULL)
+			break;
+
 		snprintf(buffer,
 			sizeof(buffer),
 			"--pci-blacklist=%s",
-			p->pci_blacklist);
+			p->pci_blacklist[i]);
 		app->eal_argv[n_args++] = strdup(buffer);
 	}
 
-	if (p->pci_whitelist) {
+	if (app->port_mask != 0)
+		for (i = 0; i < APP_MAX_LINKS; i++) {
+			if (p->pci_whitelist[i] == NULL)
+				break;
+
+			snprintf(buffer,
+				sizeof(buffer),
+				"--pci-whitelist=%s",
+				p->pci_whitelist[i]);
+			app->eal_argv[n_args++] = strdup(buffer);
+		}
+	else
+		for (i = 0; i < app->n_links; i++) {
+			char *pci_bdf = app->link_params[i].pci_bdf;
+
+			snprintf(buffer,
+				sizeof(buffer),
+				"--pci-whitelist=%s",
+				pci_bdf);
+			app->eal_argv[n_args++] = strdup(buffer);
+		}
+
+	for (i = 0; i < APP_MAX_LINKS; i++) {
+		if (p->vdev[i] == NULL)
+			break;
+
 		snprintf(buffer,
 			sizeof(buffer),
-			"--pci-whitelist=%s",
-			p->pci_whitelist);
-		app->eal_argv[n_args++] = strdup(buffer);
-	}
-
-	if (p->vdev) {
-		snprintf(buffer, sizeof(buffer), "--vdev=%s", p->vdev);
+			"--vdev=%s",
+			p->vdev[i]);
 		app->eal_argv[n_args++] = strdup(buffer);
 	}
 
@@ -267,6 +291,15 @@ app_init_eal(struct app_params *app)
 	app->eal_argc = n_args;
 
 	APP_LOG(app, HIGH, "Initializing EAL ...");
+	if (app->log_level >= APP_LOG_LEVEL_LOW) {
+		int i;
+
+		fprintf(stdout, "[APP] EAL arguments: \"");
+		for (i = 1; i < app->eal_argc; i++)
+			fprintf(stdout, "%s ", app->eal_argv[i]);
+		fprintf(stdout, "\"\n");
+	}
+
 	status = rte_eal_init(app->eal_argc, app->eal_argv);
 	if (status < 0)
 		rte_panic("EAL init error\n");
@@ -317,7 +350,7 @@ app_link_filter_tcp_syn_add(struct app_link_params *link)
 {
 	struct rte_eth_syn_filter filter = {
 		.hig_pri = 1,
-		.queue = link->tcp_syn_local_q,
+		.queue = link->tcp_syn_q,
 	};
 
 	return rte_eth_dev_filter_ctrl(link->pmd_id,
@@ -555,28 +588,45 @@ app_link_set_arp_filter(struct app_params *app, struct app_link_params *cp)
 static void
 app_link_set_tcp_syn_filter(struct app_params *app, struct app_link_params *cp)
 {
-	if (cp->tcp_syn_local_q != 0) {
+	if (cp->tcp_syn_q != 0) {
 		int status = app_link_filter_tcp_syn_add(cp);
 
 		APP_LOG(app, LOW, "%s (%" PRIu32 "): "
 			"Adding TCP SYN filter (queue = %" PRIu32 ")",
-			cp->name, cp->pmd_id, cp->tcp_syn_local_q);
+			cp->name, cp->pmd_id, cp->tcp_syn_q);
 
 		if (status)
 			rte_panic("%s (%" PRIu32 "): "
 				"Error adding TCP SYN filter "
 				"(queue = %" PRIu32 ") (%" PRId32 ")\n",
-				cp->name, cp->pmd_id, cp->tcp_syn_local_q,
+				cp->name, cp->pmd_id, cp->tcp_syn_q,
 				status);
 	}
 }
 
+static int
+app_link_is_virtual(struct app_link_params *p)
+{
+	uint32_t pmd_id = p->pmd_id;
+	struct rte_eth_dev *dev = &rte_eth_devices[pmd_id];
+
+	if (dev->dev_type == RTE_ETH_DEV_VIRTUAL)
+		return 1;
+
+	return 0;
+}
+
 void
 app_link_up_internal(struct app_params *app, struct app_link_params *cp)
 {
 	uint32_t i;
 	int status;
 
+	if (app_link_is_virtual(cp)) {
+		cp->state = 1;
+		return;
+	}
+
 	/* For each link, add filters for IP of current link */
 	if (cp->ip != 0) {
 		for (i = 0; i < app->n_links; i++) {
@@ -671,8 +721,8 @@ app_link_up_internal(struct app_params *app, struct app_link_params *cp)
 	/* PMD link up */
 	status = rte_eth_dev_set_link_up(cp->pmd_id);
 	if (status < 0)
-		rte_panic("%s (%" PRIu32 "): PMD set up error %" PRId32 "\n",
-			cp->name, cp->pmd_id, status);
+		rte_panic("%s (%" PRIu32 "): PMD set link up error %"
+			PRId32 "\n", cp->name, cp->pmd_id, status);
 
 	/* Mark link as UP */
 	cp->state = 1;
@@ -682,9 +732,18 @@ void
 app_link_down_internal(struct app_params *app, struct app_link_params *cp)
 {
 	uint32_t i;
+	int status;
+
+	if (app_link_is_virtual(cp)) {
+		cp->state = 0;
+		return;
+	}
 
 	/* PMD link down */
-	rte_eth_dev_set_link_down(cp->pmd_id);
+	status = rte_eth_dev_set_link_down(cp->pmd_id);
+	if (status < 0)
+		rte_panic("%s (%" PRIu32 "): PMD set link down error %"
+			PRId32 "\n", cp->name, cp->pmd_id, status);
 
 	/* Mark link as DOWN */
 	cp->state = 0;
diff --git a/examples/ip_pipeline/pipeline/pipeline_common_fe.c b/examples/ip_pipeline/pipeline/pipeline_common_fe.c
index 1e16ad6..bffc9a4 100644
--- a/examples/ip_pipeline/pipeline/pipeline_common_fe.c
+++ b/examples/ip_pipeline/pipeline/pipeline_common_fe.c
@@ -898,9 +898,15 @@ print_link_info(struct app_link_params *p)
 
 	mac_addr = (struct ether_addr *) &p->mac_addr;
 
-	printf("%s: flags=<%s>\n",
-		p->name,
-		(p->state) ? "UP" : "DOWN");
+	if (strlen(p->pci_bdf))
+		printf("%s(%s): flags=<%s>\n",
+			p->name,
+			p->pci_bdf,
+			(p->state) ? "UP" : "DOWN");
+	else
+		printf("%s: flags=<%s>\n",
+			p->name,
+			(p->state) ? "UP" : "DOWN");
 
 	if (p->ip)
 		printf("\tinet %" PRIu32 ".%" PRIu32
