socket: Support setsokopt SCTP_INITMSG in osmo_sock_init2_multiaddr2()

This allows setting several socket paramets which can only be set before
SCTP INIT phase (connect()).

Since no release with osmo_sock_init2_multiaddr2() happened yet, we are
allowed to extend struct osmo_sock_init2_multiaddr_pars without bumping
the version field.

Related: SYS#6558
Change-Id: Ibc55932d954279927d1e70ccce1e8bf32b180d44
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index ba986f8..bbf828f 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -130,6 +130,18 @@
 				bool abort_on_failure;
 				uint32_t value;
 			} sockopt_asconf_supported;
+			struct {
+				bool set;
+				bool abort_on_failure;
+				bool num_ostreams_present;
+				bool max_instreams_present;
+				bool max_attempts_present;
+				bool max_init_timeo_present;
+				uint16_t num_ostreams_value;
+				uint16_t max_instreams_value;
+				uint16_t max_attempts_value;
+				uint16_t max_init_timeo_value;
+			} sockopt_initmsg;
 		} sctp;
 	};
 };
diff --git a/src/core/socket.c b/src/core/socket.c
index 65da203..024757f 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -726,6 +726,46 @@
 #endif
 }
 
+static int setsockopt_sctp_initmsg(int fd, const struct osmo_sock_init2_multiaddr_pars *pars)
+{
+	if (!pars->sctp.sockopt_initmsg.num_ostreams_present &&
+	    !pars->sctp.sockopt_initmsg.max_instreams_present &&
+	    !pars->sctp.sockopt_initmsg.max_attempts_present &&
+	    !pars->sctp.sockopt_initmsg.max_init_timeo_present)
+		return 0; /* nothing to set/do */
+
+#ifdef SCTP_INITMSG
+	struct sctp_initmsg si = {0};
+	socklen_t si_len = sizeof(si);
+	int rc;
+
+	/* If at least one field not present, obtain current value from kernel: */
+	if (!pars->sctp.sockopt_initmsg.num_ostreams_present ||
+	    !pars->sctp.sockopt_initmsg.max_instreams_present ||
+	    !pars->sctp.sockopt_initmsg.max_attempts_present ||
+	    !pars->sctp.sockopt_initmsg.max_init_timeo_present) {
+		rc = getsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, &si_len);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (pars->sctp.sockopt_initmsg.num_ostreams_present)
+		si.sinit_num_ostreams = pars->sctp.sockopt_initmsg.num_ostreams_value;
+	if (pars->sctp.sockopt_initmsg.max_instreams_present)
+		si.sinit_max_instreams = pars->sctp.sockopt_initmsg.max_instreams_value;
+	if (pars->sctp.sockopt_initmsg.max_attempts_present)
+		si.sinit_max_attempts = pars->sctp.sockopt_initmsg.max_attempts_value;
+	if (pars->sctp.sockopt_initmsg.max_init_timeo_present)
+		si.sinit_max_init_timeo = pars->sctp.sockopt_initmsg.max_init_timeo_value;
+
+	return setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, sizeof(si));
+#else
+#pragma message "setsockopt(SCTP_INITMSG) not supported! some SCTP features may not be available!"
+	LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_INITMSG), skipping\n");
+	return -ENOTSUP
+#endif
+}
+
 /*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses.
  *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
  *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
@@ -907,6 +947,20 @@
 			}
 		}
 
+		if (pars && pars->sctp.sockopt_initmsg.set) {
+			rc = setsockopt_sctp_initmsg(sfd, pars);
+			if (rc < 0) {
+				int err = errno;
+				multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+				LOGP(DLGLOBAL, LOGL_ERROR,
+				     "cannot setsockopt(SCTP_INITMSG) socket: %s:%u: %s\n",
+				     strbuf, local_port, strerror(err));
+				if (pars->sctp.sockopt_initmsg.abort_on_failure)
+					goto ret_close;
+				/* do not fail, some parameters will be left as the global default */
+			}
+		}
+
 		/* Build array of addresses taking first entry for each host.
 		   TODO: Ideally we should use backtracking storing last used
 		   indexes and trying next combination if connect() fails .*/