socket: Introduce API osmo_sock_multiaddr_get_name_buf()

An extra osmo_multiaddr_ip_and_port_snprintf() API is introduced which
is used by osmo_sock_multiaddr_get_name_buf() but which will also be
used by other app uers willing to use
osmo_sock_multiaddr_get_ip_and_port() according to its needs (eg. only
printing the local side).

Related: SYS#6636
Change-Id: I48950754ed6f61ee5ffa04a447fab8903f10acc0
diff --git a/TODO-RELEASE b/TODO-RELEASE
index e365746..316c0ec 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -8,7 +8,7 @@
 # If any interfaces have been removed or changed since the last public release: c:r:0.
 #library	what			description / commit summary line
 core      ADD       osmo_sock_multiaddr_{add,del}_local_addr()
-core      ADD       osmo_sock_multiaddr_get_ip_and_port()
+core      ADD       osmo_sock_multiaddr_get_ip_and_port(), osmo_multiaddr_ip_and_port_snprintf(), osmo_sock_multiaddr_get_name_buf()
 core      ADD       gsmtap_inst_fd2() core, DEPRECATE gsmtap_inst_fd()
 isdn		ABI change		add states and flags for external T200 handling
 gsm		ABI change		add T200 timer states to lapdm_datalink
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index b8b04f8..dd14556 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -193,6 +193,10 @@
 
 int osmo_sock_multiaddr_get_ip_and_port(int fd, int ip_proto, char *ip, size_t *ip_cnt, size_t ip_len,
 					char *port, size_t port_len, bool local);
+int osmo_multiaddr_ip_and_port_snprintf(char *str, size_t str_len,
+					const char *ip, size_t ip_cnt, size_t ip_len,
+					const char *portbuf);
+int osmo_sock_multiaddr_get_name_buf(char *str, size_t str_len, int fd, int sk_proto);
 int osmo_sock_multiaddr_add_local_addr(int sfd, const char **addrs, size_t addrs_cnt);
 int osmo_sock_multiaddr_del_local_addr(int sfd, const char **addrs, size_t addrs_cnt);
 
diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map
index ffbbd2f..e7daced 100644
--- a/src/core/libosmocore.map
+++ b/src/core/libosmocore.map
@@ -298,6 +298,7 @@
 osmo_macaddr_parse;
 osmo_mnl_destroy;
 osmo_mnl_init;
+osmo_multiaddr_ip_and_port_snprintf;
 osmo_netdev_add_addr;
 osmo_netdev_add_route;
 osmo_netdev_alloc;
@@ -437,6 +438,7 @@
 osmo_sock_multiaddr_add_local_addr;
 osmo_sock_multiaddr_del_local_addr;
 osmo_sock_multiaddr_get_ip_and_port;
+osmo_sock_multiaddr_get_name_buf;
 osmo_sock_set_dscp;
 osmo_sock_set_priority;
 osmo_sock_unix_init;
diff --git a/src/core/socket.c b/src/core/socket.c
index 51703ef..c600732 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -2018,6 +2018,112 @@
 	return talloc_asprintf(ctx, "(%s)", str);
 }
 
+/*! Format multiple IP addresses and/or port number into a combined string buffer
+ *  \param[out] str  Destination string buffer.
+ *  \param[in] str_len  sizeof(str).
+ *  \param[out] ip Pointer to memory holding ip_cnt consecutive buffers of size ip_len.
+ *  \param[out] ip_cnt length ip array pointer. on return it contains the number of addresses found.
+ *  \param[in] ip_len length of each of the string buffer in the the ip array.
+ *  \param[out] port number (will be printed in when not NULL).
+ *  \return String length as returned by snprintf(), or negative on error.
+ *
+ * This API expectes an ip array as the one filled in by
+ * osmo_sock_multiaddr_get_ip_and_port(), and hence it's a good companion for
+ * that API.
+ */
+int osmo_multiaddr_ip_and_port_snprintf(char *str, size_t str_len,
+					const char *ip, size_t ip_cnt, size_t ip_len,
+					const char *portbuf)
+{
+	struct osmo_strbuf sb = { .buf = str, .len = str_len };
+	bool is_v6 = false;
+	unsigned int i;
+
+	if (ip_cnt == 0) {
+		OSMO_STRBUF_PRINTF(sb, "NULL:%s", portbuf);
+		return sb.chars_needed;
+	}
+	if (ip_cnt > 1)
+		OSMO_STRBUF_PRINTF(sb, "(");
+	else if ((is_v6 = !!strchr(&ip[0], ':'))) /* IPv6, add [] to separate from port. */
+		OSMO_STRBUF_PRINTF(sb, "[");
+
+	for (i = 0; i < ip_cnt - 1; i++)
+		OSMO_STRBUF_PRINTF(sb, "%s|", &ip[i * ip_len]);
+	OSMO_STRBUF_PRINTF(sb, "%s", &ip[i * ip_len]);
+
+	if (ip_cnt > 1)
+		OSMO_STRBUF_PRINTF(sb, ")");
+	else if (is_v6)
+		OSMO_STRBUF_PRINTF(sb, "]");
+	if (portbuf)
+		OSMO_STRBUF_PRINTF(sb, ":%s", portbuf);
+
+	return sb.chars_needed;
+}
+
+/*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10".
+ * This does not include braces like osmo_sock_get_name().
+ *  \param[out] str  Destination string buffer.
+ *  \param[in] str_len  sizeof(str).
+ *  \param[in] fd  File descriptor of socket.
+ *  \param[in] fd IPPROTO of the socket, eg: IPPROTO_SCTP.
+ *  \return String length as returned by snprintf(), or negative on error.
+ */
+int osmo_sock_multiaddr_get_name_buf(char *str, size_t str_len, int fd, int sk_proto)
+{
+	char hostbuf[OSMO_SOCK_MAX_ADDRS][INET6_ADDRSTRLEN];
+	size_t num_hostbuf = ARRAY_SIZE(hostbuf);
+	char portbuf[6];
+	struct osmo_strbuf sb = { .buf = str, .len = str_len };
+
+	if (fd < 0) {
+		osmo_strlcpy(str, "<error-bad-fd>", str_len);
+		return sb.chars_needed;
+	}
+
+	switch (sk_proto) {
+	case IPPROTO_SCTP:
+		break; /* continue below */
+	default:
+		return osmo_sock_get_name_buf(str, str_len, fd);
+	}
+
+	/* get remote */
+	OSMO_STRBUF_PRINTF(sb, "r=");
+	if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf,
+						sizeof(hostbuf[0]), portbuf, sizeof(portbuf), false) != 0) {
+		OSMO_STRBUF_PRINTF(sb, "NULL");
+	} else {
+		const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf);
+		if (need_more_bufs)
+			num_hostbuf = ARRAY_SIZE(hostbuf);
+		OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf,
+				   &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf);
+		if (need_more_bufs)
+			OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>");
+	}
+
+	OSMO_STRBUF_PRINTF(sb, "<->l=");
+
+	/* get local */
+	num_hostbuf = ARRAY_SIZE(hostbuf);
+	if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf,
+						sizeof(hostbuf[0]), portbuf, sizeof(portbuf), true) != 0) {
+		OSMO_STRBUF_PRINTF(sb, "NULL");
+	} else {
+		const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf);
+		if (need_more_bufs)
+			num_hostbuf = ARRAY_SIZE(hostbuf);
+		OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf,
+				   &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf);
+		if (need_more_bufs)
+			OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>");
+	}
+
+	return sb.chars_needed;
+}
+
 /*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10".
  * This does not include braces like osmo_sock_get_name().
  *  \param[out] str  Destination string buffer.