diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c
index 8d7dd46e..afbe1210 100644
--- a/apps/restconf/restconf_main_native.c
+++ b/apps/restconf/restconf_main_native.c
@@ -188,6 +188,8 @@
/* Forward */
static int restconf_connection(int s, void* arg);
+static int session_id_context = 1;
+
/*! Get restconf native handle
* @param[in] h Clicon handle
* @retval rh Restconf native handle
@@ -740,8 +742,67 @@ restconf_verify_certs(int preverify_ok,
return preverify_ok;
}
-static int session_id_context = 1;
-
+/*! Debug print of all incoming alpn alternatives, eg h2 and http/1.1
+ */
+static int
+dump_alpn_proto_list(const unsigned char *in,
+ unsigned int inlen)
+{
+ unsigned char *inp;
+ unsigned char len;
+ char *str;
+
+ inp = (unsigned char*)in;
+ while ((len = *inp) != 0) {
+ inp++;
+ if ((str = malloc(len+1)) == NULL){
+ clicon_err(OE_UNIX, errno, "malloc");
+ return -1;
+ }
+ strncpy(str, (const char*)inp, len);
+ str[len] = '\0';
+ clicon_debug(1, "%s %s", __FUNCTION__, str);
+ free(str);
+ inp += len;
+ }
+ return 0;
+}
+
+/*! Application-layer Protocol Negotiation (alpn) callback
+ * The value of the out, outlen vector should be set to the value of a single protocol selected from
+ * the in, inlen vector. The out buffer may point directly into in, or to a buffer that outlives the
+ * handshake.
+ */
+static int
+alpn_select_proto_cb(SSL *ssl,
+ const unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *in,
+ unsigned int inlen,
+ void *arg)
+{
+ unsigned char *inp;
+ unsigned char len;
+
+ if (clicon_debug_get())
+ dump_alpn_proto_list(in, inlen);
+ inp = (unsigned char*)in;
+ /* select http/1.1 */
+ inp = (unsigned char*)in;
+ while ((len = *inp) != 0) {
+ inp++;
+ if (len == 8 && strncmp((char*)inp, "http/1.1", len) == 0)
+ // if (len == 2 && strncmp((char*)inp, "h2", len) == 0)
+ break;
+ inp += len;
+ }
+ if (len == 0)
+ return SSL_TLSEXT_ERR_NOACK;
+ *outlen = len;
+ *out = inp;
+ return SSL_TLSEXT_ERR_OK;
+}
+
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
* evhtp_ssl_init:4794
@@ -768,6 +829,8 @@ restconf_ssl_context_create(clicon_handle h)
SSL_CTX_set_options(ctx, SSL_MODE_RELEASE_BUFFERS | SSL_OP_NO_COMPRESSION);
// SSL_CTX_set_timeout(ctx, cfg->ssl_ctx_timeout); /* default 300s */
+ /* Application Layer Protocol Negotiation (alpn) callback */
+ SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, h);
done:
return ctx;
}
@@ -1145,6 +1208,8 @@ restconf_accept_client(int fd,
int er;
int readmore;
X509 *peercert;
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
clicon_debug(1, "%s %d", __FUNCTION__, fd);
if (rsock == NULL){
@@ -1293,6 +1358,17 @@ restconf_accept_client(int fd,
goto ok;
}
}
+ /* Sets data and len to point to the client's requested protocol for this connection. */
+ SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen);
+ if (alpn == NULL) {
+ /* Returns a pointer to the selected protocol in data with length len. */
+ SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+ }
+ clicon_debug(1, "%s ALPN: %d %s", __FUNCTION__, alpnlen, alpn);
+ if (alpn == NULL || alpnlen != 8 || memcmp("http/1.1", alpn, 8) != 0) {
+ clicon_err(OE_RESTCONF, 0, "Protocol http/1.1 not selected: %s", alpn);
+ goto done;
+ }
/* Get the actual peer, XXX this maybe could be done in ca-auth client-cert code ?
* Note this _only_ works if SSL_set1_host() was set previously,...
*/
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index 221b41c7..3fa85f51 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -166,6 +166,24 @@ function testrun()
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" ""
+if [ "${WITH_RESTCONF}" = "native" ]; then # XXX does not work with nginx
+ new "restconf GET http/1.0 - returns 1.0"
+ expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.0 200 OK' "" "" ""
+fi
+ new "restconf GET http/1.1"
+ expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" ""
+
+ new "restconf GET http/2"
+ expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" ""
+
+ if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c
+ new "restconf GET http/2 prior-knowledge (http)"
+ expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta 2>&1)" "16 55" # "Error in the HTTP2 framing layer" "Connection reset by peer"
+ else
+ new "restconf GET http/2 prior-knowledge (https)"
+ expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" ""
+ fi
+
# Negative test GET datastore
if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c
new "Wrong proto=https on http port, expect err 35 wrong version number"