From: Martin Belanger <martin.belanger@dell.com>
Date: Thu, 20 May 2021 13:49:44 -0400
Subject: Add --host-iface option
Git-commit: 0120b08f7e12f3bccb758cc4f5f2b3a5a1fdbfff
References: bsc#1199670

---
 Documentation/nvme-connect-all.txt |   10 +++++++++-
 Documentation/nvme-connect.txt     |   10 +++++++++-
 Documentation/nvme-discover.txt    |   20 ++++++++++++++------
 etc/discovery.conf.in              |    2 +-
 fabrics.c                          |   30 ++++++++++++++++++++++++++----
 nvme.h                             |    1 +
 6 files changed, 60 insertions(+), 13 deletions(-)

--- a/Documentation/nvme-connect-all.txt
+++ b/Documentation/nvme-connect-all.txt
@@ -13,6 +13,7 @@ SYNOPSIS
 		[--traddr=<traddr>        | -a <traddr>]
 		[--trsvcid=<trsvcid>      | -s <trsvcid>]
 		[--host-traddr=<traddr>   | -w <traddr>]
+		[--host-iface=<iface>     | -f <iface>]
 		[--hostnqn=<hostnqn>      | -q <hostnqn>]
 		[--hostid=<hostid>        | -I <hostid>]
 		[--raw=<filename>         | -r <filename>]
@@ -77,7 +78,14 @@ OPTIONS
 -w <traddr>::
 --host-traddr=<traddr>::
 	This field specifies the network address used on the host to connect
-	to the Discovery Controller.
+	to the Controller. For TCP, this sets the source address on the socket.
+
+-f <iface>::
+--host-iface=<iface>::
+	This field specifies the network interface used on the host to connect
+	to the Controller (e.g. IP eth1, enp2s0, enx78e7d1ea46da). This forces
+	the connection to be made on a specific interface instead of letting
+	the system decide.
 
 -q <hostnqn>::
 --hostnqn=<hostnqn>::
--- a/Documentation/nvme-connect.txt
+++ b/Documentation/nvme-connect.txt
@@ -14,6 +14,7 @@ SYNOPSIS
 		[--traddr=<traddr>        | -a <traddr>]
 		[--trsvcid=<trsvcid>      | -s <trsvcid>]
 		[--host-traddr=<traddr>   | -w <traddr>]
+		[--host-iface=<iface>     | -f <iface>]
 		[--hostnqn=<hostnqn>      | -q <hostnqn>]
 		[--hostid=<hostid>        | -I <hostid>]
 		[--nr-io-queues=<#>       | -i <#>]
@@ -69,7 +70,14 @@ OPTIONS
 -w <traddr>::
 --host-traddr=<traddr>::
 	This field specifies the network address used on the host to connect
-	to the Controller.
+	to the Controller. For TCP, this sets the source address on the socket.
+
+-f <iface>::
+--host-iface=<iface>::
+	This field specifies the network interface used on the host to connect
+	to the Controller (e.g. IP eth1, enp2s0, enx78e7d1ea46da). This forces
+	the connection to be made on a specific interface instead of letting
+	the system decide.
 
 -q <hostnqn>::
 --hostnqn=<hostnqn>::
--- a/Documentation/nvme-discover.txt
+++ b/Documentation/nvme-discover.txt
@@ -13,6 +13,7 @@ SYNOPSIS
 		[--traddr=<traddr>        | -a <traddr>]
 		[--trsvcid=<trsvcid>      | -s <trsvcid>]
 		[--host-traddr=<traddr>   | -w <traddr>]
+		[--host-iface=<iface>     | -f <iface>]
 		[--hostnqn=<hostnqn>      | -q <hostnqn>]
 		[--hostid=<hostid>        | -I <hostid>]
 		[--raw=<filename>	  | -r <filename>]
@@ -33,7 +34,7 @@ DESCRIPTION
 Send one or more Get Log Page requests to a NVMe-over-Fabrics Discovery
 Controller.
 
-If no parameters are given, then 'nvme discover' will attempt to 
+If no parameters are given, then 'nvme discover' will attempt to
 find a /etc/nvme/discovery.conf file to use to supply a list of
 Discovery commands to run.  If no /etc/nvme/discovery.conf file
 exists, the command will quit with an error.
@@ -44,7 +45,7 @@ request will then be sent to the specifi
 
 BACKGROUND
 ----------
-The NVMe-over-Fabrics specification defines the concept of a 
+The NVMe-over-Fabrics specification defines the concept of a
 Discovery Controller that an NVMe Host can query on a fabric
 network to discover NVMe subsystems contained in NVMe Targets
 which it can connect to on the network.  The Discovery Controller
@@ -64,7 +65,7 @@ resources are allocated to the NVMe Host
 A Discovery Controller has it's own NQN defined in the NVMe-over-Fabrics
 specification, *nqn.2014-08.org.nvmexpress.discovery*.  All Discovery
 Controllers must use this NQN name. This NQN is used by default by
-nvme-cli for the 'discover' command. 
+nvme-cli for the 'discover' command.
 
 OPTIONS
 -------
@@ -97,11 +98,18 @@ OPTIONS
 -w <traddr>::
 --host-traddr=<traddr>::
 	This field specifies the network address used on the host to connect
-	to the Discovery Controller.
- 
+	to the Controller. For TCP, this sets the source address on the socket.
+
+-f <iface>::
+--host-iface=<iface>::
+	This field specifies the network interface used on the host to connect
+	to the Controller (e.g. IP eth1, enp2s0, enx78e7d1ea46da). This forces
+	the connection to be made on a specific interface instead of letting
+	the system decide.
+
 -q <hostnqn>::
 --hostnqn=<hostnqn>::
-	Overrides the default host NQN that identifies the NVMe Host.  
+	Overrides the default host NQN that identifies the NVMe Host.
 	If this option is not specified, the default is read from
 	/etc/nvme/hostnqn first. If that does not exist, the autogenerated
 	NQN value from the NVMe Host kernel module is used next.
--- a/etc/discovery.conf.in
+++ b/etc/discovery.conf.in
@@ -1,4 +1,4 @@
 # Used for extracting default parameters for discovery
 #
 # Example:
-# --transport=<trtype> --traddr=<traddr> --trsvcid=<trsvcid> --host-traddr=<host-traddr>
+# --transport=<trtype> --traddr=<traddr> --trsvcid=<trsvcid> --host-traddr=<host-traddr> --host-iface=<host-iface>
--- a/fabrics.c
+++ b/fabrics.c
@@ -62,6 +62,7 @@ const char *conarg_transport = "transpor
 const char *conarg_traddr = "traddr";
 const char *conarg_trsvcid = "trsvcid";
 const char *conarg_host_traddr = "host_traddr";
+const char *conarg_host_iface = "host_iface";
 
 static struct config {
 	char *nqn;
@@ -69,6 +70,7 @@ static struct config {
 	char *traddr;
 	char *trsvcid;
 	char *host_traddr;
+	char *host_iface;
 	char *hostnqn;
 	char *hostid;
 	int  nr_io_queues;
@@ -96,6 +98,7 @@ struct connect_args {
 	char *traddr;
 	char *trsvcid;
 	char *host_traddr;
+	char *host_iface;
 	struct connect_args *next;
 	struct connect_args *tail;
 };
@@ -317,6 +320,7 @@ static bool ctrl_matches_connectargs(cha
 	cargs.traddr = parse_conn_arg(addr, ' ', conarg_traddr);
 	cargs.trsvcid = parse_conn_arg(addr, ' ', conarg_trsvcid);
 	cargs.host_traddr = parse_conn_arg(addr, ' ', conarg_host_traddr);
+	cargs.host_iface = parse_conn_arg(addr, ' ', conarg_host_iface);
 
 	if (!strcmp(cargs.subsysnqn, NVME_DISC_SUBSYS_NAME)) {
 		char *kato_str = nvme_get_ctrl_attr(path, "kato"), *p;
@@ -348,12 +352,15 @@ static bool ctrl_matches_connectargs(cha
 	    (!strcmp(cargs.trsvcid, args->trsvcid) ||
 	     !strcmp(args->trsvcid, "none")) &&
 	    (!strcmp(cargs.host_traddr, args->host_traddr) ||
-	     !strcmp(args->host_traddr, "none")))
+	     !strcmp(args->host_traddr, "none")) &&
+	    (!strcmp(cargs.host_iface, args->host_iface) ||
+	     !strcmp(args->host_iface, "none")))
 		found = true;
 
 	free(cargs.traddr);
 	free(cargs.trsvcid);
 	free(cargs.host_traddr);
+	free(cargs.host_iface);
 out:
 	free(cargs.subsysnqn);
 	free(cargs.transport);
@@ -410,6 +417,7 @@ static struct connect_args *extract_conn
 	cargs->traddr = parse_conn_arg(argstr, ',', conarg_traddr);
 	cargs->trsvcid = parse_conn_arg(argstr, ',', conarg_trsvcid);
 	cargs->host_traddr = parse_conn_arg(argstr, ',', conarg_host_traddr);
+	cargs->host_iface = parse_conn_arg(argstr, ',', conarg_host_iface);
 	return cargs;
 }
 
@@ -420,6 +428,7 @@ static void free_connect_args(struct con
 	free(cargs->traddr);
 	free(cargs->trsvcid);
 	free(cargs->host_traddr);
+	free(cargs->host_iface);
 	free(cargs);
 }
 
@@ -907,6 +916,7 @@ static int build_options(char *argstr, i
 	if (add_argument(&argstr, &max_len, "transport", cfg.transport) ||
 	    add_argument(&argstr, &max_len, "traddr", cfg.traddr) ||
 	    add_argument(&argstr, &max_len, "host_traddr", cfg.host_traddr) ||
+	    add_argument(&argstr, &max_len, "host_iface", cfg.host_iface) ||
 	    add_argument(&argstr, &max_len, "trsvcid", cfg.trsvcid) ||
 	    ((cfg.hostnqn || nvmf_hostnqn_file()) &&
 		    add_argument(&argstr, &max_len, "hostnqn", cfg.hostnqn)) ||
@@ -1103,6 +1113,13 @@ static int connect_ctrl(struct nvmf_disc
 		p+= len;
 	}
 
+	if (cfg.host_iface && strcmp(cfg.host_iface, "none")) {
+		len = sprintf(p, ",host_iface=%s", cfg.host_iface);
+		if (len < 0)
+			return -EINVAL;
+		p+= len;
+	}
+
 	if (cfg.reconnect_delay) {
 		len = sprintf(p, ",reconnect_delay=%d", cfg.reconnect_delay);
 		if (len < 0)
@@ -1229,6 +1246,7 @@ static bool cargs_match_found(struct nvm
 	cargs.subsysnqn = strdup(entry->subnqn);
 	cargs.trsvcid = strdup(entry->trsvcid);
 	cargs.host_traddr = strdup(cfg.host_traddr ?: "\0");
+	cargs.host_iface = strdup(cfg.host_iface ?: "\0");
 
 	/* check if we have a match in the discovery recursion */
 	while (c) {
@@ -1236,7 +1254,8 @@ static bool cargs_match_found(struct nvm
 		    !strcmp(cargs.transport, c->transport) &&
 		    !strcmp(cargs.traddr, c->traddr) &&
 		    !strcmp(cargs.trsvcid, c->trsvcid) &&
-		    !strcmp(cargs.host_traddr, c->host_traddr))
+		    !strcmp(cargs.host_traddr, c->host_traddr) &&
+		    !strcmp(cargs.host_iface, c->host_iface))
 			return true;
 		c = c->next;
 	}
@@ -1471,7 +1490,9 @@ int fabrics_discover(const char *desc, i
 		OPT_LIST("transport",      't', &cfg.transport,       "transport type"),
 		OPT_LIST("traddr",         'a', &cfg.traddr,          "transport address"),
 		OPT_LIST("trsvcid",        's', &cfg.trsvcid,         "transport service id (e.g. IP port)"),
-		OPT_LIST("host-traddr",    'w', &cfg.host_traddr,     "host traddr (e.g. FC WWN's)"),
+		OPT_LIST("host-traddr",    'w', &cfg.host_traddr,     "host traddr (e.g. FC WWN's or IP source address)"),
+
+		OPT_LIST("host-iface",     'f', &cfg.host_iface,      "host transport interface (e.g. IP eth1, enp2s0)"),
 		OPT_LIST("hostnqn",        'q', &cfg.hostnqn,         "user-defined hostnqn (if default not used)"),
 		OPT_LIST("hostid",         'I', &cfg.hostid,          "user-defined hostid (if default not used)"),
 		OPT_LIST("raw",            'r', &cfg.raw,             "raw output file"),
@@ -1537,7 +1558,8 @@ int fabrics_connect(const char *desc, in
 		OPT_LIST("nqn",               'n', &cfg.nqn,               "nqn name"),
 		OPT_LIST("traddr",            'a', &cfg.traddr,            "transport address"),
 		OPT_LIST("trsvcid",           's', &cfg.trsvcid,           "transport service id (e.g. IP port)"),
-		OPT_LIST("host-traddr",       'w', &cfg.host_traddr,       "host traddr (e.g. FC WWN's)"),
+		OPT_LIST("host-traddr",       'w', &cfg.host_traddr,       "host traddr (e.g. FC WWN's or IP source adresso)"),
+		OPT_LIST("host-iface",        'f', &cfg.host_iface,        "host transport interface (e.g. IP eth1, enp2s0)"),
 		OPT_LIST("hostnqn",           'q', &cfg.hostnqn,           "user-defined hostnqn"),
 		OPT_LIST("hostid",            'I', &cfg.hostid,            "user-defined hostid (if default not used)"),
 		OPT_INT("nr-io-queues",       'i', &cfg.nr_io_queues,      "number of io queues to use (default is core count)"),
--- a/nvme.h
+++ b/nvme.h
@@ -56,6 +56,7 @@ struct nvme_ctrl {
 	char *traddr;
 	char *trsvcid;
 	char *host_traddr;
+	char *host_iface;
 	char *hostnqn;
 	char *hostid;
 
