The iSCSI protocol, originally described in RFC 3720, which is now obsoleted by RFC 7143. This no big deal, because the newer RFC is a consolidation of the various RFCs about iSCSI. There is a standard, and both OpenBSD and Linux claim to base their implementations on RFC 3720. What could go wrong?

My first try to attach an iSCSI LUN to my OpenBSD server showed that a lot could go wrong. The initiator iscsid(8) cannot mount the LUN from Synology. Instead, it keeps looping in the initial connection phase. The target system shows complains in the logs about missing parameters in the operational parameter negotiation phase.

What should I do next? Either I come back to my decision for option 3 or I try to debug this incompatibility. I choose the hard way and try to find out how to fix this incompatibility. Hours of packet capturing, reading RFC 7143 and the source code of iscsid(8) I found a solution for my problem. I wrote the following patch for iscsid(8) which makes it compatible with the iSCSI target on my Synology Disk Station:

iscsid.diff

Index: usr.sbin/iscsid/connection.c
===================================================================
RCS file: /cvs/src/usr.sbin/iscsid/connection.c,v
retrieving revision 1.21
diff -u -p -r1.21 connection.c
--- usr.sbin/iscsid/connection.c	5 Dec 2015 06:38:18 -0000	1.21
+++ usr.sbin/iscsid/connection.c	1 Nov 2017 05:50:14 -0000
@@ -316,44 +316,6 @@ log_debug("conn_parse_kvp: %s = %s", k->
 #undef SET_NUM
 #undef SET_BOOL
 
-int
-conn_gen_kvp(struct connection *c, struct kvp *kvp, size_t *nkvp)
-{
-	struct session *s = c->session;
-	size_t i = 0;
-
-	if (s->mine.MaxConnections != iscsi_sess_defaults.MaxConnections) {
-		if (kvp && i < *nkvp) { - kvp[i].key = strdup("MaxConnections"); - if (kvp[i].key == NULL) - return -1; - if (asprintf(&kvp[i].value, "%hu", - s->mine.MaxConnections) == -1) {
-				kvp[i].value = NULL;
-				return -1;
-			}
-		}
-		i++;
-	}
-	if (c->mine.MaxRecvDataSegmentLength !=
-	    iscsi_conn_defaults.MaxRecvDataSegmentLength) {
-		if (kvp && i < *nkvp) { - kvp[i].key = strdup("MaxRecvDataSegmentLength"); - if (kvp[i].key == NULL) - return -1; - if (asprintf(&kvp[i].value, "%u", - c->mine.MaxRecvDataSegmentLength) == -1) {
-				kvp[i].value = NULL;
-				return -1;
-			}
-		}
-		i++;
-	}
-
-	*nkvp = i;
-	return 0;
-}
-
 void
 conn_pdu_write(struct connection *c, struct pdu *p)
 {
Index: usr.sbin/iscsid/initiator.c
===================================================================
RCS file: /cvs/src/usr.sbin/iscsid/initiator.c,v
retrieving revision 1.15
diff -u -p -r1.15 initiator.c
--- usr.sbin/iscsid/initiator.c	16 Jan 2015 15:57:06 -0000	1.15
+++ usr.sbin/iscsid/initiator.c	1 Nov 2017 05:50:14 -0000
@@ -250,39 +250,113 @@ initiator_nop_in_imm(struct connection *
 	conn_task_issue(c, t);
 }
 
+#define WRITE_BOOL(k, v)	\
+do {				\
+	if (v)			\
+		k = "Yes";	\
+	else			\
+		k = "No";	\
+} while (0)
+
+#define WRITE_NUM(k, v)				\
+do {						\
+	if (asprintf(&k, "%hu", v) == -1)	\
+		errors++;			\
+} while (0)
+
+#define WRITE_INT(k, v)				\
+do {						\
+	if (asprintf(&k, "%u", v) == -1)	\
+		errors++;			\
+} while (0)
+
+#define WRITE_DIGEST(k, v)	\
+do {				\
+	if (v)			\
+		k = "CRC32";	\
+	else			\
+		k = "None";	\
+} while (0)			\
+
 struct kvp *
 initiator_login_kvp(struct connection *c, u_int8_t stage)
 {
+	int errors = 0;
 	struct kvp *kvp;
-	size_t nkvp;
+	struct session *s;
 
 	switch (stage) {
 	case ISCSI_LOGIN_STG_SECNEG:
-		if (!(kvp = calloc(4, sizeof(*kvp))))
+		if (!(kvp = calloc(5, sizeof(*kvp))))
 			return NULL;
 		kvp[0].key = "AuthMethod";
 		kvp[0].value = "None";
 		kvp[1].key = "InitiatorName";
 		kvp[1].value = c->session->config.InitiatorName;
+		kvp[2].key = "SessionType";
 
 		if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) {
-			kvp[2].key = "SessionType";
 			kvp[2].value = "Discovery";
 		} else {
-			kvp[2].key = "TargetName";
-			kvp[2].value = c->session->config.TargetName;
+			kvp[2].value = "Normal";
+			kvp[3].key = "TargetName";
+			kvp[3].value = c->session->config.TargetName;
 		}
 		break;
 	case ISCSI_LOGIN_STG_OPNEG:
-		if (conn_gen_kvp(c, NULL, &nkvp) == -1)
-			return NULL;
-		nkvp += 1; /* add slot for terminator */
-		if (!(kvp = calloc(nkvp, sizeof(*kvp))))
+		if (!(kvp = calloc(15, sizeof(*kvp))))
 			return NULL;
-		if (conn_gen_kvp(c, kvp, &nkvp) == -1) {
+
+		s = c->session;
+
+		kvp[0].key = "MaxConnections";
+		WRITE_NUM(kvp[0].value, s->mine.MaxConnections);
+
+		kvp[1].key = "InitialR2T";
+		WRITE_BOOL(kvp[1].value, s->mine.InitialR2T);
+
+		kvp[2].key = "ImmediateData";
+		WRITE_BOOL(kvp[2].value, s->mine.ImmediateData);
+
+		kvp[3].key = "MaxRecvDataSegmentLength";
+		WRITE_INT(kvp[3].value, c->mine.MaxRecvDataSegmentLength);
+
+		kvp[4].key = "MaxBurstLength";
+		WRITE_INT(kvp[4].value, s->mine.MaxBurstLength);
+
+		kvp[5].key = "FirstBurstLength";
+		WRITE_INT(kvp[5].value, s->mine.FirstBurstLength);
+
+		kvp[6].key = "HeaderDigest";
+		WRITE_DIGEST(kvp[6].value, s->config.HeaderDigest);
+
+		kvp[7].key = "DataDigest";
+		WRITE_DIGEST(kvp[7].value, s->config.DataDigest);
+
+		kvp[8].key = "MaxOutstandingR2T";
+		WRITE_NUM(kvp[8].value, s->mine.MaxOutstandingR2T);
+
+		kvp[9].key = "DataPDUInOrder";
+		WRITE_BOOL(kvp[9].value, s->mine.DataPDUInOrder);
+
+		kvp[10].key = "DataSequenceInOrder";
+		WRITE_BOOL(kvp[10].value, s->mine.DataSequenceInOrder);
+
+		kvp[11].key = "ErrorRecoveryLevel";
+		WRITE_NUM(kvp[11].value, s->mine.ErrorRecoveryLevel);
+
+		kvp[12].key = "DefaultTime2Wait";
+		WRITE_NUM(kvp[12].value, s->mine.DefaultTime2Wait);
+
+		kvp[13].key = "DefaultTime2Retain";
+		WRITE_NUM(kvp[13].value, s->mine.DefaultTime2Retain);
+
+		if (errors) {
 			free(kvp);
+			log_warnx("initiator_login_kvp: errors found");
 			return NULL;
 		}
+
 		break;
 	default:
 		log_warnx("initiator_login_kvp: exit stage left");
@@ -290,6 +364,11 @@ initiator_login_kvp(struct connection *c
 	} 
 	return kvp;
 }
+
+#undef WRITE_DIGEST
+#undef WRITE_INT
+#undef WRITE_NUM
+#undef WRITE_BOOL
 
 struct pdu *
 initiator_login_build(struct connection *c, struct task_login *tl)
Index: usr.sbin/iscsid/iscsid.c
===================================================================
RCS file: /cvs/src/usr.sbin/iscsid/iscsid.c,v
retrieving revision 1.20
diff -u -p -r1.20 iscsid.c
--- usr.sbin/iscsid/iscsid.c	23 Jan 2017 08:40:07 -0000	1.20
+++ usr.sbin/iscsid/iscsid.c	1 Nov 2017 05:50:14 -0000
@@ -254,6 +254,8 @@ iscsid_ctrl_dispatch(void *ch, struct pd
 				control_compose(ch, CTRL_FAILURE, NULL, 0);
 				goto done;
 			}
+			s->config.HeaderDigest = SESSION_DIGEST_NONE;
+			s->config.DataDigest = SESSION_DIGEST_NONE;
 		}
 
 		session_config(s, sc);
Index: usr.sbin/iscsid/iscsid.h
===================================================================
RCS file: /cvs/src/usr.sbin/iscsid/iscsid.h,v
retrieving revision 1.16
diff -u -p -r1.16 iscsid.h
--- usr.sbin/iscsid/iscsid.h	2 Sep 2016 16:22:31 -0000	1.16
+++ usr.sbin/iscsid/iscsid.h	1 Nov 2017 05:50:14 -0000
@@ -181,6 +181,9 @@ struct session_config {
 	u_int8_t		 disabled;
 };
 
+#define SESSION_DIGEST_NONE	0
+#define SESSION_DIGEST_CRC32	1
+
 #define SESSION_TYPE_NORMAL	0
 #define SESSION_TYPE_DISCOVERY	1
 

You can apply this patch on the checked out source tree:

$ cd /usr/src
$ patch < ~/iscsid.diff

Then you can recompile iscsid(8) and install it:

$ cd usr.sbin/iscsid
$ make
$ doas make install

Now iscsid(8) is able to connect to any Linux iSCSI target.