MINOR: connection: implement function to update ALPN

Implement a new function to update the ALPN on an existing connection.
on an existing connection. The ALPN from the ssl context can be checked
to update the ALPN only if it is a subset of the context value.

This method will be useful to change a connection ALPN for websocket,
must notably if the server does not support h2 websocket through the
rfc8441 Extended Connect.

(cherry picked from commit 2454bda1405da260021547d1c9edd676b4219a22)
[ad: adjusted context because of inline function moved from connection.h
 to connection.c]
Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com>
diff --git a/include/haproxy/connection.h b/include/haproxy/connection.h
index 1d65b59..fddda03 100644
--- a/include/haproxy/connection.h
+++ b/include/haproxy/connection.h
@@ -837,6 +837,8 @@
 	return NULL;
 }
 
+int conn_update_alpn(struct connection *conn, const struct ist alpn, int force);
+
 static inline const char *conn_get_ctrl_name(const struct connection *conn)
 {
 	if (!conn || !conn_ctrl_ready(conn))
diff --git a/src/connection.c b/src/connection.c
index 5d30155..f1a2c18 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -88,6 +88,74 @@
 
 }
 
+/* Set the ALPN of connection <conn> to <alpn>. If force is false, <alpn> must
+ * be a subset or identical to the registered protos for the parent SSL_CTX.
+ * In this case <alpn> must be a single protocol value, not a list.
+ *
+ * Returns 0 if ALPN is updated else -1.
+ */
+int conn_update_alpn(struct connection *conn, const struct ist alpn, int force)
+{
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+	size_t alpn_len = istlen(alpn);
+	char *ctx_alpn_str = NULL;
+	int ctx_alpn_len = 0, found = 0;
+
+	/* if not force, first search if alpn is a subset or identical to the
+	 * parent SSL_CTX.
+	 */
+	if (!force) {
+		/* retrieve the SSL_CTX according to the connection side. */
+		if (conn_is_back(conn)) {
+			if (obj_type(conn->target) == OBJ_TYPE_SERVER) {
+				struct server *srv = __objt_server(conn->target);
+				ctx_alpn_str = srv->ssl_ctx.alpn_str;
+				ctx_alpn_len = srv->ssl_ctx.alpn_len;
+			}
+		}
+		else {
+			struct session *sess = conn->owner;
+			struct listener *li = sess->listener;
+
+			if (li->bind_conf && li->bind_conf->is_ssl) {
+				ctx_alpn_str = li->bind_conf->ssl_conf.alpn_str;
+				ctx_alpn_len = li->bind_conf->ssl_conf.alpn_len;
+			}
+		}
+
+		if (ctx_alpn_str) {
+			/* search if ALPN is present in SSL_CTX ALPN before
+			 * using it.
+			 */
+			while (ctx_alpn_len) {
+				/* skip ALPN whose size is not 8 */
+				if (*ctx_alpn_str != alpn_len - 1) {
+					ctx_alpn_len -= *ctx_alpn_str + 1;
+				}
+				else {
+					if (isteqi(ist2(ctx_alpn_str, alpn_len), alpn)) {
+						found = 1;
+						break;
+					}
+				}
+				ctx_alpn_str += *ctx_alpn_str + 1;
+
+				/* This indicates an invalid ALPN formatted
+				 * string and should never happen. */
+				BUG_ON(ctx_alpn_len < 0);
+			}
+		}
+	}
+
+	if (found || force) {
+		ssl_sock_set_alpn(conn, (const uchar *)istptr(alpn), istlen(alpn));
+		return 0;
+	}
+
+#endif
+	return -1;
+}
+
 /* Send a message over an established connection. It makes use of send() and
  * returns the same return code and errno. If the socket layer is not ready yet
  * then -1 is returned and ENOTSOCK is set into errno. If the fd is not marked